diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index df00eca8b0a..aaba45901ff 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -241,10 +241,7 @@ namespace ts { oldCompilerOptions.declarationDir !== compilerOptions.declarationDir || (oldCompilerOptions.outFile || oldCompilerOptions.out) !== (compilerOptions.outFile || compilerOptions.out))) { // Add all files to affectedFilesPendingEmit since emit changed - state.affectedFilesPendingEmit = concatenate(state.affectedFilesPendingEmit, newProgram.getSourceFiles().map(f => f.path)); - if (state.affectedFilesPendingEmitIndex === undefined) { - state.affectedFilesPendingEmitIndex = 0; - } + addToAffectedFilesPendingEmit(state, newProgram.getSourceFiles().map(f => f.path)); Debug.assert(state.seenAffectedFiles === undefined); state.seenAffectedFiles = createMap(); } @@ -343,6 +340,7 @@ namespace ts { // Set the next affected file as seen and remove the cached semantic diagnostics state.affectedFilesIndex = affectedFilesIndex; cleanSemanticDiagnosticsOfAffectedFile(state, affectedFile); + handleDtsMayChangeOfAffectedFile(state, affectedFile) return affectedFile; } seenAffectedFiles.set(affectedFile.path, true); @@ -433,80 +431,7 @@ namespace ts { // If there was change in signature for the changed file, // then delete the semantic diagnostics for files that are affected by using exports of this module - - if (!state.exportedModulesMap || state.affectedFiles!.length === 1 || !state.changedFilesSet.has(affectedFile.path)) { - return; - } - - Debug.assert(!!state.currentAffectedFilesExportedModulesMap); - const seenFileAndExportsOfFile = createMap(); - // Go through exported modules from cache first - // If exported modules has path, all files referencing file exported from are affected - if (forEachEntry(state.currentAffectedFilesExportedModulesMap!, (exportedModules, exportedFromPath) => - exportedModules && - exportedModules.has(affectedFile.path) && - removeSemanticDiagnosticsOfFilesReferencingPath(state, exportedFromPath as Path, seenFileAndExportsOfFile) - )) { - return; - } - - // If exported from path is not from cache and exported modules has path, all files referencing file exported from are affected - forEachEntry(state.exportedModulesMap, (exportedModules, exportedFromPath) => - !state.currentAffectedFilesExportedModulesMap!.has(exportedFromPath) && // If we already iterated this through cache, ignore it - exportedModules.has(affectedFile.path) && - removeSemanticDiagnosticsOfFilesReferencingPath(state, exportedFromPath as Path, seenFileAndExportsOfFile) - ); - } - - /** - * removes the semantic diagnostics of files referencing referencedPath and - * returns true if there are no more semantic diagnostics from old state - */ - function removeSemanticDiagnosticsOfFilesReferencingPath(state: BuilderProgramState, referencedPath: Path, seenFileAndExportsOfFile: Map) { - return forEachEntry(state.referencedMap!, (referencesInFile, filePath) => - referencesInFile.has(referencedPath) && removeSemanticDiagnosticsOfFileAndExportsOfFile(state, filePath as Path, seenFileAndExportsOfFile) - ); - } - - /** - * Removes semantic diagnostics of file and anything that exports this file - */ - function removeSemanticDiagnosticsOfFileAndExportsOfFile(state: BuilderProgramState, filePath: Path, seenFileAndExportsOfFile: Map): boolean { - if (!addToSeen(seenFileAndExportsOfFile, filePath)) { - return false; - } - - if (removeSemanticDiagnosticsOf(state, filePath)) { - // If there are no more diagnostics from old cache, done - return true; - } - - Debug.assert(!!state.currentAffectedFilesExportedModulesMap); - // Go through exported modules from cache first - // If exported modules has path, all files referencing file exported from are affected - if (forEachEntry(state.currentAffectedFilesExportedModulesMap!, (exportedModules, exportedFromPath) => - exportedModules && - exportedModules.has(filePath) && - removeSemanticDiagnosticsOfFileAndExportsOfFile(state, exportedFromPath as Path, seenFileAndExportsOfFile) - )) { - return true; - } - - // If exported from path is not from cache and exported modules has path, all files referencing file exported from are affected - if (forEachEntry(state.exportedModulesMap!, (exportedModules, exportedFromPath) => - !state.currentAffectedFilesExportedModulesMap!.has(exportedFromPath) && // If we already iterated this through cache, ignore it - exportedModules.has(filePath) && - removeSemanticDiagnosticsOfFileAndExportsOfFile(state, exportedFromPath as Path, seenFileAndExportsOfFile) - )) { - return true; - } - - // Remove diagnostics of files that import this file (without going to exports of referencing files) - return !!forEachEntry(state.referencedMap!, (referencesInFile, referencingFilePath) => - referencesInFile.has(filePath) && - !seenFileAndExportsOfFile.has(referencingFilePath) && // Not already removed diagnostic file - removeSemanticDiagnosticsOf(state, referencingFilePath as Path) // Dont add to seen since this is not yet done with the export removal - ); + forEachReferencingModulesOfExportOfAffectedFile(state, affectedFile, removeSemanticDiagnosticsOf); } /** @@ -522,6 +447,114 @@ namespace ts { return !state.semanticDiagnosticsFromOldState.size; } + /** + * Add files, that are referencing modules that export entities from affected file as pending emit since dts may change + * Similar to cleanSemanticDiagnosticsOfAffectedFile + */ + function handleDtsMayChangeOfAffectedFile(state: BuilderProgramState, affectedFile: SourceFile) { + // If not dts emit, nothing more to do + if (!getEmitDeclarations(state.compilerOptions)) { + return; + } + + // If affected files is everything except default librarry, then nothing more to do + if (state.allFilesExcludingDefaultLibraryFile === state.affectedFiles) { + return; + } + + // If there was change in signature (dts output) for the changed file, + // then only we need to handle pending file emit + if (!state.exportedModulesMap || state.affectedFiles!.length === 1 || !state.changedFilesSet.has(affectedFile.path)) { + return; + } + + forEachReferencingModulesOfExportOfAffectedFile(state, affectedFile, (state, filePath) => { + addToAffectedFilesPendingEmit(state, [filePath]); + return false; + }); + } + + /** + * Iterate on referencing modules that export entities from affected file + */ + function forEachReferencingModulesOfExportOfAffectedFile(state: BuilderProgramState, affectedFile: SourceFile, fn: (state: BuilderProgramState, filePath: Path) => boolean) { + // If there was change in signature (dts output) for the changed file, + // then only we need to handle pending file emit + if (!state.exportedModulesMap || state.affectedFiles!.length === 1 || !state.changedFilesSet.has(affectedFile.path)) { + return; + } + + Debug.assert(!!state.currentAffectedFilesExportedModulesMap); + const seenFileAndExportsOfFile = createMap(); + // Go through exported modules from cache first + // If exported modules has path, all files referencing file exported from are affected + if (forEachEntry(state.currentAffectedFilesExportedModulesMap!, (exportedModules, exportedFromPath) => + exportedModules && + exportedModules.has(affectedFile.path) && + forEachFilesReferencingPath(state, exportedFromPath as Path, seenFileAndExportsOfFile, fn) + )) { + return; + } + + // If exported from path is not from cache and exported modules has path, all files referencing file exported from are affected + forEachEntry(state.exportedModulesMap, (exportedModules, exportedFromPath) => + !state.currentAffectedFilesExportedModulesMap!.has(exportedFromPath) && // If we already iterated this through cache, ignore it + exportedModules.has(affectedFile.path) && + forEachFilesReferencingPath(state, exportedFromPath as Path, seenFileAndExportsOfFile, fn) + ); + } + + /** + * Iterate on files referencing referencedPath + */ + function forEachFilesReferencingPath(state: BuilderProgramState, referencedPath: Path, seenFileAndExportsOfFile: Map, fn: (state: BuilderProgramState, filePath: Path) => boolean) { + return forEachEntry(state.referencedMap!, (referencesInFile, filePath) => + referencesInFile.has(referencedPath) && forEachFileAndExportsOfFile(state, filePath as Path, seenFileAndExportsOfFile, fn) + ); + } + + /** + * fn on file and iterate on anything that exports this file + */ + function forEachFileAndExportsOfFile(state: BuilderProgramState, filePath: Path, seenFileAndExportsOfFile: Map, fn: (state: BuilderProgramState, filePath: Path) => boolean): boolean { + if (!addToSeen(seenFileAndExportsOfFile, filePath)) { + return false; + } + + if (fn(state, filePath)) { + // If there are no more diagnostics from old cache, done + return true; + } + + Debug.assert(!!state.currentAffectedFilesExportedModulesMap); + // Go through exported modules from cache first + // If exported modules has path, all files referencing file exported from are affected + if (forEachEntry(state.currentAffectedFilesExportedModulesMap!, (exportedModules, exportedFromPath) => + exportedModules && + exportedModules.has(filePath) && + forEachFileAndExportsOfFile(state, exportedFromPath as Path, seenFileAndExportsOfFile, fn) + )) { + return true; + } + + // If exported from path is not from cache and exported modules has path, all files referencing file exported from are affected + if (forEachEntry(state.exportedModulesMap!, (exportedModules, exportedFromPath) => + !state.currentAffectedFilesExportedModulesMap!.has(exportedFromPath) && // If we already iterated this through cache, ignore it + exportedModules.has(filePath) && + forEachFileAndExportsOfFile(state, exportedFromPath as Path, seenFileAndExportsOfFile, fn) + )) { + return true; + } + + // Remove diagnostics of files that import this file (without going to exports of referencing files) + return !!forEachEntry(state.referencedMap!, (referencesInFile, referencingFilePath) => + referencesInFile.has(filePath) && + !seenFileAndExportsOfFile.has(referencingFilePath) && // Not already removed diagnostic file + fn(state, referencingFilePath as Path) // Dont add to seen since this is not yet done with the export removal + ); + } + + /** * This is called after completing operation on the next affected file. * The operations here are postponed to ensure that cancellation during the iteration is handled correctly @@ -929,14 +962,7 @@ namespace ts { // In case of emit builder, cache the files to be emitted if (affectedFilesPendingEmit) { - state.affectedFilesPendingEmit = concatenate(state.affectedFilesPendingEmit, affectedFilesPendingEmit); - // affectedFilesPendingEmitIndex === undefined - // - means the emit state.affectedFilesPendingEmit was undefined before adding current affected files - // so start from 0 as array would be affectedFilesPendingEmit - // else, continue to iterate from existing index, the current set is appended to existing files - if (state.affectedFilesPendingEmitIndex === undefined) { - state.affectedFilesPendingEmitIndex = 0; - } + addToAffectedFilesPendingEmit(state, affectedFilesPendingEmit); } let diagnostics: Diagnostic[] | undefined; @@ -947,6 +973,17 @@ namespace ts { } } + function addToAffectedFilesPendingEmit(state: BuilderProgramState, affectedFilesPendingEmit: readonly Path[]) { + state.affectedFilesPendingEmit = concatenate(state.affectedFilesPendingEmit, affectedFilesPendingEmit); + // affectedFilesPendingEmitIndex === undefined + // - means the emit state.affectedFilesPendingEmit was undefined before adding current affected files + // so start from 0 as array would be affectedFilesPendingEmit + // else, continue to iterate from existing index, the current set is appended to existing files + if (state.affectedFilesPendingEmitIndex === undefined) { + state.affectedFilesPendingEmitIndex = 0; + } + } + function getMapOfReferencedSet(mapLike: MapLike> | undefined): ReadonlyMap | undefined { if (!mapLike) return undefined; const map = createMap(); diff --git a/tests/baselines/reference/tsbuild/inferredTypeFromTransitiveModule/incremental-declaration-changes/inferred-type-from-transitive-module.js b/tests/baselines/reference/tsbuild/inferredTypeFromTransitiveModule/incremental-declaration-changes/inferred-type-from-transitive-module.js index ea6e9ff01c6..0dca3e32654 100644 --- a/tests/baselines/reference/tsbuild/inferredTypeFromTransitiveModule/incremental-declaration-changes/inferred-type-from-transitive-module.js +++ b/tests/baselines/reference/tsbuild/inferredTypeFromTransitiveModule/incremental-declaration-changes/inferred-type-from-transitive-module.js @@ -21,6 +21,11 @@ exports.default = foo()(function foobar() { }); +//// [/src/obj/index.d.ts] +import { LazyAction } from './bundling'; +export declare const lazyBar: LazyAction<() => void, typeof import("./lazyIndex")>; + + //// [/src/obj/tsconfig.tsbuildinfo] { "program": {