@@ -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
} ,
@@ -1034,6 +1037,7 @@ const commandOptionsWithoutBuild: CommandLineOption[] = [
1034
1037
name : "paths" ,
1035
1038
type : "object" ,
1036
1039
affectsModuleResolution : true ,
1040
+ allowConfigDirTemplateSubstitution : true ,
1037
1041
isTSConfigOnly : true ,
1038
1042
category : Diagnostics . Modules ,
1039
1043
description : Diagnostics . Specify_a_set_of_entries_that_re_map_imports_to_additional_lookup_locations ,
@@ -1051,6 +1055,7 @@ const commandOptionsWithoutBuild: CommandLineOption[] = [
1051
1055
isFilePath : true ,
1052
1056
} ,
1053
1057
affectsModuleResolution : true ,
1058
+ allowConfigDirTemplateSubstitution : true ,
1054
1059
category : Diagnostics . Modules ,
1055
1060
description : Diagnostics . Allow_multiple_folders_to_be_treated_as_one_when_resolving_modules ,
1056
1061
transpileOptionValue : undefined ,
@@ -1065,6 +1070,7 @@ const commandOptionsWithoutBuild: CommandLineOption[] = [
1065
1070
isFilePath : true ,
1066
1071
} ,
1067
1072
affectsModuleResolution : true ,
1073
+ allowConfigDirTemplateSubstitution : true ,
1068
1074
category : Diagnostics . Modules ,
1069
1075
description : Diagnostics . Specify_multiple_folders_that_act_like_Slashnode_modules_Slash_types ,
1070
1076
} ,
@@ -1600,6 +1606,15 @@ export const optionsAffectingProgramStructure: readonly CommandLineOption[] = op
1600
1606
/** @internal */
1601
1607
export const transpileOptionValueCompilerOptions : readonly CommandLineOption [ ] = optionDeclarations . filter ( option => hasProperty ( option , "transpileOptionValue" ) ) ;
1602
1608
1609
+ /** @internal */
1610
+ export const configDirTemplateSubstitutionOptions : readonly CommandLineOption [ ] = optionDeclarations . filter (
1611
+ option => option . allowConfigDirTemplateSubstitution || ( ! option . isCommandLineOnly && option . isFilePath ) ,
1612
+ ) ;
1613
+ /** @internal */
1614
+ export const configDirTemplateSubstitutionWatchOptions : readonly CommandLineOption [ ] = optionsForWatch . filter (
1615
+ option => option . allowConfigDirTemplateSubstitution || ( ! option . isCommandLineOnly && option . isFilePath ) ,
1616
+ ) ;
1617
+
1603
1618
// Build related options
1604
1619
/** @internal */
1605
1620
export const optionsForBuild : CommandLineOption [ ] = [
@@ -2628,6 +2643,9 @@ function serializeOptionBaseObject(
2628
2643
if ( pathOptions && optionDefinition . isFilePath ) {
2629
2644
result . set ( name , getRelativePathFromFile ( pathOptions . configFilePath , getNormalizedAbsolutePath ( value as string , getDirectoryPath ( pathOptions . configFilePath ) ) , getCanonicalFileName ! ) ) ;
2630
2645
}
2646
+ else if ( pathOptions && optionDefinition . type === "list" && optionDefinition . element . isFilePath ) {
2647
+ result . set ( name , ( value as string [ ] ) . map ( v => getRelativePathFromFile ( pathOptions . configFilePath , getNormalizedAbsolutePath ( v , getDirectoryPath ( pathOptions . configFilePath ) ) , getCanonicalFileName ! ) ) ) ;
2648
+ }
2631
2649
else {
2632
2650
result . set ( name , value ) ;
2633
2651
}
@@ -2890,17 +2908,23 @@ function parseJsonConfigFileContentWorker(
2890
2908
2891
2909
const parsedConfig = parseConfig ( json , sourceFile , host , basePath , configFileName , resolutionStack , errors , extendedConfigCache ) ;
2892
2910
const { raw } = parsedConfig ;
2893
- const options = extend ( existingOptions , parsedConfig . options || { } ) ;
2894
- const watchOptions = existingWatchOptions && parsedConfig . watchOptions ?
2895
- extend ( existingWatchOptions , parsedConfig . watchOptions ) :
2896
- parsedConfig . watchOptions || existingWatchOptions ;
2897
-
2911
+ const options = handleOptionConfigDirTemplateSubstitution (
2912
+ extend ( existingOptions , parsedConfig . options || { } ) ,
2913
+ configDirTemplateSubstitutionOptions ,
2914
+ basePath ,
2915
+ ) as CompilerOptions ;
2916
+ const watchOptions = handleWatchOptionsConfigDirTemplateSubstitution (
2917
+ existingWatchOptions && parsedConfig . watchOptions ?
2918
+ extend ( existingWatchOptions , parsedConfig . watchOptions ) :
2919
+ parsedConfig . watchOptions || existingWatchOptions ,
2920
+ basePath ,
2921
+ ) ;
2898
2922
options . configFilePath = configFileName && normalizeSlashes ( configFileName ) ;
2923
+ const basePathForFileNames = normalizePath ( configFileName ? directoryOfCombinedPath ( configFileName , basePath ) : basePath ) ;
2899
2924
const configFileSpecs = getConfigFileSpecs ( ) ;
2900
2925
if ( sourceFile ) sourceFile . configFileSpecs = configFileSpecs ;
2901
2926
setConfigFileInOptions ( options , sourceFile ) ;
2902
2927
2903
- const basePathForFileNames = normalizePath ( configFileName ? directoryOfCombinedPath ( configFileName , basePath ) : basePath ) ;
2904
2928
return {
2905
2929
options,
2906
2930
watchOptions,
@@ -2955,27 +2979,45 @@ function parseJsonConfigFileContentWorker(
2955
2979
includeSpecs = [ defaultIncludeSpec ] ;
2956
2980
isDefaultIncludeSpec = true ;
2957
2981
}
2982
+ let validatedIncludeSpecsBeforeSubstitution : readonly string [ ] | undefined , validatedExcludeSpecsBeforeSubstitution : readonly string [ ] | undefined ;
2958
2983
let validatedIncludeSpecs : readonly string [ ] | undefined , validatedExcludeSpecs : readonly string [ ] | undefined ;
2959
2984
2960
2985
// The exclude spec list is converted into a regular expression, which allows us to quickly
2961
2986
// test whether a file or directory should be excluded before recursively traversing the
2962
2987
// file system.
2963
2988
2964
2989
if ( includeSpecs ) {
2965
- validatedIncludeSpecs = validateSpecs ( includeSpecs , errors , /*disallowTrailingRecursion*/ true , sourceFile , "include" ) ;
2990
+ validatedIncludeSpecsBeforeSubstitution = validateSpecs ( includeSpecs , errors , /*disallowTrailingRecursion*/ true , sourceFile , "include" ) ;
2991
+ validatedIncludeSpecs = getSubstitutedStringArrayWithConfigDirTemplate (
2992
+ validatedIncludeSpecsBeforeSubstitution ,
2993
+ basePathForFileNames ,
2994
+ ) || validatedIncludeSpecsBeforeSubstitution ;
2966
2995
}
2967
2996
2968
2997
if ( excludeSpecs ) {
2969
- validatedExcludeSpecs = validateSpecs ( excludeSpecs , errors , /*disallowTrailingRecursion*/ false , sourceFile , "exclude" ) ;
2998
+ validatedExcludeSpecsBeforeSubstitution = validateSpecs ( excludeSpecs , errors , /*disallowTrailingRecursion*/ false , sourceFile , "exclude" ) ;
2999
+ validatedExcludeSpecs = getSubstitutedStringArrayWithConfigDirTemplate (
3000
+ validatedExcludeSpecsBeforeSubstitution ,
3001
+ basePathForFileNames ,
3002
+ ) || validatedExcludeSpecsBeforeSubstitution ;
2970
3003
}
2971
3004
3005
+ const validatedFilesSpecBeforeSubstitution = filter ( filesSpecs , isString ) ;
3006
+ const validatedFilesSpec = getSubstitutedStringArrayWithConfigDirTemplate (
3007
+ validatedFilesSpecBeforeSubstitution ,
3008
+ basePathForFileNames ,
3009
+ ) || validatedFilesSpecBeforeSubstitution ;
3010
+
2972
3011
return {
2973
3012
filesSpecs,
2974
3013
includeSpecs,
2975
3014
excludeSpecs,
2976
- validatedFilesSpec : filter ( filesSpecs , isString ) ,
3015
+ validatedFilesSpec,
2977
3016
validatedIncludeSpecs,
2978
3017
validatedExcludeSpecs,
3018
+ validatedFilesSpecBeforeSubstitution,
3019
+ validatedIncludeSpecsBeforeSubstitution,
3020
+ validatedExcludeSpecsBeforeSubstitution,
2979
3021
pathPatterns : undefined , // Initialized on first use
2980
3022
isDefaultIncludeSpec,
2981
3023
} ;
@@ -3043,6 +3085,84 @@ function parseJsonConfigFileContentWorker(
3043
3085
}
3044
3086
}
3045
3087
3088
+ /** @internal */
3089
+ export function handleWatchOptionsConfigDirTemplateSubstitution (
3090
+ watchOptions : WatchOptions | undefined ,
3091
+ basePath : string ,
3092
+ ) {
3093
+ return handleOptionConfigDirTemplateSubstitution ( watchOptions , configDirTemplateSubstitutionWatchOptions , basePath ) as WatchOptions | undefined ;
3094
+ }
3095
+
3096
+ function handleOptionConfigDirTemplateSubstitution (
3097
+ options : OptionsBase | undefined ,
3098
+ optionDeclarations : readonly CommandLineOption [ ] ,
3099
+ basePath : string ,
3100
+ ) {
3101
+ if ( ! options ) return options ;
3102
+ let result : OptionsBase | undefined ;
3103
+ for ( const option of optionDeclarations ) {
3104
+ if ( options [ option . name ] !== undefined ) {
3105
+ const value = options [ option . name ] ;
3106
+ switch ( option . type ) {
3107
+ case "string" :
3108
+ Debug . assert ( option . isFilePath ) ;
3109
+ if ( startsWithConfigDirTemplate ( value ) ) {
3110
+ setOptionValue ( option , getSubstitutedPathWithConfigDirTemplate ( value , basePath ) ) ;
3111
+ }
3112
+ break ;
3113
+ case "list" :
3114
+ Debug . assert ( option . element . isFilePath ) ;
3115
+ const listResult = getSubstitutedStringArrayWithConfigDirTemplate ( value as string [ ] , basePath ) ;
3116
+ if ( listResult ) setOptionValue ( option , listResult ) ;
3117
+ break ;
3118
+ case "object" :
3119
+ Debug . assert ( option . name === "paths" ) ;
3120
+ const objectResult = getSubstitutedMapLikeOfStringArrayWithConfigDirTemplate ( value as MapLike < string [ ] > , basePath ) ;
3121
+ if ( objectResult ) setOptionValue ( option , objectResult ) ;
3122
+ break ;
3123
+ default :
3124
+ Debug . fail ( "option type not supported" ) ;
3125
+ }
3126
+ }
3127
+ }
3128
+ return result || options ;
3129
+
3130
+ function setOptionValue ( option : CommandLineOption , value : CompilerOptionsValue ) {
3131
+ ( result ??= assign ( { } , options ) ) [ option . name ] = value ;
3132
+ }
3133
+ }
3134
+
3135
+ const configDirTemplate = `\${configDir}` ;
3136
+ function startsWithConfigDirTemplate ( value : any ) : value is string {
3137
+ return isString ( value ) && startsWith ( value , configDirTemplate , /*ignoreCase*/ true ) ;
3138
+ }
3139
+
3140
+ function getSubstitutedPathWithConfigDirTemplate ( value : string , basePath : string ) {
3141
+ return getNormalizedAbsolutePath ( value . replace ( configDirTemplate , "./" ) , basePath ) ;
3142
+ }
3143
+
3144
+ function getSubstitutedStringArrayWithConfigDirTemplate ( list : readonly string [ ] | undefined , basePath : string ) {
3145
+ if ( ! list ) return list ;
3146
+ let result : string [ ] | undefined ;
3147
+ list . forEach ( ( element , index ) => {
3148
+ if ( ! startsWithConfigDirTemplate ( element ) ) return ;
3149
+ ( result ??= list . slice ( ) ) [ index ] = getSubstitutedPathWithConfigDirTemplate ( element , basePath ) ;
3150
+ } ) ;
3151
+ return result ;
3152
+ }
3153
+
3154
+ function getSubstitutedMapLikeOfStringArrayWithConfigDirTemplate ( mapLike : MapLike < string [ ] > , basePath : string ) {
3155
+ let result : MapLike < string [ ] > | undefined ;
3156
+ const ownKeys = getOwnKeys ( mapLike ) ;
3157
+ ownKeys . forEach ( key => {
3158
+ if ( ! isArray ( mapLike [ key ] ) ) return ;
3159
+ const subStitution = getSubstitutedStringArrayWithConfigDirTemplate ( mapLike [ key ] , basePath ) ;
3160
+ if ( ! subStitution ) return ;
3161
+ ( result ??= assign ( { } , mapLike ) ) [ key ] = subStitution ;
3162
+ } ) ;
3163
+ return result ;
3164
+ }
3165
+
3046
3166
function isErrorNoInputFiles ( error : Diagnostic ) {
3047
3167
return error . code === Diagnostics . No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2 . code ;
3048
3168
}
@@ -3144,9 +3264,10 @@ function parseConfig(
3144
3264
else {
3145
3265
ownConfig . extendedConfigPath . forEach ( extendedConfigPath => applyExtendedConfig ( result , extendedConfigPath ) ) ;
3146
3266
}
3147
- if ( ! ownConfig . raw . include && result . include ) ownConfig . raw . include = result . include ;
3148
- if ( ! ownConfig . raw . exclude && result . exclude ) ownConfig . raw . exclude = result . exclude ;
3149
- if ( ! ownConfig . raw . files && result . files ) ownConfig . raw . files = result . files ;
3267
+ if ( result . include ) ownConfig . raw . include = result . include ;
3268
+ if ( result . exclude ) ownConfig . raw . exclude = result . exclude ;
3269
+ if ( result . files ) ownConfig . raw . files = result . files ;
3270
+
3150
3271
if ( ownConfig . raw . compileOnSave === undefined && result . compileOnSave ) ownConfig . raw . compileOnSave = result . compileOnSave ;
3151
3272
if ( sourceFile && result . extendedSourceFiles ) sourceFile . extendedSourceFiles = arrayFrom ( result . extendedSourceFiles . keys ( ) ) ;
3152
3273
@@ -3163,12 +3284,15 @@ function parseConfig(
3163
3284
const extendsRaw = extendedConfig . raw ;
3164
3285
let relativeDifference : string | undefined ;
3165
3286
const setPropertyInResultIfNotUndefined = ( propertyName : "include" | "exclude" | "files" ) => {
3287
+ if ( ownConfig . raw [ propertyName ] ) return ; // No need to calculate if already set in own config
3166
3288
if ( extendsRaw [ propertyName ] ) {
3167
3289
result [ propertyName ] = map ( extendsRaw [ propertyName ] , ( path : string ) =>
3168
- isRootedDiskPath ( path ) ? path : combinePaths (
3169
- relativeDifference ||= convertToRelativePath ( getDirectoryPath ( extendedConfigPath ) , basePath , createGetCanonicalFileName ( host . useCaseSensitiveFileNames ) ) ,
3170
- path ,
3171
- ) ) ;
3290
+ startsWithConfigDirTemplate ( path ) || isRootedDiskPath ( path ) ?
3291
+ path :
3292
+ combinePaths (
3293
+ relativeDifference ||= convertToRelativePath ( getDirectoryPath ( extendedConfigPath ) , basePath , createGetCanonicalFileName ( host . useCaseSensitiveFileNames ) ) ,
3294
+ path ,
3295
+ ) ) ;
3172
3296
}
3173
3297
} ;
3174
3298
setPropertyInResultIfNotUndefined ( "include" ) ;
@@ -3527,7 +3651,8 @@ export function convertJsonOption(
3527
3651
3528
3652
function normalizeNonListOptionValue ( option : CommandLineOption , basePath : string , value : any ) : CompilerOptionsValue {
3529
3653
if ( option . isFilePath ) {
3530
- value = getNormalizedAbsolutePath ( value , basePath ) ;
3654
+ value = normalizeSlashes ( value ) ;
3655
+ value = ! startsWithConfigDirTemplate ( value ) ? getNormalizedAbsolutePath ( value , basePath ) : value ;
3531
3656
if ( value === "" ) {
3532
3657
value = "." ;
3533
3658
}
0 commit comments