Skip to content

Commit 9efea31

Browse files
authored
Merge pull request #31101 from Microsoft/cacheParseConfigFile
Caching results of parsing Config file and extended file
2 parents d102ec0 + 1e22110 commit 9efea31

File tree

6 files changed

+137
-56
lines changed

6 files changed

+137
-56
lines changed

src/compiler/commandLineParser.ts

+74-40
Original file line numberDiff line numberDiff line change
@@ -1346,7 +1346,12 @@ namespace ts {
13461346
/**
13471347
* Reads the config file, reports errors if any and exits if the config file cannot be found
13481348
*/
1349-
export function getParsedCommandLineOfConfigFile(configFileName: string, optionsToExtend: CompilerOptions, host: ParseConfigFileHost): ParsedCommandLine | undefined {
1349+
export function getParsedCommandLineOfConfigFile(
1350+
configFileName: string,
1351+
optionsToExtend: CompilerOptions,
1352+
host: ParseConfigFileHost,
1353+
extendedConfigCache?: Map<ExtendedConfigCacheEntry>
1354+
): ParsedCommandLine | undefined {
13501355
let configFileText: string | undefined;
13511356
try {
13521357
configFileText = host.readFile(configFileName);
@@ -1367,7 +1372,16 @@ namespace ts {
13671372
result.path = toPath(configFileName, cwd, createGetCanonicalFileName(host.useCaseSensitiveFileNames));
13681373
result.resolvedPath = result.path;
13691374
result.originalFileName = result.fileName;
1370-
return parseJsonSourceFileConfigFileContent(result, host, getNormalizedAbsolutePath(getDirectoryPath(configFileName), cwd), optionsToExtend, getNormalizedAbsolutePath(configFileName, cwd));
1375+
return parseJsonSourceFileConfigFileContent(
1376+
result,
1377+
host,
1378+
getNormalizedAbsolutePath(getDirectoryPath(configFileName), cwd),
1379+
optionsToExtend,
1380+
getNormalizedAbsolutePath(configFileName, cwd),
1381+
/*resolutionStack*/ undefined,
1382+
/*extraFileExtension*/ undefined,
1383+
extendedConfigCache
1384+
);
13711385
}
13721386

13731387
/**
@@ -1981,8 +1995,8 @@ namespace ts {
19811995
* @param basePath A root directory to resolve relative path entries in the config
19821996
* file to. e.g. outDir
19831997
*/
1984-
export function parseJsonSourceFileConfigFileContent(sourceFile: TsConfigSourceFile, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string, resolutionStack?: Path[], extraFileExtensions?: ReadonlyArray<FileExtensionInfo>): ParsedCommandLine {
1985-
return parseJsonConfigFileContentWorker(/*json*/ undefined, sourceFile, host, basePath, existingOptions, configFileName, resolutionStack, extraFileExtensions);
1998+
export function parseJsonSourceFileConfigFileContent(sourceFile: TsConfigSourceFile, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string, resolutionStack?: Path[], extraFileExtensions?: ReadonlyArray<FileExtensionInfo>, /*@internal*/ extendedConfigCache?: Map<ExtendedConfigCacheEntry>): ParsedCommandLine {
1999+
return parseJsonConfigFileContentWorker(/*json*/ undefined, sourceFile, host, basePath, existingOptions, configFileName, resolutionStack, extraFileExtensions, extendedConfigCache);
19862000
}
19872001

19882002
/*@internal*/
@@ -2021,11 +2035,12 @@ namespace ts {
20212035
configFileName?: string,
20222036
resolutionStack: Path[] = [],
20232037
extraFileExtensions: ReadonlyArray<FileExtensionInfo> = [],
2038+
extendedConfigCache?: Map<ExtendedConfigCacheEntry>
20242039
): ParsedCommandLine {
20252040
Debug.assert((json === undefined && sourceFile !== undefined) || (json !== undefined && sourceFile === undefined));
20262041
const errors: Diagnostic[] = [];
20272042

2028-
const parsedConfig = parseConfig(json, sourceFile, host, basePath, configFileName, resolutionStack, errors);
2043+
const parsedConfig = parseConfig(json, sourceFile, host, basePath, configFileName, resolutionStack, errors, extendedConfigCache);
20292044
const { raw } = parsedConfig;
20302045
const options = extend(existingOptions, parsedConfig.options || {});
20312046
options.configFilePath = configFileName && normalizeSlashes(configFileName);
@@ -2173,7 +2188,7 @@ namespace ts {
21732188
return existingErrors !== configParseDiagnostics.length;
21742189
}
21752190

2176-
interface ParsedTsconfig {
2191+
export interface ParsedTsconfig {
21772192
raw: any;
21782193
options?: CompilerOptions;
21792194
typeAcquisition?: TypeAcquisition;
@@ -2192,13 +2207,14 @@ namespace ts {
21922207
* It does *not* resolve the included files.
21932208
*/
21942209
function parseConfig(
2195-
json: any,
2196-
sourceFile: TsConfigSourceFile | undefined,
2197-
host: ParseConfigHost,
2198-
basePath: string,
2199-
configFileName: string | undefined,
2200-
resolutionStack: string[],
2201-
errors: Push<Diagnostic>,
2210+
json: any,
2211+
sourceFile: TsConfigSourceFile | undefined,
2212+
host: ParseConfigHost,
2213+
basePath: string,
2214+
configFileName: string | undefined,
2215+
resolutionStack: string[],
2216+
errors: Push<Diagnostic>,
2217+
extendedConfigCache?: Map<ExtendedConfigCacheEntry>
22022218
): ParsedTsconfig {
22032219
basePath = normalizeSlashes(basePath);
22042220
const resolvedPath = getNormalizedAbsolutePath(configFileName || "", basePath);
@@ -2215,7 +2231,7 @@ namespace ts {
22152231
if (ownConfig.extendedConfigPath) {
22162232
// copy the resolution stack so it is never reused between branches in potential diamond-problem scenarios.
22172233
resolutionStack = resolutionStack.concat([resolvedPath]);
2218-
const extendedConfig = getExtendedConfig(sourceFile, ownConfig.extendedConfigPath, host, basePath, resolutionStack, errors);
2234+
const extendedConfig = getExtendedConfig(sourceFile, ownConfig.extendedConfigPath, host, basePath, resolutionStack, errors, extendedConfigCache);
22192235
if (extendedConfig && isSuccessfulParsedTsconfig(extendedConfig)) {
22202236
const baseRaw = extendedConfig.raw;
22212237
const raw = ownConfig.raw;
@@ -2359,47 +2375,65 @@ namespace ts {
23592375
return undefined;
23602376
}
23612377

2378+
export interface ExtendedConfigCacheEntry {
2379+
extendedResult: TsConfigSourceFile;
2380+
extendedConfig: ParsedTsconfig | undefined;
2381+
}
2382+
23622383
function getExtendedConfig(
23632384
sourceFile: TsConfigSourceFile | undefined,
23642385
extendedConfigPath: string,
23652386
host: ParseConfigHost,
23662387
basePath: string,
23672388
resolutionStack: string[],
23682389
errors: Push<Diagnostic>,
2390+
extendedConfigCache?: Map<ExtendedConfigCacheEntry>
23692391
): ParsedTsconfig | undefined {
2370-
const extendedResult = readJsonConfigFile(extendedConfigPath, path => host.readFile(path));
2392+
const path = host.useCaseSensitiveFileNames ? extendedConfigPath : toLowerCase(extendedConfigPath);
2393+
let value: ExtendedConfigCacheEntry | undefined;
2394+
let extendedResult: TsConfigSourceFile;
2395+
let extendedConfig: ParsedTsconfig | undefined;
2396+
if (extendedConfigCache && (value = extendedConfigCache.get(path))) {
2397+
({ extendedResult, extendedConfig } = value);
2398+
}
2399+
else {
2400+
extendedResult = readJsonConfigFile(extendedConfigPath, path => host.readFile(path));
2401+
if (!extendedResult.parseDiagnostics.length) {
2402+
const extendedDirname = getDirectoryPath(extendedConfigPath);
2403+
extendedConfig = parseConfig(/*json*/ undefined, extendedResult, host, extendedDirname,
2404+
getBaseFileName(extendedConfigPath), resolutionStack, errors, extendedConfigCache);
2405+
2406+
if (isSuccessfulParsedTsconfig(extendedConfig)) {
2407+
// Update the paths to reflect base path
2408+
const relativeDifference = convertToRelativePath(extendedDirname, basePath, identity);
2409+
const updatePath = (path: string) => isRootedDiskPath(path) ? path : combinePaths(relativeDifference, path);
2410+
const mapPropertiesInRawIfNotUndefined = (propertyName: string) => {
2411+
if (raw[propertyName]) {
2412+
raw[propertyName] = map(raw[propertyName], updatePath);
2413+
}
2414+
};
2415+
2416+
const { raw } = extendedConfig;
2417+
mapPropertiesInRawIfNotUndefined("include");
2418+
mapPropertiesInRawIfNotUndefined("exclude");
2419+
mapPropertiesInRawIfNotUndefined("files");
2420+
}
2421+
}
2422+
if (extendedConfigCache) {
2423+
extendedConfigCache.set(path, { extendedResult, extendedConfig });
2424+
}
2425+
}
23712426
if (sourceFile) {
23722427
sourceFile.extendedSourceFiles = [extendedResult.fileName];
2428+
if (extendedResult.extendedSourceFiles) {
2429+
sourceFile.extendedSourceFiles.push(...extendedResult.extendedSourceFiles);
2430+
}
23732431
}
23742432
if (extendedResult.parseDiagnostics.length) {
23752433
errors.push(...extendedResult.parseDiagnostics);
23762434
return undefined;
23772435
}
2378-
2379-
const extendedDirname = getDirectoryPath(extendedConfigPath);
2380-
const extendedConfig = parseConfig(/*json*/ undefined, extendedResult, host, extendedDirname,
2381-
getBaseFileName(extendedConfigPath), resolutionStack, errors);
2382-
if (sourceFile && extendedResult.extendedSourceFiles) {
2383-
sourceFile.extendedSourceFiles!.push(...extendedResult.extendedSourceFiles);
2384-
}
2385-
2386-
if (isSuccessfulParsedTsconfig(extendedConfig)) {
2387-
// Update the paths to reflect base path
2388-
const relativeDifference = convertToRelativePath(extendedDirname, basePath, identity);
2389-
const updatePath = (path: string) => isRootedDiskPath(path) ? path : combinePaths(relativeDifference, path);
2390-
const mapPropertiesInRawIfNotUndefined = (propertyName: string) => {
2391-
if (raw[propertyName]) {
2392-
raw[propertyName] = map(raw[propertyName], updatePath);
2393-
}
2394-
};
2395-
2396-
const { raw } = extendedConfig;
2397-
mapPropertiesInRawIfNotUndefined("include");
2398-
mapPropertiesInRawIfNotUndefined("exclude");
2399-
mapPropertiesInRawIfNotUndefined("files");
2400-
}
2401-
2402-
return extendedConfig;
2436+
return extendedConfig!;
24032437
}
24042438

24052439
function convertCompileOnSaveOptionFromJson(jsonOption: any, basePath: string, errors: Push<Diagnostic>): boolean {

src/compiler/program.ts

+25-11
Original file line numberDiff line numberDiff line change
@@ -2678,18 +2678,32 @@ namespace ts {
26782678
return fromCache || undefined;
26792679
}
26802680

2681-
// An absolute path pointing to the containing directory of the config file
2682-
const basePath = getNormalizedAbsolutePath(getDirectoryPath(refPath), host.getCurrentDirectory());
2683-
const sourceFile = host.getSourceFile(refPath, ScriptTarget.JSON) as JsonSourceFile | undefined;
2684-
addFileToFilesByName(sourceFile, sourceFilePath, /*redirectedPath*/ undefined);
2685-
if (sourceFile === undefined) {
2686-
projectReferenceRedirects.set(sourceFilePath, false);
2687-
return undefined;
2681+
let commandLine: ParsedCommandLine | undefined;
2682+
let sourceFile: JsonSourceFile | undefined;
2683+
if (host.getParsedCommandLine) {
2684+
commandLine = host.getParsedCommandLine(refPath);
2685+
if (!commandLine) {
2686+
addFileToFilesByName(/*sourceFile*/ undefined, sourceFilePath, /*redirectedPath*/ undefined);
2687+
projectReferenceRedirects.set(sourceFilePath, false);
2688+
return undefined;
2689+
}
2690+
sourceFile = Debug.assertDefined(commandLine.options.configFile);
2691+
addFileToFilesByName(sourceFile, sourceFilePath, /*redirectedPath*/ undefined);
2692+
}
2693+
else {
2694+
// An absolute path pointing to the containing directory of the config file
2695+
const basePath = getNormalizedAbsolutePath(getDirectoryPath(refPath), host.getCurrentDirectory());
2696+
sourceFile = host.getSourceFile(refPath, ScriptTarget.JSON) as JsonSourceFile | undefined;
2697+
addFileToFilesByName(sourceFile, sourceFilePath, /*redirectedPath*/ undefined);
2698+
if (sourceFile === undefined) {
2699+
projectReferenceRedirects.set(sourceFilePath, false);
2700+
return undefined;
2701+
}
2702+
sourceFile.path = sourceFilePath;
2703+
sourceFile.resolvedPath = sourceFilePath;
2704+
sourceFile.originalFileName = refPath;
2705+
commandLine = parseJsonSourceFileConfigFileContent(sourceFile, configParsingHost, basePath, /*existingOptions*/ undefined, refPath);
26882706
}
2689-
sourceFile.path = sourceFilePath;
2690-
sourceFile.resolvedPath = sourceFilePath;
2691-
sourceFile.originalFileName = refPath;
2692-
const commandLine = parseJsonSourceFileConfigFileContent(sourceFile, configParsingHost, basePath, /*existingOptions*/ undefined, refPath);
26932707
const resolvedRef: ResolvedProjectReference = { commandLine, sourceFile };
26942708
projectReferenceRedirects.set(sourceFilePath, resolvedRef);
26952709
if (commandLine.projectReferences) {

src/compiler/tsbuild.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -399,12 +399,14 @@ namespace ts {
399399
let projectCompilerOptions = baseCompilerOptions;
400400
const compilerHost = createCompilerHostFromProgramHost(host, () => projectCompilerOptions);
401401
setGetSourceFileAsHashVersioned(compilerHost, host);
402+
compilerHost.getParsedCommandLine = parseConfigFile;
402403

403404
compilerHost.resolveModuleNames = maybeBind(host, host.resolveModuleNames);
404405
compilerHost.resolveTypeReferenceDirectives = maybeBind(host, host.resolveTypeReferenceDirectives);
405406
let moduleResolutionCache = !compilerHost.resolveModuleNames ? createModuleResolutionCache(currentDirectory, getCanonicalFileName) : undefined;
406407

407408
const buildInfoChecked = createFileMap<true>(toPath);
409+
let extendedConfigCache: Map<ExtendedConfigCacheEntry> | undefined;
408410

409411
// Watch state
410412
const builderPrograms = createFileMap<T>(toPath);
@@ -481,7 +483,7 @@ namespace ts {
481483

482484
let diagnostic: Diagnostic | undefined;
483485
parseConfigFileHost.onUnRecoverableConfigFileDiagnostic = d => diagnostic = d;
484-
const parsed = getParsedCommandLineOfConfigFile(configFilePath, baseCompilerOptions, parseConfigFileHost);
486+
const parsed = getParsedCommandLineOfConfigFile(configFilePath, baseCompilerOptions, parseConfigFileHost, extendedConfigCache);
485487
parseConfigFileHost.onUnRecoverableConfigFileDiagnostic = noop;
486488
configFileCache.setValue(configFilePath, parsed || diagnostic!);
487489
return parsed;
@@ -1395,6 +1397,7 @@ namespace ts {
13951397
} = changeCompilerHostLikeToUseCache(host, toPath, (...args) => savedGetSourceFile.call(compilerHost, ...args));
13961398
readFileWithCache = newReadFileWithCache;
13971399
compilerHost.getSourceFile = getSourceFileWithCache!;
1400+
extendedConfigCache = createMap();
13981401

13991402
const originalResolveModuleNames = compilerHost.resolveModuleNames;
14001403
if (!compilerHost.resolveModuleNames) {
@@ -1463,6 +1466,7 @@ namespace ts {
14631466
host.writeFile = originalWriteFile;
14641467
compilerHost.getSourceFile = savedGetSourceFile;
14651468
readFileWithCache = savedReadFileWithCache;
1469+
extendedConfigCache = undefined;
14661470
compilerHost.resolveModuleNames = originalResolveModuleNames;
14671471
moduleResolutionCache = undefined;
14681472
return anyFailed ? ExitStatus.DiagnosticsPresent_OutputsSkipped : ExitStatus.Success;

src/compiler/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -5131,6 +5131,7 @@ namespace ts {
51315131
/* @internal */ hasInvalidatedResolution?: HasInvalidatedResolution;
51325132
/* @internal */ hasChangedAutomaticTypeDirectiveNames?: boolean;
51335133
createHash?(data: string): string;
5134+
getParsedCommandLine?(fileName: string): ParsedCommandLine | undefined;
51345135

51355136
// TODO: later handle this in better way in builder host instead once the api for tsbuild finalizes and doesnt use compilerHost as base
51365137
/*@internal*/createDirectory?(directory: string): void;

tests/baselines/reference/api/tsserverlibrary.d.ts

+16-2
Original file line numberDiff line numberDiff line change
@@ -2761,6 +2761,7 @@ declare namespace ts {
27612761
resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string, redirectedReference?: ResolvedProjectReference): (ResolvedTypeReferenceDirective | undefined)[];
27622762
getEnvironmentVariable?(name: string): string | undefined;
27632763
createHash?(data: string): string;
2764+
getParsedCommandLine?(fileName: string): ParsedCommandLine | undefined;
27642765
}
27652766
interface SourceMapRange extends TextRange {
27662767
source?: SourceMapSource;
@@ -3632,7 +3633,7 @@ declare namespace ts {
36323633
/**
36333634
* Reads the config file, reports errors if any and exits if the config file cannot be found
36343635
*/
3635-
function getParsedCommandLineOfConfigFile(configFileName: string, optionsToExtend: CompilerOptions, host: ParseConfigFileHost): ParsedCommandLine | undefined;
3636+
function getParsedCommandLineOfConfigFile(configFileName: string, optionsToExtend: CompilerOptions, host: ParseConfigFileHost, extendedConfigCache?: Map<ExtendedConfigCacheEntry>): ParsedCommandLine | undefined;
36363637
/**
36373638
* Read tsconfig.json file
36383639
* @param fileName The path to the config file
@@ -3674,7 +3675,20 @@ declare namespace ts {
36743675
* @param basePath A root directory to resolve relative path entries in the config
36753676
* file to. e.g. outDir
36763677
*/
3677-
function parseJsonSourceFileConfigFileContent(sourceFile: TsConfigSourceFile, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string, resolutionStack?: Path[], extraFileExtensions?: ReadonlyArray<FileExtensionInfo>): ParsedCommandLine;
3678+
function parseJsonSourceFileConfigFileContent(sourceFile: TsConfigSourceFile, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string, resolutionStack?: Path[], extraFileExtensions?: ReadonlyArray<FileExtensionInfo>, /*@internal*/ extendedConfigCache?: Map<ExtendedConfigCacheEntry>): ParsedCommandLine;
3679+
interface ParsedTsconfig {
3680+
raw: any;
3681+
options?: CompilerOptions;
3682+
typeAcquisition?: TypeAcquisition;
3683+
/**
3684+
* Note that the case of the config path has not yet been normalized, as no files have been imported into the project yet
3685+
*/
3686+
extendedConfigPath?: string;
3687+
}
3688+
interface ExtendedConfigCacheEntry {
3689+
extendedResult: TsConfigSourceFile;
3690+
extendedConfig: ParsedTsconfig | undefined;
3691+
}
36783692
function convertCompilerOptionsFromJson(jsonOptions: any, basePath: string, configFileName?: string): {
36793693
options: CompilerOptions;
36803694
errors: Diagnostic[];

0 commit comments

Comments
 (0)