From 6df61272f3058db3c13f820009f8a2595d47a5a7 Mon Sep 17 00:00:00 2001 From: Klaus Meinhardt Date: Fri, 3 Aug 2018 17:54:15 +0200 Subject: [PATCH 1/3] createProgram: don't use TypeChecker Avoids using the TypeChecker when trying to reuse the Program structure. This allows SourceFiles contained in the old Program to be updated using ts.updateSourceFile Fixes: #26166 --- src/compiler/program.ts | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index c70477b4a17ce..14063029c5927 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -951,8 +951,11 @@ 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 && oldProgramState.program.getSourceFile(resolutionToFile.resolvedFileName); + const resolvedFile = resolutionToFile && oldProgramState.program.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. @@ -960,16 +963,11 @@ namespace ts { // because the normal module resolution algorithm will find this anyway. return false; } - const ambientModule = oldProgramState.program && oldProgramState.program.getTypeChecker().tryFindAmbientModuleWithoutAugmentations(moduleName); - if (!(ambientModule && ambientModule.declarations)) { - return false; - } // at least one of declarations should come from non-modified source file - const firstUnmodifiedFile = forEach(ambientModule.declarations, d => { - const f = getSourceFileOfNode(d); - return !contains(oldProgramState.modifiedFilePaths, f.path) && f; - }); + const firstUnmodifiedFile = oldProgramState.program.getSourceFiles().find( + f => !contains(oldProgramState.modifiedFilePaths, f.path) && contains(f.ambientModuleNames, moduleName) + ); if (!firstUnmodifiedFile) { return false; From a1978eb8a1e2d6697230299fb29d250582af124d Mon Sep 17 00:00:00 2001 From: Klaus Meinhardt Date: Fri, 3 Aug 2018 22:09:53 +0200 Subject: [PATCH 2/3] add test --- .../unittests/reuseProgramStructure.ts | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/testRunner/unittests/reuseProgramStructure.ts b/src/testRunner/unittests/reuseProgramStructure.ts index 44082f3a302bc..cf0881dfefa1d 100644 --- a/src/testRunner/unittests/reuseProgramStructure.ts +++ b/src/testRunner/unittests/reuseProgramStructure.ts @@ -399,6 +399,29 @@ namespace ts { assert.isDefined(program2.getSourceFile("/a.ts")!.resolvedModules!.get("a"), "'a' is not an unresolved module after re-use"); }); + it("works with updated SourceFiles", () => { + const files = [ + { name: "/a.ts", text: SourceText.New("", "", 'import * as a from "a";a;') }, + { name: "/types/zzz/index.d.ts", text: SourceText.New("", "", 'declare module "a" { }') }, + ]; + const host = createTestCompilerHost(files, target); + const options: CompilerOptions = { target, typeRoots: ["/types"] }; + const program1 = createProgram(["/a.ts"], options, host); + let sourceFile = program1.getSourceFile("/a.ts")!; + assert.isDefined(sourceFile, "'/a.ts' is included in the program"); + sourceFile = updateSourceFile(sourceFile, "'use strict';" + sourceFile.text, { newLength: "'use strict';".length, span: { start: 0, length: 0 } }); + assert.strictEqual(sourceFile.statements[2].getSourceFile(), sourceFile, "parent pointers are updated"); + const updateHost: TestCompilerHost = { + ...host, + getSourceFile(fileName) { + return fileName === sourceFile.fileName ? sourceFile : program1.getSourceFile(fileName); + } + }; + const program2 = createProgram(["/a.ts"], options, updateHost, program1); + assert.isDefined(program2.getSourceFile("/a.ts")!.resolvedModules!.get("a"), "'a' is not an unresolved module after re-use"); + assert.strictEqual(sourceFile.statements[2].getSourceFile(), sourceFile, "parent pointers are not altered"); + }); + it("resolved type directives cache follows type directives", () => { const files = [ { name: "/a.ts", text: SourceText.New("/// ", "", "var x = $") }, From 3b022a4e6633e79a1cf79e0b4bb13132864a9668 Mon Sep 17 00:00:00 2001 From: Klaus Meinhardt Date: Fri, 3 Aug 2018 23:19:04 +0200 Subject: [PATCH 3/3] add link to issue --- src/testRunner/unittests/reuseProgramStructure.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/testRunner/unittests/reuseProgramStructure.ts b/src/testRunner/unittests/reuseProgramStructure.ts index cf0881dfefa1d..23d658ad483ee 100644 --- a/src/testRunner/unittests/reuseProgramStructure.ts +++ b/src/testRunner/unittests/reuseProgramStructure.ts @@ -400,6 +400,7 @@ namespace ts { }); it("works with updated SourceFiles", () => { + // adapted repro from https://github.com/Microsoft/TypeScript/issues/26166 const files = [ { name: "/a.ts", text: SourceText.New("", "", 'import * as a from "a";a;') }, { name: "/types/zzz/index.d.ts", text: SourceText.New("", "", 'declare module "a" { }') },