From 2432b01d7a230a747e4bfc34798a4c301a701625 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Thu, 12 Dec 2024 16:20:52 -0800 Subject: [PATCH 1/4] Reuse config diagnostics between programs with reused structure and identical config files --- src/compiler/program.ts | 64 +++++++++++++++++++++++++++++------------ src/compiler/types.ts | 1 + 2 files changed, 46 insertions(+), 19 deletions(-) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 45d8539f581e3..dc3b878af9744 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -57,6 +57,7 @@ import { DiagnosticArguments, DiagnosticCategory, diagnosticCategoryName, + DiagnosticCollection, DiagnosticMessage, DiagnosticMessageChain, DiagnosticReporter, @@ -246,6 +247,7 @@ import { noTransformers, ObjectLiteralExpression, OperationCanceledException, + optionDeclarations, optionsHaveChanges, PackageId, packageIdToPackageName, @@ -1666,7 +1668,9 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg * Only add diagnostics directly if it always would be done irrespective of program structure reuse. * Otherwise fileProcessingDiagnostics is correct locations so that the diagnostics can be reported in all structure use scenarios */ - const programDiagnostics = createDiagnosticCollection(); + let programDiagnostics: DiagnosticCollection | undefined; + let reuseConfigDiagnostics = false; + let configDiagnostics: DiagnosticCollection | undefined; let lazyProgramDiagnosticExplainingFile: LazyProgramDiagnosticExplainingFile[] | undefined = []; const currentDirectory = host.getCurrentDirectory(); const supportedExtensions = getSupportedExtensions(options); @@ -2006,6 +2010,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg resolvedModules, resolvedTypeReferenceDirectiveNames, resolvedLibReferences, + getConfigDiagnostics: () => configDiagnostics, getResolvedModule, getResolvedModuleFromModuleSpecifier, getResolvedTypeReferenceDirective, @@ -2046,7 +2051,9 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg onProgramCreateComplete(); - verifyCompilerOptions(); + if (!reuseConfigDiagnostics) { + verifyCompilerOptions(); + } performance.mark("afterProgram"); performance.measure("Program", "beforeProgram", "afterProgram"); tracing?.pop(); @@ -2054,12 +2061,18 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg return program; function updateAndGetProgramDiagnostics() { + if (programDiagnostics) { + return programDiagnostics; + } + + programDiagnostics = createDiagnosticCollection(); + configDiagnostics?.getDiagnostics().forEach(d => programDiagnostics!.add(d)); if (lazyProgramDiagnosticExplainingFile) { // Add file processingDiagnostics fileProcessingDiagnostics?.forEach(diagnostic => { switch (diagnostic.kind) { case FilePreprocessingDiagnosticsKind.FilePreprocessingFileExplainingDiagnostic: - return programDiagnostics.add( + return programDiagnostics!.add( createDiagnosticExplainingFile( diagnostic.file && getSourceFileByPath(diagnostic.file), diagnostic.fileProcessingReason, @@ -2068,15 +2081,15 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg ), ); case FilePreprocessingDiagnosticsKind.FilePreprocessingLibReferenceDiagnostic: - return programDiagnostics.add(filePreprocessingLibreferenceDiagnostic(diagnostic)); + return programDiagnostics!.add(filePreprocessingLibreferenceDiagnostic(diagnostic)); case FilePreprocessingDiagnosticsKind.ResolutionDiagnostics: - return diagnostic.diagnostics.forEach(d => programDiagnostics.add(d)); + return diagnostic.diagnostics.forEach(d => programDiagnostics!.add(d)); default: Debug.assertNever(diagnostic); } }); lazyProgramDiagnosticExplainingFile.forEach(({ file, diagnostic, args }) => - programDiagnostics.add( + programDiagnostics!.add( createDiagnosticExplainingFile(file, /*fileProcessingReason*/ undefined, diagnostic, args), ) ); @@ -2722,6 +2735,15 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg filesByName.set(path, filesByName.get(oldFile.path)); }); + if ( + oldOptions.configFile && oldOptions.configFile === options.configFile || + !oldOptions.configFile && !options.configFile && !optionsHaveChanges(oldOptions, options, optionDeclarations) + ) { + oldProgram.getProgramDiagnostics(oldOptions.configFile!); + configDiagnostics = oldProgram.getConfigDiagnostics(); + reuseConfigDiagnostics = true; + } + files = newSourceFiles; fileReasons = oldProgram.getFileIncludeReasons(); fileProcessingDiagnostics = oldProgram.getFileProcessingDiagnostics(); @@ -4308,7 +4330,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg const outputFile = options.outFile; if (!options.tsBuildInfoFile && options.incremental && !outputFile && !options.configFilePath) { - programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_incremental_can_only_be_specified_using_tsconfig_emitting_to_single_file_or_when_option_tsBuildInfoFile_is_specified)); + addConfigDiagnostic(createCompilerDiagnostic(Diagnostics.Option_incremental_can_only_be_specified_using_tsconfig_emitting_to_single_file_or_when_option_tsBuildInfoFile_is_specified)); } verifyDeprecatedCompilerOptions(); @@ -4410,7 +4432,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg else if (firstNonAmbientExternalModuleSourceFile && languageVersion < ScriptTarget.ES2015 && options.module === ModuleKind.None) { // We cannot use createDiagnosticFromNode because nodes do not have parents yet const span = getErrorSpanForNode(firstNonAmbientExternalModuleSourceFile, typeof firstNonAmbientExternalModuleSourceFile.externalModuleIndicator === "boolean" ? firstNonAmbientExternalModuleSourceFile : firstNonAmbientExternalModuleSourceFile.externalModuleIndicator!); - programDiagnostics.add(createFileDiagnostic(firstNonAmbientExternalModuleSourceFile, span.start, span.length, Diagnostics.Cannot_use_imports_exports_or_module_augmentations_when_module_is_none)); + addConfigDiagnostic(createFileDiagnostic(firstNonAmbientExternalModuleSourceFile, span.start, span.length, Diagnostics.Cannot_use_imports_exports_or_module_augmentations_when_module_is_none)); } // Cannot specify module gen that isn't amd or system with --out @@ -4420,7 +4442,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg } else if (options.module === undefined && firstNonAmbientExternalModuleSourceFile) { const span = getErrorSpanForNode(firstNonAmbientExternalModuleSourceFile, typeof firstNonAmbientExternalModuleSourceFile.externalModuleIndicator === "boolean" ? firstNonAmbientExternalModuleSourceFile : firstNonAmbientExternalModuleSourceFile.externalModuleIndicator!); - programDiagnostics.add(createFileDiagnostic(firstNonAmbientExternalModuleSourceFile, span.start, span.length, Diagnostics.Cannot_compile_modules_using_option_0_unless_the_module_flag_is_amd_or_system, "outFile")); + addConfigDiagnostic(createFileDiagnostic(firstNonAmbientExternalModuleSourceFile, span.start, span.length, Diagnostics.Cannot_compile_modules_using_option_0_unless_the_module_flag_is_amd_or_system, "outFile")); } } @@ -4965,7 +4987,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg forEachPropertyAssignment(pathProp.initializer, key, keyProps => { const initializer = keyProps.initializer; if (isArrayLiteralExpression(initializer) && initializer.elements.length > valueIndex) { - programDiagnostics.add(createDiagnosticForNodeInSourceFile(options.configFile!, initializer.elements[valueIndex], message, ...args)); + addConfigDiagnostic(createDiagnosticForNodeInSourceFile(options.configFile!, initializer.elements[valueIndex], message, ...args)); needCompilerDiagnostic = false; } }); @@ -5027,10 +5049,10 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg function createDiagnosticForReference(sourceFile: JsonSourceFile | undefined, index: number, message: DiagnosticMessage, ...args: DiagnosticArguments) { const referencesSyntax = forEachTsConfigPropArray(sourceFile || options.configFile, "references", property => isArrayLiteralExpression(property.initializer) ? property.initializer : undefined); if (referencesSyntax && referencesSyntax.elements.length > index) { - programDiagnostics.add(createDiagnosticForNodeInSourceFile(sourceFile || options.configFile!, referencesSyntax.elements[index], message, ...args)); + addConfigDiagnostic(createDiagnosticForNodeInSourceFile(sourceFile || options.configFile!, referencesSyntax.elements[index], message, ...args)); } else { - programDiagnostics.add(createCompilerDiagnostic(message, ...args)); + addConfigDiagnostic(createCompilerDiagnostic(message, ...args)); } } @@ -5054,18 +5076,18 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg if (compilerOptionsProperty) { // eslint-disable-next-line local/no-in-operator if ("messageText" in message) { - programDiagnostics.add(createDiagnosticForNodeFromMessageChain(options.configFile!, compilerOptionsProperty.name, message)); + addConfigDiagnostic(createDiagnosticForNodeFromMessageChain(options.configFile!, compilerOptionsProperty.name, message)); } else { - programDiagnostics.add(createDiagnosticForNodeInSourceFile(options.configFile!, compilerOptionsProperty.name, message, ...args)); + addConfigDiagnostic(createDiagnosticForNodeInSourceFile(options.configFile!, compilerOptionsProperty.name, message, ...args)); } } // eslint-disable-next-line local/no-in-operator else if ("messageText" in message) { - programDiagnostics.add(createCompilerDiagnosticFromMessageChain(message)); + addConfigDiagnostic(createCompilerDiagnosticFromMessageChain(message)); } else { - programDiagnostics.add(createCompilerDiagnostic(message, ...args)); + addConfigDiagnostic(createCompilerDiagnostic(message, ...args)); } } @@ -5096,10 +5118,10 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg forEachPropertyAssignment(objectLiteral, key1, prop => { // eslint-disable-next-line local/no-in-operator if ("messageText" in message) { - programDiagnostics.add(createDiagnosticForNodeFromMessageChain(options.configFile!, onKey ? prop.name : prop.initializer, message)); + addConfigDiagnostic(createDiagnosticForNodeFromMessageChain(options.configFile!, onKey ? prop.name : prop.initializer, message)); } else { - programDiagnostics.add(createDiagnosticForNodeInSourceFile(options.configFile!, onKey ? prop.name : prop.initializer, message, ...args)); + addConfigDiagnostic(createDiagnosticForNodeInSourceFile(options.configFile!, onKey ? prop.name : prop.initializer, message, ...args)); } needsCompilerDiagnostic = true; }, key2); @@ -5108,7 +5130,11 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg function blockEmittingOfFile(emitFileName: string, diag: Diagnostic) { hasEmitBlockingDiagnostics.set(toPath(emitFileName), true); - programDiagnostics.add(diag); + addConfigDiagnostic(diag); + } + + function addConfigDiagnostic(diag: Diagnostic) { + (configDiagnostics ??= createDiagnosticCollection()).add(diag); } function isEmittedFile(file: string): boolean { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 46ff57009e7fa..d15310730ff32 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4892,6 +4892,7 @@ export interface Program extends ScriptReferenceHost { * @internal */ resolvedLibReferences: Map | undefined; + /** @internal */ getConfigDiagnostics: () => DiagnosticCollection | undefined; /** @internal */ getCurrentPackagesMap(): Map | undefined; /** * Is the file emitted file From 45070c4b4054786ffba4f458c1959ceb41c9df08 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Fri, 13 Dec 2024 00:53:42 +0000 Subject: [PATCH 2/4] Avoid allocation for normalized absolute paths. --- src/compiler/path.ts | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/compiler/path.ts b/src/compiler/path.ts index b05216adc47b5..85705667c55f3 100644 --- a/src/compiler/path.ts +++ b/src/compiler/path.ts @@ -625,7 +625,41 @@ export function getNormalizedPathComponents(path: string, currentDirectory: stri /** @internal */ export function getNormalizedAbsolutePath(fileName: string, currentDirectory: string | undefined): string { + if (isNotNormalizedOrAbsolute(fileName)) { return getPathFromPathComponents(getNormalizedPathComponents(fileName, currentDirectory)); + } + + return fileName; +} + +function isNotNormalizedOrAbsolute(s: string) { + // The path is not absolute. + if (getEncodedRootLength(s) === 0) return true; + + if (s.length > 0) { + const lastChar = s.charCodeAt(s.length - 1); + if (lastChar === CharacterCodes.slash || lastChar === CharacterCodes.backslash) return true; + } + + for (let i = 0, n = s.length - 1; i < n; i++) { + const curr = s.charCodeAt(i); + const next = s.charCodeAt(i + 1); + if (curr === CharacterCodes.dot) { + if (next === CharacterCodes.slash) { + return true; + } + } + else if (curr === CharacterCodes.slash) { + if (next === CharacterCodes.slash) { + return true; + } + } + else if (curr === CharacterCodes.backslash) { + return true; + } + } + + return false; } /** @internal */ From 88149ba270584d6254a7df900592cd44647328f0 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Fri, 13 Dec 2024 09:36:57 -0800 Subject: [PATCH 3/4] Remove debug change --- src/compiler/program.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index dc3b878af9744..39f75b21d3211 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -2739,7 +2739,6 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg oldOptions.configFile && oldOptions.configFile === options.configFile || !oldOptions.configFile && !options.configFile && !optionsHaveChanges(oldOptions, options, optionDeclarations) ) { - oldProgram.getProgramDiagnostics(oldOptions.configFile!); configDiagnostics = oldProgram.getConfigDiagnostics(); reuseConfigDiagnostics = true; } From 209073e0f1066b4dbccaf2d729ab951fe72f4e50 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Fri, 13 Dec 2024 18:50:29 +0000 Subject: [PATCH 4/4] Indent. --- src/compiler/path.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/path.ts b/src/compiler/path.ts index 85705667c55f3..17b8d26520112 100644 --- a/src/compiler/path.ts +++ b/src/compiler/path.ts @@ -626,7 +626,7 @@ export function getNormalizedPathComponents(path: string, currentDirectory: stri /** @internal */ export function getNormalizedAbsolutePath(fileName: string, currentDirectory: string | undefined): string { if (isNotNormalizedOrAbsolute(fileName)) { - return getPathFromPathComponents(getNormalizedPathComponents(fileName, currentDirectory)); + return getPathFromPathComponents(getNormalizedPathComponents(fileName, currentDirectory)); } return fileName;