Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge pull request #31101 from Microsoft/cacheParseConfigFile
Caching results of parsing Config file and extended file
  • Loading branch information
sheetalkamat committed Apr 30, 2019
2 parents d102ec0 + 1e22110 commit 9efea31
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 56 deletions.
114 changes: 74 additions & 40 deletions src/compiler/commandLineParser.ts
Expand Up @@ -1346,7 +1346,12 @@ namespace ts {
/**
* Reads the config file, reports errors if any and exits if the config file cannot be found
*/
export function getParsedCommandLineOfConfigFile(configFileName: string, optionsToExtend: CompilerOptions, host: ParseConfigFileHost): ParsedCommandLine | undefined {
export function getParsedCommandLineOfConfigFile(
configFileName: string,
optionsToExtend: CompilerOptions,
host: ParseConfigFileHost,
extendedConfigCache?: Map<ExtendedConfigCacheEntry>
): ParsedCommandLine | undefined {
let configFileText: string | undefined;
try {
configFileText = host.readFile(configFileName);
Expand All @@ -1367,7 +1372,16 @@ namespace ts {
result.path = toPath(configFileName, cwd, createGetCanonicalFileName(host.useCaseSensitiveFileNames));
result.resolvedPath = result.path;
result.originalFileName = result.fileName;
return parseJsonSourceFileConfigFileContent(result, host, getNormalizedAbsolutePath(getDirectoryPath(configFileName), cwd), optionsToExtend, getNormalizedAbsolutePath(configFileName, cwd));
return parseJsonSourceFileConfigFileContent(
result,
host,
getNormalizedAbsolutePath(getDirectoryPath(configFileName), cwd),
optionsToExtend,
getNormalizedAbsolutePath(configFileName, cwd),
/*resolutionStack*/ undefined,
/*extraFileExtension*/ undefined,
extendedConfigCache
);
}

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

/*@internal*/
Expand Down Expand Up @@ -2021,11 +2035,12 @@ namespace ts {
configFileName?: string,
resolutionStack: Path[] = [],
extraFileExtensions: ReadonlyArray<FileExtensionInfo> = [],
extendedConfigCache?: Map<ExtendedConfigCacheEntry>
): ParsedCommandLine {
Debug.assert((json === undefined && sourceFile !== undefined) || (json !== undefined && sourceFile === undefined));
const errors: Diagnostic[] = [];

const parsedConfig = parseConfig(json, sourceFile, host, basePath, configFileName, resolutionStack, errors);
const parsedConfig = parseConfig(json, sourceFile, host, basePath, configFileName, resolutionStack, errors, extendedConfigCache);
const { raw } = parsedConfig;
const options = extend(existingOptions, parsedConfig.options || {});
options.configFilePath = configFileName && normalizeSlashes(configFileName);
Expand Down Expand Up @@ -2173,7 +2188,7 @@ namespace ts {
return existingErrors !== configParseDiagnostics.length;
}

interface ParsedTsconfig {
export interface ParsedTsconfig {
raw: any;
options?: CompilerOptions;
typeAcquisition?: TypeAcquisition;
Expand All @@ -2192,13 +2207,14 @@ namespace ts {
* It does *not* resolve the included files.
*/
function parseConfig(
json: any,
sourceFile: TsConfigSourceFile | undefined,
host: ParseConfigHost,
basePath: string,
configFileName: string | undefined,
resolutionStack: string[],
errors: Push<Diagnostic>,
json: any,
sourceFile: TsConfigSourceFile | undefined,
host: ParseConfigHost,
basePath: string,
configFileName: string | undefined,
resolutionStack: string[],
errors: Push<Diagnostic>,
extendedConfigCache?: Map<ExtendedConfigCacheEntry>
): ParsedTsconfig {
basePath = normalizeSlashes(basePath);
const resolvedPath = getNormalizedAbsolutePath(configFileName || "", basePath);
Expand All @@ -2215,7 +2231,7 @@ namespace ts {
if (ownConfig.extendedConfigPath) {
// copy the resolution stack so it is never reused between branches in potential diamond-problem scenarios.
resolutionStack = resolutionStack.concat([resolvedPath]);
const extendedConfig = getExtendedConfig(sourceFile, ownConfig.extendedConfigPath, host, basePath, resolutionStack, errors);
const extendedConfig = getExtendedConfig(sourceFile, ownConfig.extendedConfigPath, host, basePath, resolutionStack, errors, extendedConfigCache);
if (extendedConfig && isSuccessfulParsedTsconfig(extendedConfig)) {
const baseRaw = extendedConfig.raw;
const raw = ownConfig.raw;
Expand Down Expand Up @@ -2359,47 +2375,65 @@ namespace ts {
return undefined;
}

export interface ExtendedConfigCacheEntry {
extendedResult: TsConfigSourceFile;
extendedConfig: ParsedTsconfig | undefined;
}

function getExtendedConfig(
sourceFile: TsConfigSourceFile | undefined,
extendedConfigPath: string,
host: ParseConfigHost,
basePath: string,
resolutionStack: string[],
errors: Push<Diagnostic>,
extendedConfigCache?: Map<ExtendedConfigCacheEntry>
): ParsedTsconfig | undefined {
const extendedResult = readJsonConfigFile(extendedConfigPath, path => host.readFile(path));
const path = host.useCaseSensitiveFileNames ? extendedConfigPath : toLowerCase(extendedConfigPath);
let value: ExtendedConfigCacheEntry | undefined;
let extendedResult: TsConfigSourceFile;
let extendedConfig: ParsedTsconfig | undefined;
if (extendedConfigCache && (value = extendedConfigCache.get(path))) {
({ extendedResult, extendedConfig } = value);
}
else {
extendedResult = readJsonConfigFile(extendedConfigPath, path => host.readFile(path));
if (!extendedResult.parseDiagnostics.length) {
const extendedDirname = getDirectoryPath(extendedConfigPath);
extendedConfig = parseConfig(/*json*/ undefined, extendedResult, host, extendedDirname,
getBaseFileName(extendedConfigPath), resolutionStack, errors, extendedConfigCache);

if (isSuccessfulParsedTsconfig(extendedConfig)) {
// Update the paths to reflect base path
const relativeDifference = convertToRelativePath(extendedDirname, basePath, identity);
const updatePath = (path: string) => isRootedDiskPath(path) ? path : combinePaths(relativeDifference, path);
const mapPropertiesInRawIfNotUndefined = (propertyName: string) => {
if (raw[propertyName]) {
raw[propertyName] = map(raw[propertyName], updatePath);
}
};

const { raw } = extendedConfig;
mapPropertiesInRawIfNotUndefined("include");
mapPropertiesInRawIfNotUndefined("exclude");
mapPropertiesInRawIfNotUndefined("files");
}
}
if (extendedConfigCache) {
extendedConfigCache.set(path, { extendedResult, extendedConfig });
}
}
if (sourceFile) {
sourceFile.extendedSourceFiles = [extendedResult.fileName];
if (extendedResult.extendedSourceFiles) {
sourceFile.extendedSourceFiles.push(...extendedResult.extendedSourceFiles);
}
}
if (extendedResult.parseDiagnostics.length) {
errors.push(...extendedResult.parseDiagnostics);
return undefined;
}

const extendedDirname = getDirectoryPath(extendedConfigPath);
const extendedConfig = parseConfig(/*json*/ undefined, extendedResult, host, extendedDirname,
getBaseFileName(extendedConfigPath), resolutionStack, errors);
if (sourceFile && extendedResult.extendedSourceFiles) {
sourceFile.extendedSourceFiles!.push(...extendedResult.extendedSourceFiles);
}

if (isSuccessfulParsedTsconfig(extendedConfig)) {
// Update the paths to reflect base path
const relativeDifference = convertToRelativePath(extendedDirname, basePath, identity);
const updatePath = (path: string) => isRootedDiskPath(path) ? path : combinePaths(relativeDifference, path);
const mapPropertiesInRawIfNotUndefined = (propertyName: string) => {
if (raw[propertyName]) {
raw[propertyName] = map(raw[propertyName], updatePath);
}
};

const { raw } = extendedConfig;
mapPropertiesInRawIfNotUndefined("include");
mapPropertiesInRawIfNotUndefined("exclude");
mapPropertiesInRawIfNotUndefined("files");
}

return extendedConfig;
return extendedConfig!;
}

function convertCompileOnSaveOptionFromJson(jsonOption: any, basePath: string, errors: Push<Diagnostic>): boolean {
Expand Down
36 changes: 25 additions & 11 deletions src/compiler/program.ts
Expand Up @@ -2678,18 +2678,32 @@ namespace ts {
return fromCache || undefined;
}

// An absolute path pointing to the containing directory of the config file
const basePath = getNormalizedAbsolutePath(getDirectoryPath(refPath), host.getCurrentDirectory());
const sourceFile = host.getSourceFile(refPath, ScriptTarget.JSON) as JsonSourceFile | undefined;
addFileToFilesByName(sourceFile, sourceFilePath, /*redirectedPath*/ undefined);
if (sourceFile === undefined) {
projectReferenceRedirects.set(sourceFilePath, false);
return undefined;
let commandLine: ParsedCommandLine | undefined;
let sourceFile: JsonSourceFile | undefined;
if (host.getParsedCommandLine) {
commandLine = host.getParsedCommandLine(refPath);
if (!commandLine) {
addFileToFilesByName(/*sourceFile*/ undefined, sourceFilePath, /*redirectedPath*/ undefined);
projectReferenceRedirects.set(sourceFilePath, false);
return undefined;
}
sourceFile = Debug.assertDefined(commandLine.options.configFile);
addFileToFilesByName(sourceFile, sourceFilePath, /*redirectedPath*/ undefined);
}
else {
// An absolute path pointing to the containing directory of the config file
const basePath = getNormalizedAbsolutePath(getDirectoryPath(refPath), host.getCurrentDirectory());
sourceFile = host.getSourceFile(refPath, ScriptTarget.JSON) as JsonSourceFile | undefined;
addFileToFilesByName(sourceFile, sourceFilePath, /*redirectedPath*/ undefined);
if (sourceFile === undefined) {
projectReferenceRedirects.set(sourceFilePath, false);
return undefined;
}
sourceFile.path = sourceFilePath;
sourceFile.resolvedPath = sourceFilePath;
sourceFile.originalFileName = refPath;
commandLine = parseJsonSourceFileConfigFileContent(sourceFile, configParsingHost, basePath, /*existingOptions*/ undefined, refPath);
}
sourceFile.path = sourceFilePath;
sourceFile.resolvedPath = sourceFilePath;
sourceFile.originalFileName = refPath;
const commandLine = parseJsonSourceFileConfigFileContent(sourceFile, configParsingHost, basePath, /*existingOptions*/ undefined, refPath);
const resolvedRef: ResolvedProjectReference = { commandLine, sourceFile };
projectReferenceRedirects.set(sourceFilePath, resolvedRef);
if (commandLine.projectReferences) {
Expand Down
6 changes: 5 additions & 1 deletion src/compiler/tsbuild.ts
Expand Up @@ -399,12 +399,14 @@ namespace ts {
let projectCompilerOptions = baseCompilerOptions;
const compilerHost = createCompilerHostFromProgramHost(host, () => projectCompilerOptions);
setGetSourceFileAsHashVersioned(compilerHost, host);
compilerHost.getParsedCommandLine = parseConfigFile;

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

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

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

let diagnostic: Diagnostic | undefined;
parseConfigFileHost.onUnRecoverableConfigFileDiagnostic = d => diagnostic = d;
const parsed = getParsedCommandLineOfConfigFile(configFilePath, baseCompilerOptions, parseConfigFileHost);
const parsed = getParsedCommandLineOfConfigFile(configFilePath, baseCompilerOptions, parseConfigFileHost, extendedConfigCache);
parseConfigFileHost.onUnRecoverableConfigFileDiagnostic = noop;
configFileCache.setValue(configFilePath, parsed || diagnostic!);
return parsed;
Expand Down Expand Up @@ -1395,6 +1397,7 @@ namespace ts {
} = changeCompilerHostLikeToUseCache(host, toPath, (...args) => savedGetSourceFile.call(compilerHost, ...args));
readFileWithCache = newReadFileWithCache;
compilerHost.getSourceFile = getSourceFileWithCache!;
extendedConfigCache = createMap();

const originalResolveModuleNames = compilerHost.resolveModuleNames;
if (!compilerHost.resolveModuleNames) {
Expand Down Expand Up @@ -1463,6 +1466,7 @@ namespace ts {
host.writeFile = originalWriteFile;
compilerHost.getSourceFile = savedGetSourceFile;
readFileWithCache = savedReadFileWithCache;
extendedConfigCache = undefined;
compilerHost.resolveModuleNames = originalResolveModuleNames;
moduleResolutionCache = undefined;
return anyFailed ? ExitStatus.DiagnosticsPresent_OutputsSkipped : ExitStatus.Success;
Expand Down
1 change: 1 addition & 0 deletions src/compiler/types.ts
Expand Up @@ -5131,6 +5131,7 @@ namespace ts {
/* @internal */ hasInvalidatedResolution?: HasInvalidatedResolution;
/* @internal */ hasChangedAutomaticTypeDirectiveNames?: boolean;
createHash?(data: string): string;
getParsedCommandLine?(fileName: string): ParsedCommandLine | undefined;

// TODO: later handle this in better way in builder host instead once the api for tsbuild finalizes and doesnt use compilerHost as base
/*@internal*/createDirectory?(directory: string): void;
Expand Down
18 changes: 16 additions & 2 deletions tests/baselines/reference/api/tsserverlibrary.d.ts
Expand Up @@ -2761,6 +2761,7 @@ declare namespace ts {
resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string, redirectedReference?: ResolvedProjectReference): (ResolvedTypeReferenceDirective | undefined)[];
getEnvironmentVariable?(name: string): string | undefined;
createHash?(data: string): string;
getParsedCommandLine?(fileName: string): ParsedCommandLine | undefined;
}
interface SourceMapRange extends TextRange {
source?: SourceMapSource;
Expand Down Expand Up @@ -3632,7 +3633,7 @@ declare namespace ts {
/**
* Reads the config file, reports errors if any and exits if the config file cannot be found
*/
function getParsedCommandLineOfConfigFile(configFileName: string, optionsToExtend: CompilerOptions, host: ParseConfigFileHost): ParsedCommandLine | undefined;
function getParsedCommandLineOfConfigFile(configFileName: string, optionsToExtend: CompilerOptions, host: ParseConfigFileHost, extendedConfigCache?: Map<ExtendedConfigCacheEntry>): ParsedCommandLine | undefined;
/**
* Read tsconfig.json file
* @param fileName The path to the config file
Expand Down Expand Up @@ -3674,7 +3675,20 @@ declare namespace ts {
* @param basePath A root directory to resolve relative path entries in the config
* file to. e.g. outDir
*/
function parseJsonSourceFileConfigFileContent(sourceFile: TsConfigSourceFile, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string, resolutionStack?: Path[], extraFileExtensions?: ReadonlyArray<FileExtensionInfo>): ParsedCommandLine;
function parseJsonSourceFileConfigFileContent(sourceFile: TsConfigSourceFile, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string, resolutionStack?: Path[], extraFileExtensions?: ReadonlyArray<FileExtensionInfo>, /*@internal*/ extendedConfigCache?: Map<ExtendedConfigCacheEntry>): ParsedCommandLine;
interface ParsedTsconfig {
raw: any;
options?: CompilerOptions;
typeAcquisition?: TypeAcquisition;
/**
* Note that the case of the config path has not yet been normalized, as no files have been imported into the project yet
*/
extendedConfigPath?: string;
}
interface ExtendedConfigCacheEntry {
extendedResult: TsConfigSourceFile;
extendedConfig: ParsedTsconfig | undefined;
}
function convertCompilerOptionsFromJson(jsonOptions: any, basePath: string, configFileName?: string): {
options: CompilerOptions;
errors: Diagnostic[];
Expand Down

0 comments on commit 9efea31

Please sign in to comment.