diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index a1200b6a072..ee379787768 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -3199,9 +3199,12 @@ }, "Scoped package detected, looking in '{0}'": { "category": "Message", - "code": "6182" + "code": 6182 + }, + "Reusing module resolutions originating in '{0}' since this file was not modified.": { + "category": "Message", + "code": 6183 }, - "Variable '{0}' implicitly has an '{1}' type.": { "category": "Error", "code": 7005 diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 373ccb08d4d..2a9891e9d64 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -8,17 +8,17 @@ namespace ts { const enum StructuralChangesFromOldProgram { - None = 0, - NoOldProgram = 1, - ModuleResolutionOptions = 2, - RootNames = 3, - TypesOptions = 4, - SourceFileRemoved = 5, - HasNoDefaultLib = 6, - TripleSlashReferences = 7, - Imports = 8, - ModuleAugmentations = 9, - TripleSlashTypesReferences = 10, + None = 0, + NoOldProgram = 1 << 1, + ModuleResolutionOptions = 1 << 2, + RootNames = 1 << 3, + TypesOptions = 1 << 4, + SourceFileRemoved = 1 << 5, + HasNoDefaultLib = 1 << 6, + TripleSlashReferences = 1 << 7, + Imports = 1 << 8, + ModuleAugmentations = 1 << 9, + TripleSlashTypesReferences = 1 << 10, CannotReuseResolution = NoOldProgram | ModuleResolutionOptions | RootNames | TypesOptions | SourceFileRemoved } @@ -520,6 +520,9 @@ namespace ts { const oldSourceFile = oldProgramState.program && oldProgramState.program.getSourceFile(containingFile); if (oldSourceFile === file) { // `file` is unchanged from the old program, so we can reuse old module resolutions. + if (isTraceEnabled(options, host)) { + trace(host, Diagnostics.Reusing_module_resolutions_originating_in_0_since_this_file_was_not_modified, file.path); + } const oldSourceFileResult: ResolvedModuleFull[] = []; for (const moduleName of moduleNames) { const resolvedModule = oldSourceFile.resolvedModules.get(moduleName); @@ -710,10 +713,6 @@ namespace ts { // tentatively approve the file modifiedSourceFiles.push({ oldFile: oldSourceFile, newFile: newSourceFile }); } - else { - // file has no changes - use it as is - newSourceFile = oldSourceFile; - } // if file has passed all checks it should be safe to reuse it newSourceFiles.push(newSourceFile); diff --git a/src/harness/unittests/reuseProgramStructure.ts b/src/harness/unittests/reuseProgramStructure.ts index 43014f25afb..e53aa0b4524 100644 --- a/src/harness/unittests/reuseProgramStructure.ts +++ b/src/harness/unittests/reuseProgramStructure.ts @@ -140,9 +140,7 @@ namespace ts { useCaseSensitiveFileNames(): boolean { return sys && sys.useCaseSensitiveFileNames; }, - getNewLine(): string { - return sys ? sys.newLine : newLine; - }, + getNewLine, fileExists: fileName => files.has(fileName), readFile: fileName => { const file = files.get(fileName); @@ -217,7 +215,7 @@ namespace ts { describe("Reuse program structure", () => { const target = ScriptTarget.Latest; - const files = [ + const files: NamedSourceText[] = [ { name: "a.ts", text: SourceText.New( ` /// @@ -468,8 +466,73 @@ namespace ts { "File '/fs.jsx' does not exist.", "======== Module name 'fs' was not resolved. ========", ], "should look for 'fs' again since node.d.ts was changed"); - }); + + it("can reuse module resolutions from non-modified files", () => { + const files = [ + { name: "a1.ts", text: SourceText.New("", "", "let x = 1;") }, + { name: "a2.ts", text: SourceText.New("", "", "let x = 1;") }, + { name: "b1.ts", text: SourceText.New("", "export class B { x: number; }", "") }, + { name: "b2.ts", text: SourceText.New("", "export class B { x: number; }", "") }, + { name: "node_modules/@types/typerefs1/index.d.ts", text: SourceText.New("", "", "declare let z: string;") }, + { name: "node_modules/@types/typerefs2/index.d.ts", text: SourceText.New("", "", "declare let z: string;") }, + { + name: "f1.ts", + text: SourceText.New( + `/// ${newLine}/// ${newLine}/// `, + `import { B } from './b1';${newLine}export let BB = B;`, + "declare module './b1' { interface B { y: string; } }") + }, + { + name: "f2.ts", + text: SourceText.New( + `/// ${newLine}/// `, + `import { B } from './b2';${newLine}import { BB } from './f1';`, + "(new BB).x; (new BB).y;") + }, + ]; + + const options = { target: ScriptTarget.ES2015, traceResolution: true }; + const program_1 = newProgram(files, files.map(f => f.name), options); + assert.deepEqual(program_1.host.getTrace(), + [ + "======== Resolving type reference directive 'typerefs1', containing file 'f1.ts', root directory 'node_modules/@types'. ========", + "Resolving with primary search path 'node_modules/@types'.", + "File 'node_modules/@types/typerefs1/package.json' does not exist.", + "File 'node_modules/@types/typerefs1/index.d.ts' exist - use it as a name resolution result.", + "======== Type reference directive 'typerefs1' was successfully resolved to 'node_modules/@types/typerefs1/index.d.ts', primary: true. ========", + "======== Resolving module './b1' from 'f1.ts'. ========", + "Module resolution kind is not specified, using 'Classic'.", + "File 'b1.ts' exist - use it as a name resolution result.", + "======== Module name './b1' was successfully resolved to 'b1.ts'. ========", + "======== Resolving type reference directive 'typerefs2', containing file 'f2.ts', root directory 'node_modules/@types'. ========", + "Resolving with primary search path 'node_modules/@types'.", + "File 'node_modules/@types/typerefs2/package.json' does not exist.", + "File 'node_modules/@types/typerefs2/index.d.ts' exist - use it as a name resolution result.", + "======== Type reference directive 'typerefs2' was successfully resolved to 'node_modules/@types/typerefs2/index.d.ts', primary: true. ========", + "======== Resolving module './b2' from 'f2.ts'. ========", + "Module resolution kind is not specified, using 'Classic'.", + "File 'b2.ts' exist - use it as a name resolution result.", + "======== Module name './b2' was successfully resolved to 'b2.ts'. ========", + "======== Resolving module './f1' from 'f2.ts'. ========", + "Module resolution kind is not specified, using 'Classic'.", + "File 'f1.ts' exist - use it as a name resolution result.", + "======== Module name './f1' was successfully resolved to 'f1.ts'. ========" + ], + "First program should execute module reoslution normally."); + + // Create project. Edit file-exists to track disk accesses. Edit f1.ts, update project. + // check that updating project "inherits" the fileexists implementation. + const indexOfF1 = 6; + function updateProgram_1(files: NamedSourceText[]): void { + files[indexOfF1].text = SourceText.New("","",""); + } + + const program_2 = updateProgram(program_1, program_1.getRootFileNames(), options, updateProgram_1); + assert.deepEqual(program_2.host.getTrace(), [ + "Module 'fs' was resolved as ambient module declared in '/a/b/node.d.ts' since this file was not modified." + ], "should reuse module resolutions in f2 since it is unchanged"); + }); }); describe("host is optional", () => {