@@ -12,6 +12,7 @@ import {
12
12
CommandLineOption ,
13
13
CommandLineOptionOfCustomType ,
14
14
CommandLineOptionOfListType ,
15
+ CommandLineOptionOfObjectType ,
15
16
CompilerOptions ,
16
17
CompilerOptionsValue ,
17
18
ConfigFileSpecs ,
@@ -22,6 +23,7 @@ import {
22
23
createGetCanonicalFileName ,
23
24
Debug ,
24
25
Diagnostic ,
26
+ DiagnosticAndArguments ,
25
27
DiagnosticArguments ,
26
28
DiagnosticMessage ,
27
29
Diagnostics ,
@@ -45,6 +47,7 @@ import {
45
47
flatten ,
46
48
forEach ,
47
49
forEachEntry ,
50
+ forEachPropertyAssignment ,
48
51
forEachTsConfigPropArray ,
49
52
getBaseFileName ,
50
53
getDirectoryPath ,
@@ -95,6 +98,7 @@ import {
95
98
parseJsonText ,
96
99
parsePackageName ,
97
100
Path ,
101
+ PluginImport ,
98
102
PollingWatchKind ,
99
103
PrefixUnaryExpression ,
100
104
ProjectReference ,
@@ -112,7 +116,6 @@ import {
112
116
toPath ,
113
117
tracing ,
114
118
trimString ,
115
- TsConfigOnlyOption ,
116
119
TsConfigSourceFile ,
117
120
TypeAcquisition ,
118
121
unescapeLeadingUnderscores ,
@@ -315,17 +318,27 @@ export const optionsForWatch: CommandLineOption[] = [
315
318
} ,
316
319
{
317
320
name : "watchFactory" ,
318
- type : "string" ,
321
+ type : "string | object " ,
319
322
category : Diagnostics . Watch_and_Build_Modes ,
320
323
description : Diagnostics . Specify_which_factory_to_invoke_watchFile_and_watchDirectory_on ,
321
- extraValidation : watchFactoryToDiagnostic
324
+ extraValidation : watchFactoryToDiagnostic ,
325
+ elementOptions : commandLineOptionsToMap ( [
326
+ {
327
+ name : "name" ,
328
+ type : "string" ,
329
+ description : Diagnostics . Specify_which_factory_to_invoke_watchFile_and_watchDirectory_on ,
330
+ } ,
331
+ ] ) ,
322
332
} ,
323
333
] ;
324
334
325
- function watchFactoryToDiagnostic ( watchFactory : CompilerOptionsValue ) : [ DiagnosticMessage ] | undefined {
326
- return parsePackageName ( watchFactory as string ) . rest ?
327
- [ Diagnostics . watchFactory_name_can_only_be_a_package_name ] :
328
- undefined ;
335
+ function watchFactoryToDiagnostic ( watchFactory : CompilerOptionsValue , valueExpression : Expression | undefined ) {
336
+ const watchFactoryName = isString ( watchFactory ) ? watchFactory : ( watchFactory as PluginImport ) . name ;
337
+ if ( watchFactoryName && ! parsePackageName ( watchFactoryName ) . rest ) return undefined ;
338
+ const diagnostics : DiagnosticAndArguments = [ Diagnostics . watchFactory_name_can_only_be_a_package_name ] ;
339
+ if ( ! valueExpression || ! isObjectLiteralExpression ( valueExpression ) ) return diagnostics ;
340
+ const errorNode = forEachPropertyAssignment ( valueExpression , "name" , prop => prop . initializer ) ;
341
+ return errorNode ? { diagnostics, errorNode } : diagnostics ;
329
342
}
330
343
331
344
/** @internal */
@@ -1747,12 +1760,32 @@ export function parseListTypeOption(opt: CommandLineOptionOfListType, value = ""
1747
1760
return mapDefined ( values , v => validateJsonOptionValue ( opt . element , v || "" , errors ) ) ;
1748
1761
case "boolean" :
1749
1762
case "object" :
1763
+ case "string | object" :
1750
1764
return Debug . fail ( `List of ${ opt . element . type } is not yet supported.` ) ;
1751
1765
default :
1752
1766
return mapDefined ( values , v => parseCustomTypeOption ( opt . element as CommandLineOptionOfCustomType , v , errors ) ) ;
1753
1767
}
1754
1768
}
1755
1769
1770
+ /** @internal */
1771
+ export function parseObjectTypeOption ( opt : CommandLineOptionOfObjectType , value : string | undefined , errors : Diagnostic [ ] ) : { value : CompilerOptionsValue | undefined } | undefined {
1772
+ if ( value === undefined ) return undefined ;
1773
+ value = trimString ( value ) ;
1774
+ if ( startsWith ( value , "-" ) ) return undefined ;
1775
+ if ( opt . type === "string | object" && ! startsWith ( value , "{" ) ) {
1776
+ return { value : validateJsonOptionValue ( opt , value , errors ) } ;
1777
+ }
1778
+ try {
1779
+ const parsedValue = JSON . parse ( value ) ;
1780
+ if ( typeof parsedValue === "object" ) {
1781
+ return { value : validateJsonOptionValue ( opt , parsedValue , errors ) } ;
1782
+ }
1783
+ }
1784
+ catch { } // eslint-disable-line no-empty
1785
+ errors . push ( createCompilerDiagnostic ( Diagnostics . Argument_for_0_option_must_be_Colon_1 , opt . name , getCompilerOptionValueTypeString ( opt ) ) ) ;
1786
+ return { value : undefined } ;
1787
+ }
1788
+
1756
1789
/** @internal */
1757
1790
export interface OptionsBase {
1758
1791
[ option : string ] : CompilerOptionsValue | TsConfigSourceFile | undefined ;
@@ -1930,9 +1963,15 @@ function parseOptionValue(
1930
1963
case "listOrElement" :
1931
1964
Debug . fail ( "listOrElement not supported here" ) ;
1932
1965
break ;
1966
+ case "object" :
1967
+ case "string | object" :
1968
+ const objectResult = parseObjectTypeOption ( opt , args [ i ] , errors ) ;
1969
+ options [ opt . name ] = objectResult ?. value ;
1970
+ if ( objectResult ) i ++ ;
1971
+ break ;
1933
1972
// If not a primitive, the possible types are specified in what is effectively a map of options.
1934
1973
default :
1935
- options [ opt . name ] = parseCustomTypeOption ( opt as CommandLineOptionOfCustomType , args [ i ] , errors ) ;
1974
+ options [ opt . name ] = parseCustomTypeOption ( opt , args [ i ] , errors ) ;
1936
1975
i ++ ;
1937
1976
break ;
1938
1977
}
@@ -2175,30 +2214,30 @@ const extendsOptionDeclaration: CommandLineOptionOfListType = {
2175
2214
type : "listOrElement" ,
2176
2215
element : {
2177
2216
name : "extends" ,
2178
- type : "string"
2217
+ type : "string" ,
2179
2218
} ,
2180
2219
category : Diagnostics . File_Management ,
2181
2220
disallowNullOrUndefined : true ,
2182
2221
} ;
2183
- const compilerOptionsDeclaration : TsConfigOnlyOption = {
2222
+ const compilerOptionsDeclaration : CommandLineOptionOfObjectType = {
2184
2223
name : "compilerOptions" ,
2185
2224
type : "object" ,
2186
2225
elementOptions : getCommandLineCompilerOptionsMap ( ) ,
2187
2226
extraKeyDiagnostics : compilerOptionsDidYouMeanDiagnostics ,
2188
2227
} ;
2189
- const watchOptionsDeclaration : TsConfigOnlyOption = {
2228
+ const watchOptionsDeclaration : CommandLineOptionOfObjectType = {
2190
2229
name : "watchOptions" ,
2191
2230
type : "object" ,
2192
2231
elementOptions : getCommandLineWatchOptionsMap ( ) ,
2193
2232
extraKeyDiagnostics : watchOptionsDidYouMeanDiagnostics ,
2194
2233
} ;
2195
- const typeAcquisitionDeclaration : TsConfigOnlyOption = {
2234
+ const typeAcquisitionDeclaration : CommandLineOptionOfObjectType = {
2196
2235
name : "typeAcquisition" ,
2197
2236
type : "object" ,
2198
2237
elementOptions : getCommandLineTypeAcquisitionMap ( ) ,
2199
2238
extraKeyDiagnostics : typeAcquisitionDidYouMeanDiagnostics
2200
2239
} ;
2201
- let _tsconfigRootOptions : TsConfigOnlyOption ;
2240
+ let _tsconfigRootOptions : CommandLineOptionOfObjectType ;
2202
2241
function getTsconfigRootOptionsMap ( ) {
2203
2242
if ( _tsconfigRootOptions === undefined ) {
2204
2243
_tsconfigRootOptions = {
@@ -2256,12 +2295,12 @@ function getTsconfigRootOptionsMap() {
2256
2295
2257
2296
/** @internal */
2258
2297
export interface JsonConversionNotifier {
2259
- rootOptions : TsConfigOnlyOption ;
2298
+ rootOptions : CommandLineOptionOfObjectType ;
2260
2299
onPropertySet (
2261
2300
keyText : string ,
2262
2301
value : any ,
2263
2302
propertyAssignment : PropertyAssignment ,
2264
- parentOption : TsConfigOnlyOption | undefined ,
2303
+ parentOption : CommandLineOptionOfObjectType | undefined ,
2265
2304
option : CommandLineOption | undefined ,
2266
2305
) : void ;
2267
2306
}
@@ -2322,7 +2361,7 @@ export function convertToJson(
2322
2361
2323
2362
function convertObjectLiteralExpressionToJson (
2324
2363
node : ObjectLiteralExpression ,
2325
- objectOption : TsConfigOnlyOption | undefined ,
2364
+ objectOption : CommandLineOptionOfObjectType | undefined ,
2326
2365
) : any {
2327
2366
const result : any = returnValue ? { } : undefined ;
2328
2367
for ( const element of node . properties ) {
@@ -2402,7 +2441,7 @@ export function convertToJson(
2402
2441
// that satifies it and need it to modify options set in them (for normalizing file paths)
2403
2442
// vs what we set in the json
2404
2443
// If need arises, we can modify this interface and callbacks as needed
2405
- return convertObjectLiteralExpressionToJson ( objectLiteralExpression , option as TsConfigOnlyOption ) ;
2444
+ return convertObjectLiteralExpressionToJson ( objectLiteralExpression , option as CommandLineOptionOfObjectType ) ;
2406
2445
2407
2446
case SyntaxKind . ArrayLiteralExpression :
2408
2447
return convertArrayLiteralExpressionToJson (
@@ -2443,6 +2482,9 @@ function isCompilerOptionsValue(option: CommandLineOption | undefined, value: an
2443
2482
if ( option . type === "listOrElement" ) {
2444
2483
return isArray ( value ) || isCompilerOptionsValue ( option . element , value ) ;
2445
2484
}
2485
+ if ( option . type === "string | object" ) {
2486
+ return isString ( value ) || typeof value === "object" ;
2487
+ }
2446
2488
const expectedType = isString ( option . type ) ? option . type : "string" ;
2447
2489
return typeof value === expectedType ;
2448
2490
}
@@ -2552,6 +2594,7 @@ function getCustomTypeMapOfCommandLineOption(optionDefinition: CommandLineOption
2552
2594
case "number" :
2553
2595
case "boolean" :
2554
2596
case "object" :
2597
+ case "string | object" :
2555
2598
// this is of a type CommandLineOptionOfPrimitiveType
2556
2599
return undefined ;
2557
2600
case "list" :
@@ -3268,7 +3311,7 @@ function parseOwnConfigOfJsonSourceFile(
3268
3311
keyText : string ,
3269
3312
value : any ,
3270
3313
propertyAssignment : PropertyAssignment ,
3271
- parentOption : TsConfigOnlyOption | undefined ,
3314
+ parentOption : CommandLineOptionOfObjectType | undefined ,
3272
3315
option : CommandLineOption | undefined ,
3273
3316
) {
3274
3317
// Ensure value is verified except for extends which is handled in its own way for error reporting
@@ -3279,7 +3322,8 @@ function parseOwnConfigOfJsonSourceFile(
3279
3322
if ( parentOption === compilerOptionsDeclaration ) currentOption = options ;
3280
3323
else if ( parentOption === watchOptionsDeclaration ) currentOption = watchOptions ??= { } ;
3281
3324
else if ( parentOption === typeAcquisitionDeclaration ) currentOption = typeAcquisition ??= getDefaultTypeAcquisition ( configFileName ) ;
3282
- else Debug . fail ( "Unknown option" ) ;
3325
+ // Ignore anything other option that comes through as parent is not from root
3326
+ else return ;
3283
3327
currentOption [ option . name ] = value ;
3284
3328
}
3285
3329
else if ( keyText && parentOption ?. extraKeyDiagnostics ) {
@@ -3491,11 +3535,10 @@ export function convertJsonOption(
3491
3535
return undefined ;
3492
3536
}
3493
3537
if ( isCompilerOptionsValue ( opt , value ) ) {
3494
- const optType = opt . type ;
3495
- if ( ( optType === "list" ) && isArray ( value ) ) {
3538
+ if ( ( opt . type === "list" ) && isArray ( value ) ) {
3496
3539
return convertJsonOptionOfListType ( opt , value , basePath , errors , propertyAssignment , valueExpression as ArrayLiteralExpression | undefined , sourceFile ) ;
3497
3540
}
3498
- else if ( optType === "listOrElement" ) {
3541
+ else if ( opt . type === "listOrElement" ) {
3499
3542
return isArray ( value ) ?
3500
3543
convertJsonOptionOfListType ( opt , value , basePath , errors , propertyAssignment , valueExpression as ArrayLiteralExpression | undefined , sourceFile ) :
3501
3544
convertJsonOption ( opt . element , value , basePath , errors , propertyAssignment , valueExpression , sourceFile ) ;
@@ -3529,9 +3572,11 @@ function validateJsonOptionValue<T extends CompilerOptionsValue>(
3529
3572
sourceFile ?: TsConfigSourceFile ,
3530
3573
) : T | undefined {
3531
3574
if ( isNullOrUndefined ( value ) ) return undefined ;
3532
- const d = opt . extraValidation ?.( value ) ;
3575
+ const d = opt . extraValidation ?.( value , valueExpression ) ;
3533
3576
if ( ! d ) return value ;
3534
- errors . push ( createDiagnosticForNodeInSourceFileOrCompilerDiagnostic ( sourceFile , valueExpression , ...d ) ) ;
3577
+ const diagnostics = isArray ( d ) ? d : d . diagnostics ;
3578
+ const errorNode = isArray ( d ) ? valueExpression : d . errorNode ;
3579
+ errors . push ( createDiagnosticForNodeInSourceFileOrCompilerDiagnostic ( sourceFile , errorNode , ...diagnostics ) ) ;
3535
3580
return undefined ;
3536
3581
}
3537
3582
@@ -3762,20 +3807,16 @@ function matchesExcludeWorker(
3762
3807
function validateSpecs ( specs : readonly string [ ] , errors : Diagnostic [ ] , disallowTrailingRecursion : boolean , jsonSourceFile : TsConfigSourceFile | undefined , specKey : string ) : readonly string [ ] {
3763
3808
return specs . filter ( spec => {
3764
3809
if ( ! isString ( spec ) ) return false ;
3765
- const diag = specToDiagnostic ( spec , disallowTrailingRecursion ) ;
3810
+ const diag = specToDiagnostic ( spec , /*valueExpresion*/ undefined , disallowTrailingRecursion ) ;
3766
3811
if ( diag !== undefined ) {
3767
- errors . push ( createDiagnostic ( ...diag ) ) ;
3812
+ const element = getTsConfigPropArrayElementValue ( jsonSourceFile , specKey , spec ) ;
3813
+ errors . push ( createDiagnosticForNodeInSourceFileOrCompilerDiagnostic ( jsonSourceFile , element , ...diag ) ) ;
3768
3814
}
3769
3815
return diag === undefined ;
3770
3816
} ) ;
3771
-
3772
- function createDiagnostic ( message : DiagnosticMessage , spec : string ) : Diagnostic {
3773
- const element = getTsConfigPropArrayElementValue ( jsonSourceFile , specKey , spec ) ;
3774
- return createDiagnosticForNodeInSourceFileOrCompilerDiagnostic ( jsonSourceFile , element , message , spec ) ;
3775
- }
3776
3817
}
3777
3818
3778
- function specToDiagnostic ( spec : CompilerOptionsValue , disallowTrailingRecursion ?: boolean ) : [ DiagnosticMessage , string ] | undefined {
3819
+ function specToDiagnostic ( spec : CompilerOptionsValue , _valueExpresion ?: Expression , disallowTrailingRecursion ?: boolean ) : DiagnosticAndArguments | undefined {
3779
3820
Debug . assert ( typeof spec === "string" ) ;
3780
3821
if ( disallowTrailingRecursion && invalidTrailingRecursionPattern . test ( spec ) ) {
3781
3822
return [ Diagnostics . File_specification_cannot_end_in_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0 , spec ] ;
@@ -3940,6 +3981,7 @@ function getOptionValueWithEmptyStrings(value: any, option: CommandLineOption):
3940
3981
if ( value === undefined ) return value ;
3941
3982
switch ( option . type ) {
3942
3983
case "object" : // "paths". Can't get any useful information from the value since we blank out strings, so just return "".
3984
+ case "string | object" :
3943
3985
return "" ;
3944
3986
case "string" : // Could be any arbitrary string -- use empty string instead.
3945
3987
return "" ;
@@ -3970,6 +4012,7 @@ function getDefaultValueForOption(option: CommandLineOption): {} {
3970
4012
case "boolean" :
3971
4013
return true ;
3972
4014
case "string" :
4015
+ case "string | object" :
3973
4016
const defaultValue = option . defaultValueDescription ;
3974
4017
return option . isFilePath ? `./${ defaultValue && typeof defaultValue === "string" ? defaultValue : "" } ` : "" ;
3975
4018
case "list" :
0 commit comments