From fecafebf769140a88c63dcf894117d0d31547a83 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 23 Apr 2019 14:02:08 -0700 Subject: [PATCH 1/3] Add getParsedCommandLine optional method on compiler host to be able to provide parsedCommandLine instead of redoing work --- src/compiler/program.ts | 34 +++++++++++++++++++++++----------- src/compiler/tsbuild.ts | 1 + src/compiler/types.ts | 1 + 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 4f50aa3ab1f59..9103ca2432d74 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -2677,18 +2677,30 @@ 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) { + projectReferenceRedirects.set(sourceFilePath, false); + return undefined; + } + sourceFile = Debug.assertDefined(commandLine.options.configFile); + } + 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) { diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index f60641fe0cfe7..10beb32136140 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -399,6 +399,7 @@ namespace ts { let projectCompilerOptions = baseCompilerOptions; const compilerHost = createCompilerHostFromProgramHost(host, () => projectCompilerOptions); setGetSourceFileAsHashVersioned(compilerHost, host); + compilerHost.getParsedCommandLine = parseConfigFile; const buildInfoChecked = createFileMap(toPath); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index b421efa19e0d8..36b1a32e54a6a 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5130,6 +5130,7 @@ namespace ts { /* @internal */ hasInvalidatedResolution?: HasInvalidatedResolution; /* @internal */ hasChangedAutomaticTypeDirectiveNames?: boolean; createHash?(data: string): string; + /*@internal*/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; From 80f1ba4e42cc9fc58feaa68ce4532980a8701018 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 24 Apr 2019 15:45:55 -0700 Subject: [PATCH 2/3] Cache files extended so we arent calculating the config again and again --- src/compiler/commandLineParser.ts | 114 ++++++++++++++++++++---------- src/compiler/tsbuild.ts | 5 +- 2 files changed, 79 insertions(+), 40 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index e1a8d77b7675a..8005aabcc3538 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -1341,7 +1341,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, + /*@internal*/ extendedConfigCache?: Map + ): ParsedCommandLine | undefined { let configFileText: string | undefined; try { configFileText = host.readFile(configFileName); @@ -1362,7 +1367,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 + ); } /** @@ -1976,8 +1990,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): 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, /*@internal*/ extendedConfigCache?: Map): ParsedCommandLine { + return parseJsonConfigFileContentWorker(/*json*/ undefined, sourceFile, host, basePath, existingOptions, configFileName, resolutionStack, extraFileExtensions, extendedConfigCache); } /*@internal*/ @@ -2016,11 +2030,12 @@ namespace ts { configFileName?: string, resolutionStack: Path[] = [], extraFileExtensions: ReadonlyArray = [], + extendedConfigCache?: Map ): 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); @@ -2187,13 +2202,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, + json: any, + sourceFile: TsConfigSourceFile | undefined, + host: ParseConfigHost, + basePath: string, + configFileName: string | undefined, + resolutionStack: string[], + errors: Push, + extendedConfigCache?: Map ): ParsedTsconfig { basePath = normalizeSlashes(basePath); const resolvedPath = getNormalizedAbsolutePath(configFileName || "", basePath); @@ -2210,7 +2226,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; @@ -2354,6 +2370,12 @@ namespace ts { return undefined; } + /*@internal*/ + export interface ExtendedConfigCacheEntry { + extendedResult: TsConfigSourceFile; + extendedConfig: ParsedTsconfig | undefined; + } + function getExtendedConfig( sourceFile: TsConfigSourceFile | undefined, extendedConfigPath: string, @@ -2361,40 +2383,54 @@ namespace ts { basePath: string, resolutionStack: string[], errors: Push, + extendedConfigCache?: Map ): 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): boolean { diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index 10beb32136140..c5d8c77a1bcd9 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -402,6 +402,7 @@ namespace ts { compilerHost.getParsedCommandLine = parseConfigFile; const buildInfoChecked = createFileMap(toPath); + let extendedConfigCache: Map | undefined; // Watch state const builderPrograms = createFileMap(toPath); @@ -478,7 +479,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; @@ -1368,6 +1369,7 @@ namespace ts { } = changeCompilerHostLikeToUseCache(host, toPath, (...args) => savedGetSourceFile.call(compilerHost, ...args)); readFileWithCache = newReadFileWithCache; compilerHost.getSourceFile = getSourceFileWithCache!; + extendedConfigCache = createMap(); const graph = getGlobalDependencyGraph(); reportBuildQueue(graph); @@ -1429,6 +1431,7 @@ namespace ts { host.writeFile = originalWriteFile; compilerHost.getSourceFile = savedGetSourceFile; readFileWithCache = savedReadFileWithCache; + extendedConfigCache = undefined; return anyFailed ? ExitStatus.DiagnosticsPresent_OutputsSkipped : ExitStatus.Success; } From d4c2fdc6fd7dd8077710673f6d5c736f5a20aa72 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 26 Apr 2019 13:51:18 -0700 Subject: [PATCH 3/3] Make changes to public API --- src/compiler/commandLineParser.ts | 8 +++----- src/compiler/program.ts | 2 ++ src/compiler/types.ts | 2 +- .../reference/api/tsserverlibrary.d.ts | 18 ++++++++++++++++-- tests/baselines/reference/api/typescript.d.ts | 18 ++++++++++++++++-- 5 files changed, 38 insertions(+), 10 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 8005aabcc3538..4e37395f41e50 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -1345,7 +1345,7 @@ namespace ts { configFileName: string, optionsToExtend: CompilerOptions, host: ParseConfigFileHost, - /*@internal*/ extendedConfigCache?: Map + extendedConfigCache?: Map ): ParsedCommandLine | undefined { let configFileText: string | undefined; try { @@ -2183,7 +2183,7 @@ namespace ts { return existingErrors !== configParseDiagnostics.length; } - interface ParsedTsconfig { + export interface ParsedTsconfig { raw: any; options?: CompilerOptions; typeAcquisition?: TypeAcquisition; @@ -2370,7 +2370,6 @@ namespace ts { return undefined; } - /*@internal*/ export interface ExtendedConfigCacheEntry { extendedResult: TsConfigSourceFile; extendedConfig: ParsedTsconfig | undefined; @@ -2398,7 +2397,6 @@ namespace ts { 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 @@ -2423,7 +2421,7 @@ namespace ts { if (sourceFile) { sourceFile.extendedSourceFiles = [extendedResult.fileName]; if (extendedResult.extendedSourceFiles) { - sourceFile.extendedSourceFiles!.push(...extendedResult.extendedSourceFiles); + sourceFile.extendedSourceFiles.push(...extendedResult.extendedSourceFiles); } } if (extendedResult.parseDiagnostics.length) { diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 9103ca2432d74..61030ed39feaf 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -2682,10 +2682,12 @@ namespace ts { 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 diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 36b1a32e54a6a..da5ab5a677263 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5130,7 +5130,7 @@ namespace ts { /* @internal */ hasInvalidatedResolution?: HasInvalidatedResolution; /* @internal */ hasChangedAutomaticTypeDirectiveNames?: boolean; createHash?(data: string): string; - /*@internal*/getParsedCommandLine?(fileName: string): ParsedCommandLine | undefined; + 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; diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 5d5472e840592..c0cc90efdc770 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -2760,6 +2760,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; @@ -3631,7 +3632,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): ParsedCommandLine | undefined; /** * Read tsconfig.json file * @param fileName The path to the config file @@ -3673,7 +3674,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): ParsedCommandLine; + function parseJsonSourceFileConfigFileContent(sourceFile: TsConfigSourceFile, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string, resolutionStack?: Path[], extraFileExtensions?: ReadonlyArray, /*@internal*/ extendedConfigCache?: Map): 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[]; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index e8e2bed3bb8f9..61f5919396d74 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -2760,6 +2760,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; @@ -3631,7 +3632,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): ParsedCommandLine | undefined; /** * Read tsconfig.json file * @param fileName The path to the config file @@ -3673,7 +3674,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): ParsedCommandLine; + function parseJsonSourceFileConfigFileContent(sourceFile: TsConfigSourceFile, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string, resolutionStack?: Path[], extraFileExtensions?: ReadonlyArray, /*@internal*/ extendedConfigCache?: Map): 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[];