@@ -53,6 +53,7 @@ import {
5353 getFileMatcherPatterns ,
5454 getLocaleSpecificMessage ,
5555 getNormalizedAbsolutePath ,
56+ getOwnKeys ,
5657 getRegexFromPattern ,
5758 getRegularExpressionForWildcard ,
5859 getRegularExpressionsForWildcards ,
@@ -313,6 +314,7 @@ export const optionsForWatch: CommandLineOption[] = [
313314 isFilePath : true ,
314315 extraValidation : specToDiagnostic ,
315316 } ,
317+ allowConfigDirTemplateSubstitution : true ,
316318 category : Diagnostics . Watch_and_Build_Modes ,
317319 description : Diagnostics . Remove_a_list_of_directories_from_the_watch_process ,
318320 } ,
@@ -325,6 +327,7 @@ export const optionsForWatch: CommandLineOption[] = [
325327 isFilePath : true ,
326328 extraValidation : specToDiagnostic ,
327329 } ,
330+ allowConfigDirTemplateSubstitution : true ,
328331 category : Diagnostics . Watch_and_Build_Modes ,
329332 description : Diagnostics . Remove_a_list_of_files_from_the_watch_mode_s_processing ,
330333 } ,
@@ -1034,6 +1037,7 @@ const commandOptionsWithoutBuild: CommandLineOption[] = [
10341037 name : "paths" ,
10351038 type : "object" ,
10361039 affectsModuleResolution : true ,
1040+ allowConfigDirTemplateSubstitution : true ,
10371041 isTSConfigOnly : true ,
10381042 category : Diagnostics . Modules ,
10391043 description : Diagnostics . Specify_a_set_of_entries_that_re_map_imports_to_additional_lookup_locations ,
@@ -1051,6 +1055,7 @@ const commandOptionsWithoutBuild: CommandLineOption[] = [
10511055 isFilePath : true ,
10521056 } ,
10531057 affectsModuleResolution : true ,
1058+ allowConfigDirTemplateSubstitution : true ,
10541059 category : Diagnostics . Modules ,
10551060 description : Diagnostics . Allow_multiple_folders_to_be_treated_as_one_when_resolving_modules ,
10561061 transpileOptionValue : undefined ,
@@ -1065,6 +1070,7 @@ const commandOptionsWithoutBuild: CommandLineOption[] = [
10651070 isFilePath : true ,
10661071 } ,
10671072 affectsModuleResolution : true ,
1073+ allowConfigDirTemplateSubstitution : true ,
10681074 category : Diagnostics . Modules ,
10691075 description : Diagnostics . Specify_multiple_folders_that_act_like_Slashnode_modules_Slash_types ,
10701076 } ,
@@ -1600,6 +1606,15 @@ export const optionsAffectingProgramStructure: readonly CommandLineOption[] = op
16001606/** @internal */
16011607export const transpileOptionValueCompilerOptions : readonly CommandLineOption [ ] = optionDeclarations . filter ( option => hasProperty ( option , "transpileOptionValue" ) ) ;
16021608
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+
16031618// Build related options
16041619/** @internal */
16051620export const optionsForBuild : CommandLineOption [ ] = [
@@ -2628,6 +2643,9 @@ function serializeOptionBaseObject(
26282643 if ( pathOptions && optionDefinition . isFilePath ) {
26292644 result . set ( name , getRelativePathFromFile ( pathOptions . configFilePath , getNormalizedAbsolutePath ( value as string , getDirectoryPath ( pathOptions . configFilePath ) ) , getCanonicalFileName ! ) ) ;
26302645 }
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+ }
26312649 else {
26322650 result . set ( name , value ) ;
26332651 }
@@ -2890,17 +2908,23 @@ function parseJsonConfigFileContentWorker(
28902908
28912909 const parsedConfig = parseConfig ( json , sourceFile , host , basePath , configFileName , resolutionStack , errors , extendedConfigCache ) ;
28922910 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+ ) ;
28982922 options . configFilePath = configFileName && normalizeSlashes ( configFileName ) ;
2923+ const basePathForFileNames = normalizePath ( configFileName ? directoryOfCombinedPath ( configFileName , basePath ) : basePath ) ;
28992924 const configFileSpecs = getConfigFileSpecs ( ) ;
29002925 if ( sourceFile ) sourceFile . configFileSpecs = configFileSpecs ;
29012926 setConfigFileInOptions ( options , sourceFile ) ;
29022927
2903- const basePathForFileNames = normalizePath ( configFileName ? directoryOfCombinedPath ( configFileName , basePath ) : basePath ) ;
29042928 return {
29052929 options,
29062930 watchOptions,
@@ -2955,27 +2979,45 @@ function parseJsonConfigFileContentWorker(
29552979 includeSpecs = [ defaultIncludeSpec ] ;
29562980 isDefaultIncludeSpec = true ;
29572981 }
2982+ let validatedIncludeSpecsBeforeSubstitution : readonly string [ ] | undefined , validatedExcludeSpecsBeforeSubstitution : readonly string [ ] | undefined ;
29582983 let validatedIncludeSpecs : readonly string [ ] | undefined , validatedExcludeSpecs : readonly string [ ] | undefined ;
29592984
29602985 // The exclude spec list is converted into a regular expression, which allows us to quickly
29612986 // test whether a file or directory should be excluded before recursively traversing the
29622987 // file system.
29632988
29642989 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 ;
29662995 }
29672996
29682997 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 ;
29703003 }
29713004
3005+ const validatedFilesSpecBeforeSubstitution = filter ( filesSpecs , isString ) ;
3006+ const validatedFilesSpec = getSubstitutedStringArrayWithConfigDirTemplate (
3007+ validatedFilesSpecBeforeSubstitution ,
3008+ basePathForFileNames ,
3009+ ) || validatedFilesSpecBeforeSubstitution ;
3010+
29723011 return {
29733012 filesSpecs,
29743013 includeSpecs,
29753014 excludeSpecs,
2976- validatedFilesSpec : filter ( filesSpecs , isString ) ,
3015+ validatedFilesSpec,
29773016 validatedIncludeSpecs,
29783017 validatedExcludeSpecs,
3018+ validatedFilesSpecBeforeSubstitution,
3019+ validatedIncludeSpecsBeforeSubstitution,
3020+ validatedExcludeSpecsBeforeSubstitution,
29793021 pathPatterns : undefined , // Initialized on first use
29803022 isDefaultIncludeSpec,
29813023 } ;
@@ -3043,6 +3085,84 @@ function parseJsonConfigFileContentWorker(
30433085 }
30443086}
30453087
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+
30463166function isErrorNoInputFiles ( error : Diagnostic ) {
30473167 return error . code === Diagnostics . No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2 . code ;
30483168}
@@ -3144,9 +3264,10 @@ function parseConfig(
31443264 else {
31453265 ownConfig . extendedConfigPath . forEach ( extendedConfigPath => applyExtendedConfig ( result , extendedConfigPath ) ) ;
31463266 }
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+
31503271 if ( ownConfig . raw . compileOnSave === undefined && result . compileOnSave ) ownConfig . raw . compileOnSave = result . compileOnSave ;
31513272 if ( sourceFile && result . extendedSourceFiles ) sourceFile . extendedSourceFiles = arrayFrom ( result . extendedSourceFiles . keys ( ) ) ;
31523273
@@ -3163,12 +3284,15 @@ function parseConfig(
31633284 const extendsRaw = extendedConfig . raw ;
31643285 let relativeDifference : string | undefined ;
31653286 const setPropertyInResultIfNotUndefined = ( propertyName : "include" | "exclude" | "files" ) => {
3287+ if ( ownConfig . raw [ propertyName ] ) return ; // No need to calculate if already set in own config
31663288 if ( extendsRaw [ propertyName ] ) {
31673289 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+ ) ) ;
31723296 }
31733297 } ;
31743298 setPropertyInResultIfNotUndefined ( "include" ) ;
@@ -3527,7 +3651,8 @@ export function convertJsonOption(
35273651
35283652function normalizeNonListOptionValue ( option : CommandLineOption , basePath : string , value : any ) : CompilerOptionsValue {
35293653 if ( option . isFilePath ) {
3530- value = getNormalizedAbsolutePath ( value , basePath ) ;
3654+ value = normalizeSlashes ( value ) ;
3655+ value = ! startsWithConfigDirTemplate ( value ) ? getNormalizedAbsolutePath ( value , basePath ) : value ;
35313656 if ( value === "" ) {
35323657 value = "." ;
35333658 }
0 commit comments