@@ -53,6 +53,7 @@ import {
53
53
getFileMatcherPatterns ,
54
54
getLocaleSpecificMessage ,
55
55
getNormalizedAbsolutePath ,
56
+ getOwnKeys ,
56
57
getRegexFromPattern ,
57
58
getRegularExpressionForWildcard ,
58
59
getRegularExpressionsForWildcards ,
@@ -313,6 +314,7 @@ export const optionsForWatch: CommandLineOption[] = [
313
314
isFilePath : true ,
314
315
extraValidation : specToDiagnostic ,
315
316
} ,
317
+ allowConfigDirTemplateSubstitution : true ,
316
318
category : Diagnostics . Watch_and_Build_Modes ,
317
319
description : Diagnostics . Remove_a_list_of_directories_from_the_watch_process ,
318
320
} ,
@@ -325,6 +327,7 @@ export const optionsForWatch: CommandLineOption[] = [
325
327
isFilePath : true ,
326
328
extraValidation : specToDiagnostic ,
327
329
} ,
330
+ allowConfigDirTemplateSubstitution : true ,
328
331
category : Diagnostics . Watch_and_Build_Modes ,
329
332
description : Diagnostics . Remove_a_list_of_files_from_the_watch_mode_s_processing ,
330
333
} ,
@@ -1033,6 +1036,7 @@ const commandOptionsWithoutBuild: CommandLineOption[] = [
1033
1036
name : "paths" ,
1034
1037
type : "object" ,
1035
1038
affectsModuleResolution : true ,
1039
+ allowConfigDirTemplateSubstitution : true ,
1036
1040
isTSConfigOnly : true ,
1037
1041
category : Diagnostics . Modules ,
1038
1042
description : Diagnostics . Specify_a_set_of_entries_that_re_map_imports_to_additional_lookup_locations ,
@@ -1050,6 +1054,7 @@ const commandOptionsWithoutBuild: CommandLineOption[] = [
1050
1054
isFilePath : true ,
1051
1055
} ,
1052
1056
affectsModuleResolution : true ,
1057
+ allowConfigDirTemplateSubstitution : true ,
1053
1058
category : Diagnostics . Modules ,
1054
1059
description : Diagnostics . Allow_multiple_folders_to_be_treated_as_one_when_resolving_modules ,
1055
1060
transpileOptionValue : undefined ,
@@ -1064,6 +1069,7 @@ const commandOptionsWithoutBuild: CommandLineOption[] = [
1064
1069
isFilePath : true ,
1065
1070
} ,
1066
1071
affectsModuleResolution : true ,
1072
+ allowConfigDirTemplateSubstitution : true ,
1067
1073
category : Diagnostics . Modules ,
1068
1074
description : Diagnostics . Specify_multiple_folders_that_act_like_Slashnode_modules_Slash_types ,
1069
1075
} ,
@@ -1599,6 +1605,15 @@ export const optionsAffectingProgramStructure: readonly CommandLineOption[] = op
1599
1605
/** @internal */
1600
1606
export const transpileOptionValueCompilerOptions : readonly CommandLineOption [ ] = optionDeclarations . filter ( option => hasProperty ( option , "transpileOptionValue" ) ) ;
1601
1607
1608
+ /** @internal */
1609
+ export const configDirTemplateSubstitutionOptions : readonly CommandLineOption [ ] = optionDeclarations . filter (
1610
+ option => option . allowConfigDirTemplateSubstitution || ( ! option . isCommandLineOnly && option . isFilePath ) ,
1611
+ ) ;
1612
+ /** @internal */
1613
+ export const configDirTemplateSubstitutionWatchOptions : readonly CommandLineOption [ ] = optionsForWatch . filter (
1614
+ option => option . allowConfigDirTemplateSubstitution || ( ! option . isCommandLineOnly && option . isFilePath ) ,
1615
+ ) ;
1616
+
1602
1617
// Build related options
1603
1618
/** @internal */
1604
1619
export const optionsForBuild : CommandLineOption [ ] = [
@@ -2627,6 +2642,9 @@ function serializeOptionBaseObject(
2627
2642
if ( pathOptions && optionDefinition . isFilePath ) {
2628
2643
result . set ( name , getRelativePathFromFile ( pathOptions . configFilePath , getNormalizedAbsolutePath ( value as string , getDirectoryPath ( pathOptions . configFilePath ) ) , getCanonicalFileName ! ) ) ;
2629
2644
}
2645
+ else if ( pathOptions && optionDefinition . type === "list" && optionDefinition . element . isFilePath ) {
2646
+ result . set ( name , ( value as string [ ] ) . map ( v => getRelativePathFromFile ( pathOptions . configFilePath , getNormalizedAbsolutePath ( v , getDirectoryPath ( pathOptions . configFilePath ) ) , getCanonicalFileName ! ) ) ) ;
2647
+ }
2630
2648
else {
2631
2649
result . set ( name , value ) ;
2632
2650
}
@@ -2890,16 +2908,17 @@ function parseJsonConfigFileContentWorker(
2890
2908
const parsedConfig = parseConfig ( json , sourceFile , host , basePath , configFileName , resolutionStack , errors , extendedConfigCache ) ;
2891
2909
const { raw } = parsedConfig ;
2892
2910
const options = extend ( existingOptions , parsedConfig . options || { } ) ;
2893
- const watchOptions = existingWatchOptions && parsedConfig . watchOptions ?
2911
+ let watchOptions = existingWatchOptions && parsedConfig . watchOptions ?
2894
2912
extend ( existingWatchOptions , parsedConfig . watchOptions ) :
2895
2913
parsedConfig . watchOptions || existingWatchOptions ;
2896
-
2914
+ handleOptionConfigDirTemplateSubstitution ( options , configDirTemplateSubstitutionOptions , basePath ) ;
2915
+ watchOptions = handleWatchOptionsConfigDirTemplateSubstitution ( watchOptions , basePath , ! existingWatchOptions || ! parsedConfig . watchOptions ) ;
2897
2916
options . configFilePath = configFileName && normalizeSlashes ( configFileName ) ;
2917
+ const basePathForFileNames = normalizePath ( configFileName ? directoryOfCombinedPath ( configFileName , basePath ) : basePath ) ;
2898
2918
const configFileSpecs = getConfigFileSpecs ( ) ;
2899
2919
if ( sourceFile ) sourceFile . configFileSpecs = configFileSpecs ;
2900
2920
setConfigFileInOptions ( options , sourceFile ) ;
2901
2921
2902
- const basePathForFileNames = normalizePath ( configFileName ? directoryOfCombinedPath ( configFileName , basePath ) : basePath ) ;
2903
2922
return {
2904
2923
options,
2905
2924
watchOptions,
@@ -2954,27 +2973,48 @@ function parseJsonConfigFileContentWorker(
2954
2973
includeSpecs = [ defaultIncludeSpec ] ;
2955
2974
isDefaultIncludeSpec = true ;
2956
2975
}
2976
+ let validatedIncludeSpecsBeforeSubstitution : readonly string [ ] | undefined , validatedExcludeSpecsBeforeSubstitution : readonly string [ ] | undefined ;
2957
2977
let validatedIncludeSpecs : readonly string [ ] | undefined , validatedExcludeSpecs : readonly string [ ] | undefined ;
2958
2978
2959
2979
// The exclude spec list is converted into a regular expression, which allows us to quickly
2960
2980
// test whether a file or directory should be excluded before recursively traversing the
2961
2981
// file system.
2962
2982
2963
2983
if ( includeSpecs ) {
2964
- validatedIncludeSpecs = validateSpecs ( includeSpecs , errors , /*disallowTrailingRecursion*/ true , sourceFile , "include" ) ;
2984
+ validatedIncludeSpecsBeforeSubstitution = validateSpecs ( includeSpecs , errors , /*disallowTrailingRecursion*/ true , sourceFile , "include" ) ;
2985
+ validatedIncludeSpecs = getSubstitutedStringArrayWithConfigDirTemplate (
2986
+ validatedIncludeSpecsBeforeSubstitution ,
2987
+ basePathForFileNames ,
2988
+ /*createCopyOnSubstitute*/ true ,
2989
+ ) || validatedIncludeSpecsBeforeSubstitution ;
2965
2990
}
2966
2991
2967
2992
if ( excludeSpecs ) {
2968
- validatedExcludeSpecs = validateSpecs ( excludeSpecs , errors , /*disallowTrailingRecursion*/ false , sourceFile , "exclude" ) ;
2993
+ validatedExcludeSpecsBeforeSubstitution = validateSpecs ( excludeSpecs , errors , /*disallowTrailingRecursion*/ false , sourceFile , "exclude" ) ;
2994
+ validatedExcludeSpecs = getSubstitutedStringArrayWithConfigDirTemplate (
2995
+ validatedExcludeSpecsBeforeSubstitution ,
2996
+ basePathForFileNames ,
2997
+ /*createCopyOnSubstitute*/ true ,
2998
+ ) || validatedExcludeSpecsBeforeSubstitution ;
2969
2999
}
2970
3000
3001
+ const validatedFilesSpecBeforeSubstitution = filter ( filesSpecs , isString ) ;
3002
+ const validatedFilesSpec = getSubstitutedStringArrayWithConfigDirTemplate (
3003
+ validatedFilesSpecBeforeSubstitution ,
3004
+ basePathForFileNames ,
3005
+ /*createCopyOnSubstitute*/ true ,
3006
+ ) || validatedFilesSpecBeforeSubstitution ;
3007
+
2971
3008
return {
2972
3009
filesSpecs,
2973
3010
includeSpecs,
2974
3011
excludeSpecs,
2975
- validatedFilesSpec : filter ( filesSpecs , isString ) ,
3012
+ validatedFilesSpec,
2976
3013
validatedIncludeSpecs,
2977
3014
validatedExcludeSpecs,
3015
+ validatedFilesSpecBeforeSubstitution,
3016
+ validatedIncludeSpecsBeforeSubstitution,
3017
+ validatedExcludeSpecsBeforeSubstitution,
2978
3018
pathPatterns : undefined , // Initialized on first use
2979
3019
isDefaultIncludeSpec,
2980
3020
} ;
@@ -3042,6 +3082,97 @@ function parseJsonConfigFileContentWorker(
3042
3082
}
3043
3083
}
3044
3084
3085
+ /** @internal */
3086
+ export function handleWatchOptionsConfigDirTemplateSubstitution (
3087
+ watchOptions : WatchOptions | undefined ,
3088
+ basePath : string ,
3089
+ createCopyOnSubstitute ?: boolean ,
3090
+ ) {
3091
+ return handleOptionConfigDirTemplateSubstitution ( watchOptions , configDirTemplateSubstitutionWatchOptions , basePath , createCopyOnSubstitute ) as WatchOptions | undefined ;
3092
+ }
3093
+
3094
+ function handleOptionConfigDirTemplateSubstitution (
3095
+ options : OptionsBase | undefined ,
3096
+ optionDeclarations : readonly CommandLineOption [ ] ,
3097
+ basePath : string ,
3098
+ createCopyOnSubstitute ?: boolean ,
3099
+ ) {
3100
+ if ( ! options ) return options ;
3101
+ let result : OptionsBase | undefined ;
3102
+ for ( const option of optionDeclarations ) {
3103
+ if ( options [ option . name ] !== undefined ) {
3104
+ const value = options [ option . name ] ;
3105
+ switch ( option . type ) {
3106
+ case "string" :
3107
+ Debug . assert ( option . isFilePath ) ;
3108
+ if ( startsWithConfigDirTemplate ( value as string ) ) {
3109
+ setOptionValue ( option , getSubstitutedPathWithConfigDirTemplate ( value as string , basePath ) ) ;
3110
+ }
3111
+ break ;
3112
+ case "list" :
3113
+ Debug . assert ( option . element . isFilePath ) ;
3114
+ const listResult = getSubstitutedStringArrayWithConfigDirTemplate ( value as string [ ] , basePath , createCopyOnSubstitute ) ;
3115
+ if ( listResult ) setOptionValue ( option , listResult ) ;
3116
+ break ;
3117
+ case "object" :
3118
+ Debug . assert ( option . name === "paths" ) ;
3119
+ const objectResult = getSubstitutedMapLikeOfStringArrayWithConfigDirTemplate ( value as MapLike < string [ ] > , basePath , createCopyOnSubstitute ) ;
3120
+ if ( objectResult ) setOptionValue ( option , objectResult ) ;
3121
+ break ;
3122
+ default :
3123
+ Debug . fail ( "option type not supported" ) ;
3124
+ }
3125
+ }
3126
+ }
3127
+ return result || options ;
3128
+
3129
+ function setOptionValue ( option : CommandLineOption , value : CompilerOptionsValue ) {
3130
+ if ( createCopyOnSubstitute ) {
3131
+ if ( ! result ) result = assign ( { } , options ) ;
3132
+ result [ option . name ] = value ;
3133
+ }
3134
+ else {
3135
+ options ! [ option . name ] = value ;
3136
+ }
3137
+ }
3138
+ }
3139
+
3140
+ const configDirTemplate = `\${configDir}` ;
3141
+ function startsWithConfigDirTemplate ( value : string ) {
3142
+ return startsWith ( value , configDirTemplate , /*ignoreCase*/ true ) ;
3143
+ }
3144
+
3145
+ function getSubstitutedPathWithConfigDirTemplate ( value : string , basePath : string ) {
3146
+ return getNormalizedAbsolutePath ( value . replace ( configDirTemplate , "./" ) , basePath ) ;
3147
+ }
3148
+
3149
+ function getSubstitutedStringArrayWithConfigDirTemplate ( list : string [ ] | undefined , basePath : string , createCopyOnSubstitute ?: boolean ) : string [ ] | undefined ;
3150
+ function getSubstitutedStringArrayWithConfigDirTemplate ( list : readonly string [ ] | undefined , basePath : string , createCopyOnSubstitute : true ) : string [ ] | undefined ;
3151
+ function getSubstitutedStringArrayWithConfigDirTemplate ( list : readonly string [ ] | string [ ] | undefined , basePath : string , createCopyOnSubstitute ?: boolean ) {
3152
+ if ( ! list ) return list ;
3153
+ let result : string [ ] | undefined ;
3154
+ list . forEach ( ( element , index ) => {
3155
+ if ( ! startsWithConfigDirTemplate ( element ) ) return ;
3156
+ if ( createCopyOnSubstitute ) result ??= list . slice ( ) ;
3157
+ else result ??= list as unknown as string [ ] ;
3158
+ result [ index ] = getSubstitutedPathWithConfigDirTemplate ( element , basePath ) ;
3159
+ } ) ;
3160
+ return result ;
3161
+ }
3162
+
3163
+ function getSubstitutedMapLikeOfStringArrayWithConfigDirTemplate ( mapLike : MapLike < string [ ] > , basePath : string , createCopyOnSubstitute ?: boolean ) {
3164
+ let result : MapLike < string [ ] > | undefined ;
3165
+ const ownKeys = getOwnKeys ( mapLike ) ;
3166
+ ownKeys . forEach ( key => {
3167
+ const subStitution = getSubstitutedStringArrayWithConfigDirTemplate ( mapLike [ key ] , basePath , createCopyOnSubstitute ) ;
3168
+ if ( ! subStitution ) return ;
3169
+ if ( createCopyOnSubstitute ) result ??= assign ( { } , mapLike ) ;
3170
+ else result ??= mapLike ;
3171
+ mapLike [ key ] = subStitution ;
3172
+ } ) ;
3173
+ return result ;
3174
+ }
3175
+
3045
3176
function isErrorNoInputFiles ( error : Diagnostic ) {
3046
3177
return error . code === Diagnostics . No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2 . code ;
3047
3178
}
@@ -3143,9 +3274,10 @@ function parseConfig(
3143
3274
else {
3144
3275
ownConfig . extendedConfigPath . forEach ( extendedConfigPath => applyExtendedConfig ( result , extendedConfigPath ) ) ;
3145
3276
}
3146
- if ( ! ownConfig . raw . include && result . include ) ownConfig . raw . include = result . include ;
3147
- if ( ! ownConfig . raw . exclude && result . exclude ) ownConfig . raw . exclude = result . exclude ;
3148
- if ( ! ownConfig . raw . files && result . files ) ownConfig . raw . files = result . files ;
3277
+ if ( result . include ) ownConfig . raw . include = result . include ;
3278
+ if ( result . exclude ) ownConfig . raw . exclude = result . exclude ;
3279
+ if ( result . files ) ownConfig . raw . files = result . files ;
3280
+
3149
3281
if ( ownConfig . raw . compileOnSave === undefined && result . compileOnSave ) ownConfig . raw . compileOnSave = result . compileOnSave ;
3150
3282
if ( sourceFile && result . extendedSourceFiles ) sourceFile . extendedSourceFiles = arrayFrom ( result . extendedSourceFiles . keys ( ) ) ;
3151
3283
@@ -3162,12 +3294,15 @@ function parseConfig(
3162
3294
const extendsRaw = extendedConfig . raw ;
3163
3295
let relativeDifference : string | undefined ;
3164
3296
const setPropertyInResultIfNotUndefined = ( propertyName : "include" | "exclude" | "files" ) => {
3297
+ if ( ownConfig . raw [ propertyName ] ) return ; // No need to calculate if already set in own config
3165
3298
if ( extendsRaw [ propertyName ] ) {
3166
3299
result [ propertyName ] = map ( extendsRaw [ propertyName ] , ( path : string ) =>
3167
- isRootedDiskPath ( path ) ? path : combinePaths (
3168
- relativeDifference ||= convertToRelativePath ( getDirectoryPath ( extendedConfigPath ) , basePath , createGetCanonicalFileName ( host . useCaseSensitiveFileNames ) ) ,
3169
- path ,
3170
- ) ) ;
3300
+ startsWithConfigDirTemplate ( path ) || isRootedDiskPath ( path ) ?
3301
+ path :
3302
+ combinePaths (
3303
+ relativeDifference ||= convertToRelativePath ( getDirectoryPath ( extendedConfigPath ) , basePath , createGetCanonicalFileName ( host . useCaseSensitiveFileNames ) ) ,
3304
+ path ,
3305
+ ) ) ;
3171
3306
}
3172
3307
} ;
3173
3308
setPropertyInResultIfNotUndefined ( "include" ) ;
@@ -3526,7 +3661,8 @@ export function convertJsonOption(
3526
3661
3527
3662
function normalizeNonListOptionValue ( option : CommandLineOption , basePath : string , value : any ) : CompilerOptionsValue {
3528
3663
if ( option . isFilePath ) {
3529
- value = getNormalizedAbsolutePath ( value , basePath ) ;
3664
+ value = normalizeSlashes ( value ) ;
3665
+ value = ! startsWithConfigDirTemplate ( value ) ? getNormalizedAbsolutePath ( value , basePath ) : value ;
3530
3666
if ( value === "" ) {
3531
3667
value = "." ;
3532
3668
}
0 commit comments