diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 44eae36d65470..ed009f23ce3f9 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -574,7 +574,7 @@ namespace ts { let diagnosticsProducingTypeChecker: TypeChecker; let noDiagnosticsTypeChecker: TypeChecker; let classifiableNames: UnderscoreEscapedMap; - let modifiedFilePaths: Path[] | undefined; + const ambientModuleNameToUnmodifiedFileName = createMap(); const cachedSemanticDiagnosticsForFile: DiagnosticCache = {}; const cachedDeclarationDiagnosticsForFile: DiagnosticCache = {}; @@ -850,21 +850,14 @@ namespace ts { return classifiableNames; } - interface OldProgramState { - program: Program | undefined; - oldSourceFile: SourceFile | undefined; - /** The collection of paths modified *since* the old program. */ - modifiedFilePaths: Path[] | undefined; - } - - function resolveModuleNamesReusingOldState(moduleNames: string[], containingFile: string, file: SourceFile, oldProgramState: OldProgramState) { + function resolveModuleNamesReusingOldState(moduleNames: string[], containingFile: string, file: SourceFile) { if (structuralIsReused === StructureIsReused.Not && !file.ambientModuleNames.length) { // If the old program state does not permit reusing resolutions and `file` does not contain locally defined ambient modules, // the best we can do is fallback to the default logic. return resolveModuleNamesWorker(moduleNames, containingFile); } - const oldSourceFile = oldProgramState.program && oldProgramState.program.getSourceFile(containingFile); + const oldSourceFile = oldProgram && oldProgram.getSourceFile(containingFile); if (oldSourceFile !== file && file.resolvedModules) { // `file` was created for the new program. // @@ -928,7 +921,7 @@ namespace ts { } } else { - resolvesToAmbientModuleInNonModifiedFile = moduleNameResolvesToAmbientModuleInNonModifiedFile(moduleName, oldProgramState); + resolvesToAmbientModuleInNonModifiedFile = moduleNameResolvesToAmbientModuleInNonModifiedFile(moduleName); } if (resolvesToAmbientModuleInNonModifiedFile) { @@ -971,12 +964,9 @@ namespace ts { // If we change our policy of rechecking failed lookups on each program create, // we should adjust the value returned here. - function moduleNameResolvesToAmbientModuleInNonModifiedFile(moduleName: string, oldProgramState: OldProgramState): boolean { - if (!oldProgramState.program) { - return false; - } - const resolutionToFile = getResolvedModule(oldProgramState.oldSourceFile!, moduleName); // TODO: GH#18217 - const resolvedFile = resolutionToFile && oldProgramState.program.getSourceFile(resolutionToFile.resolvedFileName); + function moduleNameResolvesToAmbientModuleInNonModifiedFile(moduleName: string): boolean { + const resolutionToFile = getResolvedModule(oldSourceFile!, moduleName); + const resolvedFile = resolutionToFile && oldProgram!.getSourceFile(resolutionToFile.resolvedFileName); if (resolutionToFile && resolvedFile && !resolvedFile.externalModuleIndicator) { // In the old program, we resolved to an ambient module that was in the same // place as we expected to find an actual module file. @@ -986,16 +976,14 @@ namespace ts { } // at least one of declarations should come from non-modified source file - const firstUnmodifiedFile = oldProgramState.program.getSourceFiles().find( - f => !contains(oldProgramState.modifiedFilePaths, f.path) && contains(f.ambientModuleNames, moduleName) - ); + const unmodifiedFile = ambientModuleNameToUnmodifiedFileName.get(moduleName); - if (!firstUnmodifiedFile) { + if (!unmodifiedFile) { return false; } if (isTraceEnabled(options, host)) { - trace(host, Diagnostics.Module_0_was_resolved_as_ambient_module_declared_in_1_since_this_file_was_not_modified, moduleName, firstUnmodifiedFile.fileName); + trace(host, Diagnostics.Module_0_was_resolved_as_ambient_module_declared_in_1_since_this_file_was_not_modified, moduleName, unmodifiedFile); } return true; } @@ -1188,14 +1176,20 @@ namespace ts { return oldProgram.structureIsReused; } - modifiedFilePaths = modifiedSourceFiles.map(f => f.newFile.path); + const modifiedFiles = modifiedSourceFiles.map(f => f.oldFile); + for (const oldFile of oldSourceFiles) { + if (!contains(modifiedFiles, oldFile)) { + for (const moduleName of oldFile.ambientModuleNames) { + ambientModuleNameToUnmodifiedFileName.set(moduleName, oldFile.fileName); + } + } + } // try to verify results of module resolution for (const { oldFile: oldSourceFile, newFile: newSourceFile } of modifiedSourceFiles) { const newSourceFilePath = getNormalizedAbsolutePath(newSourceFile.originalFileName, currentDirectory); if (resolveModuleNamesWorker) { const moduleNames = getModuleNames(newSourceFile); - const oldProgramState: OldProgramState = { program: oldProgram, oldSourceFile, modifiedFilePaths }; - const resolutions = resolveModuleNamesReusingOldState(moduleNames, newSourceFilePath, newSourceFile, oldProgramState); + const resolutions = resolveModuleNamesReusingOldState(moduleNames, newSourceFilePath, newSourceFile); // ensure that module resolution results are still correct const resolutionsChanged = hasChangesInResolutions(moduleNames, resolutions, oldSourceFile.resolvedModules, moduleResolutionIsEqualTo); if (resolutionsChanged) { @@ -2308,8 +2302,7 @@ namespace ts { if (file.imports.length || file.moduleAugmentations.length) { // Because global augmentation doesn't have string literal name, we can check for global augmentation as such. const moduleNames = getModuleNames(file); - const oldProgramState: OldProgramState = { program: oldProgram, oldSourceFile: oldProgram && oldProgram.getSourceFile(file.fileName), modifiedFilePaths }; - const resolutions = resolveModuleNamesReusingOldState(moduleNames, getNormalizedAbsolutePath(file.originalFileName, currentDirectory), file, oldProgramState); + const resolutions = resolveModuleNamesReusingOldState(moduleNames, getNormalizedAbsolutePath(file.originalFileName, currentDirectory), file); Debug.assert(resolutions.length === moduleNames.length); for (let i = 0; i < moduleNames.length; i++) { const resolution = resolutions[i];