• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1namespace ts {
2
3    const enum ChangedPart {
4        references = 1 << 0,
5        importsAndExports = 1 << 1,
6        program = 1 << 2
7    }
8
9    const newLine = "\r\n";
10
11    interface SourceFileWithText extends SourceFile {
12        sourceText?: SourceText;
13    }
14
15    export interface NamedSourceText {
16        name: string;
17        text: SourceText;
18    }
19
20    export interface ProgramWithSourceTexts extends Program {
21        sourceTexts?: readonly NamedSourceText[];
22        host: TestCompilerHost;
23    }
24
25    interface TestCompilerHost extends CompilerHost {
26        getTrace(): string[];
27    }
28
29    export class SourceText implements IScriptSnapshot {
30        private fullText: string | undefined;
31
32        constructor(private references: string,
33            private importsAndExports: string,
34            private program: string,
35            private changedPart: ChangedPart = 0,
36            private version = 0) {
37        }
38
39        static New(references: string, importsAndExports: string, program: string): SourceText {
40            Debug.assert(references !== undefined);
41            Debug.assert(importsAndExports !== undefined);
42            Debug.assert(program !== undefined);
43            return new SourceText(references + newLine, importsAndExports + newLine, program || "");
44        }
45
46        public getVersion(): number {
47            return this.version;
48        }
49
50        public updateReferences(newReferences: string): SourceText {
51            Debug.assert(newReferences !== undefined);
52            return new SourceText(newReferences + newLine, this.importsAndExports, this.program, this.changedPart | ChangedPart.references, this.version + 1);
53        }
54        public updateImportsAndExports(newImportsAndExports: string): SourceText {
55            Debug.assert(newImportsAndExports !== undefined);
56            return new SourceText(this.references, newImportsAndExports + newLine, this.program, this.changedPart | ChangedPart.importsAndExports, this.version + 1);
57        }
58        public updateProgram(newProgram: string): SourceText {
59            Debug.assert(newProgram !== undefined);
60            return new SourceText(this.references, this.importsAndExports, newProgram, this.changedPart | ChangedPart.program, this.version + 1);
61        }
62
63        public getFullText() {
64            return this.fullText || (this.fullText = this.references + this.importsAndExports + this.program);
65        }
66
67        public getText(start: number, end: number): string {
68            return this.getFullText().substring(start, end);
69        }
70
71        getLength(): number {
72            return this.getFullText().length;
73        }
74
75        getChangeRange(oldSnapshot: IScriptSnapshot): TextChangeRange {
76            const oldText = oldSnapshot as SourceText;
77            let oldSpan: TextSpan;
78            let newLength: number;
79            switch (oldText.changedPart ^ this.changedPart) {
80                case ChangedPart.references:
81                    oldSpan = createTextSpan(0, oldText.references.length);
82                    newLength = this.references.length;
83                    break;
84                case ChangedPart.importsAndExports:
85                    oldSpan = createTextSpan(oldText.references.length, oldText.importsAndExports.length);
86                    newLength = this.importsAndExports.length;
87                    break;
88                case ChangedPart.program:
89                    oldSpan = createTextSpan(oldText.references.length + oldText.importsAndExports.length, oldText.program.length);
90                    newLength = this.program.length;
91                    break;
92                default:
93                    return Debug.fail("Unexpected change");
94            }
95
96            return createTextChangeRange(oldSpan, newLength);
97        }
98    }
99
100    function createSourceFileWithText(fileName: string, sourceText: SourceText, target: ScriptTarget) {
101        const file = createSourceFile(fileName, sourceText.getFullText(), target) as SourceFileWithText;
102        file.sourceText = sourceText;
103        file.version = "" + sourceText.getVersion();
104        return file;
105    }
106
107    export function createTestCompilerHost(texts: readonly NamedSourceText[], target: ScriptTarget, oldProgram?: ProgramWithSourceTexts, useGetSourceFileByPath?: boolean) {
108        const files = arrayToMap(texts, t => t.name, t => {
109            if (oldProgram) {
110                let oldFile = oldProgram.getSourceFile(t.name) as SourceFileWithText;
111                if (oldFile && oldFile.redirectInfo) {
112                    oldFile = oldFile.redirectInfo.unredirected;
113                }
114                if (oldFile && oldFile.sourceText!.getVersion() === t.text.getVersion()) {
115                    return oldFile;
116                }
117            }
118            return createSourceFileWithText(t.name, t.text, target);
119        });
120        const useCaseSensitiveFileNames = sys && sys.useCaseSensitiveFileNames;
121        const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames);
122        const trace: string[] = [];
123        const result: TestCompilerHost = {
124            trace: s => trace.push(s),
125            getTrace: () => trace,
126            getSourceFile: fileName => files.get(fileName),
127            getDefaultLibFileName: () => "lib.d.ts",
128            writeFile: notImplemented,
129            getCurrentDirectory: () => "",
130            getDirectories: () => [],
131            getCanonicalFileName,
132            useCaseSensitiveFileNames: () => useCaseSensitiveFileNames,
133            getNewLine: () => sys ? sys.newLine : newLine,
134            fileExists: fileName => files.has(fileName),
135            readFile: fileName => {
136                const file = files.get(fileName);
137                return file && file.text;
138            },
139        };
140        if (useGetSourceFileByPath) {
141            const filesByPath = mapEntries(files, (fileName, file) => [toPath(fileName, "", getCanonicalFileName), file]);
142            result.getSourceFileByPath = (_fileName, path) => filesByPath.get(path);
143        }
144        return result;
145    }
146
147    export function newProgram(texts: NamedSourceText[], rootNames: string[], options: CompilerOptions, useGetSourceFileByPath?: boolean): ProgramWithSourceTexts {
148        const host = createTestCompilerHost(texts, options.target!, /*oldProgram*/ undefined, useGetSourceFileByPath);
149        const program = createProgram(rootNames, options, host) as ProgramWithSourceTexts;
150        program.sourceTexts = texts;
151        program.host = host;
152        return program;
153    }
154
155    export function updateProgram(oldProgram: ProgramWithSourceTexts, rootNames: readonly string[], options: CompilerOptions, updater: (files: NamedSourceText[]) => void, newTexts?: NamedSourceText[], useGetSourceFileByPath?: boolean) {
156        if (!newTexts) {
157            newTexts = oldProgram.sourceTexts!.slice(0);
158        }
159        updater(newTexts);
160        const host = createTestCompilerHost(newTexts, options.target!, oldProgram, useGetSourceFileByPath);
161        const program = createProgram(rootNames, options, host, oldProgram) as ProgramWithSourceTexts;
162        program.sourceTexts = newTexts;
163        program.host = host;
164        return program;
165    }
166
167    export function updateProgramText(files: readonly NamedSourceText[], fileName: string, newProgramText: string) {
168        const file = find(files, f => f.name === fileName)!;
169        file.text = file.text.updateProgram(newProgramText);
170    }
171
172    function checkResolvedTypeDirective(actual: ResolvedTypeReferenceDirective, expected: ResolvedTypeReferenceDirective) {
173        assert.equal(actual.resolvedFileName, expected.resolvedFileName, `'resolvedFileName': expected '${actual.resolvedFileName}' to be equal to '${expected.resolvedFileName}'`);
174        assert.equal(actual.primary, expected.primary, `'primary': expected '${actual.primary}' to be equal to '${expected.primary}'`);
175        return true;
176    }
177
178    function checkCache<T>(caption: string, program: Program, fileName: string, expectedContent: ESMap<string, T> | undefined, getCache: (f: SourceFile) => ModeAwareCache<T> | undefined, entryChecker: (expected: T, original: T) => boolean): void {
179        const file = program.getSourceFile(fileName);
180        assert.isTrue(file !== undefined, `cannot find file ${fileName}`);
181        const cache = getCache(file!);
182        if (expectedContent === undefined) {
183            assert.isTrue(cache === undefined, `expected ${caption} to be undefined`);
184        }
185        else {
186            assert.isTrue(cache !== undefined, `expected ${caption} to be set`);
187            assert.isTrue(mapEqualToCache(expectedContent, cache!, entryChecker), `contents of ${caption} did not match the expected contents.`);
188        }
189    }
190
191    /** True if the maps have the same keys and values. */
192    function mapEqualToCache<T>(left: ESMap<string, T>, right: ModeAwareCache<T>, valuesAreEqual?: (left: T, right: T) => boolean): boolean {
193        if (left as any === right) return true; // given the type mismatch (the tests never pass a cache), this'll never be true
194        if (!left || !right) return false;
195        const someInLeftHasNoMatch = forEachEntry(left, (leftValue, leftKey) => {
196            if (!right.has(leftKey, /*mode*/ undefined)) return true;
197            const rightValue = right.get(leftKey, /*mode*/ undefined)!;
198            return !(valuesAreEqual ? valuesAreEqual(leftValue, rightValue) : leftValue === rightValue);
199        });
200        if (someInLeftHasNoMatch) return false;
201        let someInRightHasNoMatch = false;
202        right.forEach((_, rightKey) => someInRightHasNoMatch = someInRightHasNoMatch || !left.has(rightKey));
203        return !someInRightHasNoMatch;
204    }
205
206    function checkResolvedModulesCache(program: Program, fileName: string, expectedContent: ESMap<string, ResolvedModule | undefined> | undefined): void {
207        checkCache("resolved modules", program, fileName, expectedContent, f => f.resolvedModules, checkResolvedModule);
208    }
209
210    function checkResolvedTypeDirectivesCache(program: Program, fileName: string, expectedContent: ESMap<string, ResolvedTypeReferenceDirective> | undefined): void {
211        checkCache("resolved type directives", program, fileName, expectedContent, f => f.resolvedTypeReferenceDirectiveNames, checkResolvedTypeDirective);
212    }
213
214    describe("unittests:: Reuse program structure:: General", () => {
215        const target = ScriptTarget.Latest;
216        const files: NamedSourceText[] = [
217            {
218                name: "a.ts", text: SourceText.New(
219                    `
220/// <reference path='b.ts'/>
221/// <reference path='non-existing-file.ts'/>
222/// <reference types="typerefs" />
223`, "", `var x = 1`)
224            },
225            { name: "b.ts", text: SourceText.New(`/// <reference path='c.ts'/>`, "", `var y = 2`) },
226            { name: "c.ts", text: SourceText.New("", "", `var z = 1;`) },
227            { name: "types/typerefs/index.d.ts", text: SourceText.New("", "", `declare let z: number;`) },
228        ];
229
230        it("successful if change does not affect imports", () => {
231            const program1 = newProgram(files, ["a.ts"], { target });
232            const program2 = updateProgram(program1, ["a.ts"], { target }, files => {
233                files[0].text = files[0].text.updateProgram("var x = 100");
234            });
235            assert.equal(program2.structureIsReused, StructureIsReused.Completely);
236            const program1Diagnostics = program1.getSemanticDiagnostics(program1.getSourceFile("a.ts"));
237            const program2Diagnostics = program2.getSemanticDiagnostics(program2.getSourceFile("a.ts"));
238            assert.equal(program1Diagnostics.length, program2Diagnostics.length);
239        });
240
241        it("successful if change does not affect type reference directives", () => {
242            const program1 = newProgram(files, ["a.ts"], { target });
243            const program2 = updateProgram(program1, ["a.ts"], { target }, files => {
244                files[0].text = files[0].text.updateProgram("var x = 100");
245            });
246            assert.equal(program2.structureIsReused, StructureIsReused.Completely);
247            const program1Diagnostics = program1.getSemanticDiagnostics(program1.getSourceFile("a.ts"));
248            const program2Diagnostics = program2.getSemanticDiagnostics(program2.getSourceFile("a.ts"));
249            assert.equal(program1Diagnostics.length, program2Diagnostics.length);
250        });
251
252        it("successful if change affects a single module of a package", () => {
253            const files = [
254                { name: "/a.ts", text: SourceText.New("", "import {b} from 'b'", "var a = b;") },
255                { name: "/node_modules/b/index.d.ts", text: SourceText.New("", "export * from './internal';", "") },
256                { name: "/node_modules/b/internal.d.ts", text: SourceText.New("", "", "export const b = 1;") },
257                { name: "/node_modules/b/package.json", text: SourceText.New("", "", JSON.stringify({ name: "b", version: "1.2.3" })) },
258            ];
259
260            const options: CompilerOptions = { target, moduleResolution: ModuleResolutionKind.NodeJs };
261            const program1 = newProgram(files, ["/a.ts"], options);
262            const program2 = updateProgram(program1, ["/a.ts"], options, files => {
263                files[2].text = files[2].text.updateProgram("export const b = 2;");
264            });
265            assert.equal(program2.structureIsReused, StructureIsReused.Completely);
266            const program1Diagnostics = program1.getSemanticDiagnostics(program1.getSourceFile("a.ts"));
267            const program2Diagnostics = program2.getSemanticDiagnostics(program2.getSourceFile("a.ts"));
268            assert.equal(program1Diagnostics.length, program2Diagnostics.length);
269        });
270
271        it("fails if change affects tripleslash references", () => {
272            const program1 = newProgram(files, ["a.ts"], { target });
273            const program2 = updateProgram(program1, ["a.ts"], { target }, files => {
274                const newReferences = `/// <reference path='b.ts'/>
275                /// <reference path='c.ts'/>
276                `;
277                files[0].text = files[0].text.updateReferences(newReferences);
278            });
279            assert.equal(program2.structureIsReused, StructureIsReused.SafeModules);
280        });
281
282        it("fails if change affects type references", () => {
283            const program1 = newProgram(files, ["a.ts"], { types: ["a"] });
284            const program2 = updateProgram(program1, ["a.ts"], { types: ["b"] }, noop);
285            assert.equal(program2.structureIsReused, StructureIsReused.SafeModules);
286        });
287
288        it("succeeds if change doesn't affect type references", () => {
289            const program1 = newProgram(files, ["a.ts"], { types: ["a"] });
290            const program2 = updateProgram(program1, ["a.ts"], { types: ["a"] }, noop);
291            assert.equal(program2.structureIsReused, StructureIsReused.Completely);
292        });
293
294        it("fails if change affects imports", () => {
295            const program1 = newProgram(files, ["a.ts"], { target });
296            const program2 = updateProgram(program1, ["a.ts"], { target }, files => {
297                files[2].text = files[2].text.updateImportsAndExports("import x from 'b'");
298            });
299            assert.equal(program2.structureIsReused, StructureIsReused.SafeModules);
300        });
301
302        it("fails if change affects type directives", () => {
303            const program1 = newProgram(files, ["a.ts"], { target });
304            const program2 = updateProgram(program1, ["a.ts"], { target }, files => {
305                const newReferences = `
306/// <reference path='b.ts'/>
307/// <reference path='non-existing-file.ts'/>
308/// <reference types="typerefs1" />`;
309                files[0].text = files[0].text.updateReferences(newReferences);
310            });
311            assert.equal(program2.structureIsReused, StructureIsReused.SafeModules);
312        });
313
314        it("fails if module kind changes", () => {
315            const program1 = newProgram(files, ["a.ts"], { target, module: ModuleKind.CommonJS });
316            const program2 = updateProgram(program1, ["a.ts"], { target, module: ModuleKind.AMD }, noop);
317            assert.equal(program2.structureIsReused, StructureIsReused.Not);
318        });
319
320        it("succeeds if rootdir changes", () => {
321            const program1 = newProgram(files, ["a.ts"], { target, module: ModuleKind.CommonJS, rootDir: "/a/b" });
322            const program2 = updateProgram(program1, ["a.ts"], { target, module: ModuleKind.CommonJS, rootDir: "/a/c" }, noop);
323            assert.equal(program2.structureIsReused, StructureIsReused.Completely);
324        });
325
326        it("fails if config path changes", () => {
327            const program1 = newProgram(files, ["a.ts"], { target, module: ModuleKind.CommonJS, configFilePath: "/a/b/tsconfig.json" });
328            const program2 = updateProgram(program1, ["a.ts"], { target, module: ModuleKind.CommonJS, configFilePath: "/a/c/tsconfig.json" }, noop);
329            assert.equal(program2.structureIsReused, StructureIsReused.Not);
330        });
331
332        it("succeeds if missing files remain missing", () => {
333            const options: CompilerOptions = { target, noLib: true };
334
335            const program1 = newProgram(files, ["a.ts"], options);
336            assert.notDeepEqual(emptyArray, program1.getMissingFilePaths());
337
338            const program2 = updateProgram(program1, ["a.ts"], options, noop);
339            assert.deepEqual(program1.getMissingFilePaths(), program2.getMissingFilePaths());
340
341            assert.equal(program2.structureIsReused, StructureIsReused.Completely,);
342        });
343
344        it("fails if missing file is created", () => {
345            const options: CompilerOptions = { target, noLib: true };
346
347            const program1 = newProgram(files, ["a.ts"], options);
348            assert.notDeepEqual(emptyArray, program1.getMissingFilePaths());
349
350            const newTexts: NamedSourceText[] = files.concat([{ name: "non-existing-file.ts", text: SourceText.New("", "", `var x = 1`) }]);
351            const program2 = updateProgram(program1, ["a.ts"], options, noop, newTexts);
352            assert.lengthOf(program2.getMissingFilePaths(), 0);
353
354            assert.equal(program2.structureIsReused, StructureIsReused.Not);
355        });
356
357        it("resolution cache follows imports", () => {
358            (Error as any).stackTraceLimit = Infinity;
359
360            const files = [
361                { name: "a.ts", text: SourceText.New("", "import {_} from 'b'", "var x = 1") },
362                { name: "b.ts", text: SourceText.New("", "", "var y = 2") },
363            ];
364            const options: CompilerOptions = { target };
365
366            const program1 = newProgram(files, ["a.ts"], options);
367            checkResolvedModulesCache(program1, "a.ts", new Map(getEntries({ b: createResolvedModule("b.ts") })));
368            checkResolvedModulesCache(program1, "b.ts", /*expectedContent*/ undefined);
369
370            const program2 = updateProgram(program1, ["a.ts"], options, files => {
371                files[0].text = files[0].text.updateProgram("var x = 2");
372            });
373            assert.equal(program2.structureIsReused, StructureIsReused.Completely);
374
375            // content of resolution cache should not change
376            checkResolvedModulesCache(program1, "a.ts", new Map(getEntries({ b: createResolvedModule("b.ts") })));
377            checkResolvedModulesCache(program1, "b.ts", /*expectedContent*/ undefined);
378
379            // imports has changed - program is not reused
380            const program3 = updateProgram(program2, ["a.ts"], options, files => {
381                files[0].text = files[0].text.updateImportsAndExports("");
382            });
383            assert.equal(program3.structureIsReused, StructureIsReused.SafeModules);
384            checkResolvedModulesCache(program3, "a.ts", /*expectedContent*/ undefined);
385
386            const program4 = updateProgram(program3, ["a.ts"], options, files => {
387                const newImports = `import x from 'b'
388                import y from 'c'
389                `;
390                files[0].text = files[0].text.updateImportsAndExports(newImports);
391            });
392            assert.equal(program4.structureIsReused, StructureIsReused.SafeModules);
393            checkResolvedModulesCache(program4, "a.ts", new Map(getEntries({ b: createResolvedModule("b.ts"), c: undefined })));
394        });
395
396        it("set the resolvedImports after re-using an ambient external module declaration", () => {
397            const files = [
398                { name: "/a.ts", text: SourceText.New("", "", 'import * as a from "a";') },
399                { name: "/types/zzz/index.d.ts", text: SourceText.New("", "", 'declare module "a" { }') },
400            ];
401            const options: CompilerOptions = { target, typeRoots: ["/types"] };
402            const program1 = newProgram(files, ["/a.ts"], options);
403            const program2 = updateProgram(program1, ["/a.ts"], options, files => {
404                files[0].text = files[0].text.updateProgram('import * as aa from "a";');
405            });
406            assert.isDefined(program2.getSourceFile("/a.ts")!.resolvedModules!.get("a", /*mode*/ undefined), "'a' is not an unresolved module after re-use");
407        });
408
409        it("works with updated SourceFiles", () => {
410            // adapted repro from https://github.com/Microsoft/TypeScript/issues/26166
411            const files = [
412                { name: "/a.ts", text: SourceText.New("", "", 'import * as a from "a";a;') },
413                { name: "/types/zzz/index.d.ts", text: SourceText.New("", "", 'declare module "a" { }') },
414            ];
415            const host = createTestCompilerHost(files, target);
416            const options: CompilerOptions = { target, typeRoots: ["/types"] };
417            const program1 = createProgram(["/a.ts"], options, host);
418            let sourceFile = program1.getSourceFile("/a.ts")!;
419            assert.isDefined(sourceFile, "'/a.ts' is included in the program");
420            sourceFile = updateSourceFile(sourceFile, "'use strict';" + sourceFile.text, { newLength: "'use strict';".length, span: { start: 0, length: 0 } });
421            assert.strictEqual(sourceFile.statements[2].getSourceFile(), sourceFile, "parent pointers are updated");
422            const updateHost: TestCompilerHost = {
423                ...host,
424                getSourceFile(fileName) {
425                    return fileName === sourceFile.fileName ? sourceFile : program1.getSourceFile(fileName);
426                }
427            };
428            const program2 = createProgram(["/a.ts"], options, updateHost, program1);
429            assert.isDefined(program2.getSourceFile("/a.ts")!.resolvedModules!.get("a", /*mode*/ undefined), "'a' is not an unresolved module after re-use");
430            assert.strictEqual(sourceFile.statements[2].getSourceFile(), sourceFile, "parent pointers are not altered");
431        });
432
433        it("resolved type directives cache follows type directives", () => {
434            const files = [
435                { name: "/a.ts", text: SourceText.New("/// <reference types='typedefs'/>", "", "var x = $") },
436                { name: "/types/typedefs/index.d.ts", text: SourceText.New("", "", "declare var $: number") },
437            ];
438            const options: CompilerOptions = { target, typeRoots: ["/types"] };
439
440            const program1 = newProgram(files, ["/a.ts"], options);
441            checkResolvedTypeDirectivesCache(program1, "/a.ts", new Map(getEntries({ typedefs: { resolvedFileName: "/types/typedefs/index.d.ts", primary: true } })));
442            checkResolvedTypeDirectivesCache(program1, "/types/typedefs/index.d.ts", /*expectedContent*/ undefined);
443
444            const program2 = updateProgram(program1, ["/a.ts"], options, files => {
445                files[0].text = files[0].text.updateProgram("var x = 2");
446            });
447            assert.equal(program2.structureIsReused, StructureIsReused.Completely);
448
449            // content of resolution cache should not change
450            checkResolvedTypeDirectivesCache(program1, "/a.ts", new Map(getEntries({ typedefs: { resolvedFileName: "/types/typedefs/index.d.ts", primary: true } })));
451            checkResolvedTypeDirectivesCache(program1, "/types/typedefs/index.d.ts", /*expectedContent*/ undefined);
452
453            // type reference directives has changed - program is not reused
454            const program3 = updateProgram(program2, ["/a.ts"], options, files => {
455                files[0].text = files[0].text.updateReferences("");
456            });
457
458            assert.equal(program3.structureIsReused, StructureIsReused.SafeModules);
459            checkResolvedTypeDirectivesCache(program3, "/a.ts", /*expectedContent*/ undefined);
460
461            const program4 = updateProgram(program3, ["/a.ts"], options, files => {
462                const newReferences = `/// <reference types="typedefs"/>
463                /// <reference types="typedefs2"/>
464                `;
465                files[0].text = files[0].text.updateReferences(newReferences);
466            });
467            assert.equal(program4.structureIsReused, StructureIsReused.SafeModules);
468            checkResolvedTypeDirectivesCache(program1, "/a.ts", new Map(getEntries({ typedefs: { resolvedFileName: "/types/typedefs/index.d.ts", primary: true } })));
469        });
470
471        it("fetches imports after npm install", () => {
472            const file1Ts = { name: "file1.ts", text: SourceText.New("", `import * as a from "a";`, "const myX: number = a.x;") };
473            const file2Ts = { name: "file2.ts", text: SourceText.New("", "", "") };
474            const indexDTS = { name: "node_modules/a/index.d.ts", text: SourceText.New("", "export declare let x: number;", "") };
475            const options: CompilerOptions = { target: ScriptTarget.ES2015, traceResolution: true, moduleResolution: ModuleResolutionKind.NodeJs };
476            const rootFiles = [file1Ts, file2Ts];
477            const filesAfterNpmInstall = [file1Ts, file2Ts, indexDTS];
478
479            const initialProgram = newProgram(rootFiles, rootFiles.map(f => f.name), options);
480            {
481                assert.deepEqual(initialProgram.host.getTrace(),
482                    [
483                        "======== Resolving module 'a' from 'file1.ts'. ========",
484                        "Explicitly specified module resolution kind: 'NodeJs'.",
485                        "Loading module 'a' from 'node_modules' folder, target file type 'TypeScript'.",
486                        "File 'node_modules/a/package.json' does not exist.",
487                        "File 'node_modules/a.ts' does not exist.",
488                        "File 'node_modules/a.tsx' does not exist.",
489                        "File 'node_modules/a.d.ts' does not exist.",
490                        "File 'node_modules/a.ets' does not exist.",
491                        "File 'node_modules/a.d.ets' does not exist.",
492                        "File 'node_modules/a/index.ts' does not exist.",
493                        "File 'node_modules/a/index.tsx' does not exist.",
494                        "File 'node_modules/a/index.d.ts' does not exist.",
495                        "File 'node_modules/a/index.ets' does not exist.",
496                        "File 'node_modules/a/index.d.ets' does not exist.",
497                        "File 'node_modules/@types/a/package.json' does not exist.",
498                        "File 'node_modules/@types/a.d.ts' does not exist.",
499                        "File 'node_modules/@types/a/index.d.ts' does not exist.",
500                        "Loading module 'a' from 'node_modules' folder, target file type 'JavaScript'.",
501                        "File 'node_modules/a/package.json' does not exist according to earlier cached lookups.",
502                        "File 'node_modules/a.js' does not exist.",
503                        "File 'node_modules/a.jsx' does not exist.",
504                        "File 'node_modules/a/index.js' does not exist.",
505                        "File 'node_modules/a/index.jsx' does not exist.",
506                        "======== Module name 'a' was not resolved. ========"
507                    ],
508                    "initialProgram: execute module resolution normally.");
509
510                const initialProgramDiagnostics = initialProgram.getSemanticDiagnostics(initialProgram.getSourceFile("file1.ts"));
511                assert.lengthOf(initialProgramDiagnostics, 1, `initialProgram: import should fail.`);
512            }
513
514            const afterNpmInstallProgram = updateProgram(initialProgram, rootFiles.map(f => f.name), options, f => {
515                f[1].text = f[1].text.updateReferences(`/// <reference no-default-lib="true"/>`);
516            }, filesAfterNpmInstall);
517            {
518                assert.deepEqual(afterNpmInstallProgram.host.getTrace(),
519                    [
520                        "======== Resolving module 'a' from 'file1.ts'. ========",
521                        "Explicitly specified module resolution kind: 'NodeJs'.",
522                        "Loading module 'a' from 'node_modules' folder, target file type 'TypeScript'.",
523                        "File 'node_modules/a/package.json' does not exist.",
524                        "File 'node_modules/a.ts' does not exist.",
525                        "File 'node_modules/a.tsx' does not exist.",
526                        "File 'node_modules/a.d.ts' does not exist.",
527                        "File 'node_modules/a.ets' does not exist.",
528                        "File 'node_modules/a.d.ets' does not exist.",
529                        "File 'node_modules/a/index.ts' does not exist.",
530                        "File 'node_modules/a/index.tsx' does not exist.",
531                        "File 'node_modules/a/index.d.ts' exist - use it as a name resolution result.",
532                        "======== Module name 'a' was successfully resolved to 'node_modules/a/index.d.ts'. ========"
533                    ],
534                    "afterNpmInstallProgram: execute module resolution normally.");
535
536                const afterNpmInstallProgramDiagnostics = afterNpmInstallProgram.getSemanticDiagnostics(afterNpmInstallProgram.getSourceFile("file1.ts"));
537                assert.lengthOf(afterNpmInstallProgramDiagnostics, 0, `afterNpmInstallProgram: program is well-formed with import.`);
538            }
539        });
540
541        it("can reuse ambient module declarations from non-modified files", () => {
542            const files = [
543                { name: "/a/b/app.ts", text: SourceText.New("", "import * as fs from 'fs'", "") },
544                { name: "/a/b/node.d.ts", text: SourceText.New("", "", "declare module 'fs' {}") }
545            ];
546            const options = { target: ScriptTarget.ES2015, traceResolution: true };
547            const program = newProgram(files, files.map(f => f.name), options);
548            assert.deepEqual(program.host.getTrace(),
549                [
550                    "======== Resolving module 'fs' from '/a/b/app.ts'. ========",
551                    "Module resolution kind is not specified, using 'Classic'.",
552                    "File '/a/b/fs.ts' does not exist.",
553                    "File '/a/b/fs.tsx' does not exist.",
554                    "File '/a/b/fs.d.ts' does not exist.",
555                    "File '/a/b/fs.ets' does not exist.",
556                    "File '/a/b/fs.d.ets' does not exist.",
557                    "File '/a/fs.ts' does not exist.",
558                    "File '/a/fs.tsx' does not exist.",
559                    "File '/a/fs.d.ts' does not exist.",
560                    "File '/a/fs.ets' does not exist.",
561                    "File '/a/fs.d.ets' does not exist.",
562                    "File '/fs.ts' does not exist.",
563                    "File '/fs.tsx' does not exist.",
564                    "File '/fs.d.ts' does not exist.",
565                    "File '/fs.ets' does not exist.",
566                    "File '/fs.d.ets' does not exist.",
567                    "File '/a/b/node_modules/@types/fs/package.json' does not exist.",
568                    "File '/a/b/node_modules/@types/fs.d.ts' does not exist.",
569                    "File '/a/b/node_modules/@types/fs/index.d.ts' does not exist.",
570                    "File '/a/node_modules/@types/fs/package.json' does not exist.",
571                    "File '/a/node_modules/@types/fs.d.ts' does not exist.",
572                    "File '/a/node_modules/@types/fs/index.d.ts' does not exist.",
573                    "File '/node_modules/@types/fs/package.json' does not exist.",
574                    "File '/node_modules/@types/fs.d.ts' does not exist.",
575                    "File '/node_modules/@types/fs/index.d.ts' does not exist.",
576                    "File '/a/b/fs.js' does not exist.",
577                    "File '/a/b/fs.jsx' does not exist.",
578                    "File '/a/fs.js' does not exist.",
579                    "File '/a/fs.jsx' does not exist.",
580                    "File '/fs.js' does not exist.",
581                    "File '/fs.jsx' does not exist.",
582                    "======== Module name 'fs' was not resolved. ========",
583                ], "should look for 'fs'");
584
585            const program2 = updateProgram(program, program.getRootFileNames(), options, f => {
586                f[0].text = f[0].text.updateProgram("var x = 1;");
587            });
588            assert.deepEqual(program2.host.getTrace(), [
589                "Module 'fs' was resolved as ambient module declared in '/a/b/node.d.ts' since this file was not modified."
590            ], "should reuse 'fs' since node.d.ts was not changed");
591
592            const program3 = updateProgram(program2, program2.getRootFileNames(), options, f => {
593                f[0].text = f[0].text.updateProgram("var y = 1;");
594                f[1].text = f[1].text.updateProgram("declare var process: any");
595            });
596            assert.deepEqual(program3.host.getTrace(),
597                [
598                    "======== Resolving module 'fs' from '/a/b/app.ts'. ========",
599                    "Module resolution kind is not specified, using 'Classic'.",
600                    "File '/a/b/fs.ts' does not exist.",
601                    "File '/a/b/fs.tsx' does not exist.",
602                    "File '/a/b/fs.d.ts' does not exist.",
603                    "File '/a/b/fs.ets' does not exist.",
604                    "File '/a/b/fs.d.ets' does not exist.",
605                    "File '/a/fs.ts' does not exist.",
606                    "File '/a/fs.tsx' does not exist.",
607                    "File '/a/fs.d.ts' does not exist.",
608                    "File '/a/fs.ets' does not exist.",
609                    "File '/a/fs.d.ets' does not exist.",
610                    "File '/fs.ts' does not exist.",
611                    "File '/fs.tsx' does not exist.",
612                    "File '/fs.d.ts' does not exist.",
613                    "File '/fs.ets' does not exist.",
614                    "File '/fs.d.ets' does not exist.",
615                    "File '/a/b/node_modules/@types/fs/package.json' does not exist.",
616                    "File '/a/b/node_modules/@types/fs.d.ts' does not exist.",
617                    "File '/a/b/node_modules/@types/fs/index.d.ts' does not exist.",
618                    "File '/a/node_modules/@types/fs/package.json' does not exist.",
619                    "File '/a/node_modules/@types/fs.d.ts' does not exist.",
620                    "File '/a/node_modules/@types/fs/index.d.ts' does not exist.",
621                    "File '/node_modules/@types/fs/package.json' does not exist.",
622                    "File '/node_modules/@types/fs.d.ts' does not exist.",
623                    "File '/node_modules/@types/fs/index.d.ts' does not exist.",
624                    "File '/a/b/fs.js' does not exist.",
625                    "File '/a/b/fs.jsx' does not exist.",
626                    "File '/a/fs.js' does not exist.",
627                    "File '/a/fs.jsx' does not exist.",
628                    "File '/fs.js' does not exist.",
629                    "File '/fs.jsx' does not exist.",
630                    "======== Module name 'fs' was not resolved. ========",
631                ], "should look for 'fs' again since node.d.ts was changed");
632        });
633
634        it("can reuse module resolutions from non-modified files", () => {
635            const files = [
636                { name: "a1.ts", text: SourceText.New("", "", "let x = 1;") },
637                { name: "a2.ts", text: SourceText.New("", "", "let x = 1;") },
638                { name: "b1.ts", text: SourceText.New("", "export class B { x: number; }", "") },
639                { name: "b2.ts", text: SourceText.New("", "export class B { x: number; }", "") },
640                { name: "node_modules/@types/typerefs1/index.d.ts", text: SourceText.New("", "", "declare let z: string;") },
641                { name: "node_modules/@types/typerefs2/index.d.ts", text: SourceText.New("", "", "declare let z: string;") },
642                {
643                    name: "f1.ts",
644                    text:
645                    SourceText.New(
646                        `/// <reference path="a1.ts"/>${newLine}/// <reference types="typerefs1"/>${newLine}/// <reference no-default-lib="true"/>`,
647                        `import { B } from './b1';${newLine}export let BB = B;`,
648                        "declare module './b1' { interface B { y: string; } }")
649                },
650                {
651                    name: "f2.ts",
652                    text: SourceText.New(
653                        `/// <reference path="a2.ts"/>${newLine}/// <reference types="typerefs2"/>`,
654                        `import { B } from './b2';${newLine}import { BB } from './f1';`,
655                        "(new BB).x; (new BB).y;")
656                },
657            ];
658
659            const options: CompilerOptions = { target: ScriptTarget.ES2015, traceResolution: true, moduleResolution: ModuleResolutionKind.Classic };
660            const program1 = newProgram(files, files.map(f => f.name), options);
661            let expectedErrors = 0;
662            {
663                assert.deepEqual(program1.host.getTrace(),
664                    [
665                        "======== Resolving type reference directive 'typerefs1', containing file 'f1.ts', root directory 'node_modules/@types'. ========",
666                        "Resolving with primary search path 'node_modules/@types'.",
667                        "File 'node_modules/@types/typerefs1/package.json' does not exist.",
668                        "File 'node_modules/@types/typerefs1/index.d.ts' exist - use it as a name resolution result.",
669                        "======== Type reference directive 'typerefs1' was successfully resolved to 'node_modules/@types/typerefs1/index.d.ts', primary: true. ========",
670                        "======== Resolving module './b1' from 'f1.ts'. ========",
671                        "Explicitly specified module resolution kind: 'Classic'.",
672                        "File 'b1.ts' exist - use it as a name resolution result.",
673                        "======== Module name './b1' was successfully resolved to 'b1.ts'. ========",
674                        "======== Resolving type reference directive 'typerefs2', containing file 'f2.ts', root directory 'node_modules/@types'. ========",
675                        "Resolving with primary search path 'node_modules/@types'.",
676                        "File 'node_modules/@types/typerefs2/package.json' does not exist.",
677                        "File 'node_modules/@types/typerefs2/index.d.ts' exist - use it as a name resolution result.",
678                        "======== Type reference directive 'typerefs2' was successfully resolved to 'node_modules/@types/typerefs2/index.d.ts', primary: true. ========",
679                        "======== Resolving module './b2' from 'f2.ts'. ========",
680                        "Explicitly specified module resolution kind: 'Classic'.",
681                        "File 'b2.ts' exist - use it as a name resolution result.",
682                        "======== Module name './b2' was successfully resolved to 'b2.ts'. ========",
683                        "======== Resolving module './f1' from 'f2.ts'. ========",
684                        "Explicitly specified module resolution kind: 'Classic'.",
685                        "File 'f1.ts' exist - use it as a name resolution result.",
686                        "======== Module name './f1' was successfully resolved to 'f1.ts'. ========"
687                    ],
688                    "program1: execute module resolution normally.");
689
690                const program1Diagnostics = program1.getSemanticDiagnostics(program1.getSourceFile("f2.ts"));
691                assert.lengthOf(program1Diagnostics, expectedErrors, `initial program should be well-formed`);
692            }
693            const indexOfF1 = 6;
694            const program2 = updateProgram(program1, program1.getRootFileNames(), options, f => {
695                const newSourceText = f[indexOfF1].text.updateReferences(`/// <reference path="a1.ts"/>${newLine}/// <reference types="typerefs1"/>`);
696                f[indexOfF1] = { name: "f1.ts", text: newSourceText };
697            });
698
699            {
700                const program2Diagnostics = program2.getSemanticDiagnostics(program2.getSourceFile("f2.ts"));
701                assert.lengthOf(program2Diagnostics, expectedErrors, `removing no-default-lib shouldn't affect any types used.`);
702
703                assert.deepEqual(program2.host.getTrace(), [
704                    "======== Resolving type reference directive 'typerefs1', containing file 'f1.ts', root directory 'node_modules/@types'. ========",
705                    "Resolving with primary search path 'node_modules/@types'.",
706                    "File 'node_modules/@types/typerefs1/package.json' does not exist.",
707                    "File 'node_modules/@types/typerefs1/index.d.ts' exist - use it as a name resolution result.",
708                    "======== Type reference directive 'typerefs1' was successfully resolved to 'node_modules/@types/typerefs1/index.d.ts', primary: true. ========",
709                    "======== Resolving module './b1' from 'f1.ts'. ========",
710                    "Explicitly specified module resolution kind: 'Classic'.",
711                    "File 'b1.ts' exist - use it as a name resolution result.",
712                    "======== Module name './b1' was successfully resolved to 'b1.ts'. ========",
713                    "======== Resolving type reference directive 'typerefs2', containing file 'f2.ts', root directory 'node_modules/@types'. ========",
714                    "Resolving with primary search path 'node_modules/@types'.",
715                    "File 'node_modules/@types/typerefs2/package.json' does not exist.",
716                    "File 'node_modules/@types/typerefs2/index.d.ts' exist - use it as a name resolution result.",
717                    "======== Type reference directive 'typerefs2' was successfully resolved to 'node_modules/@types/typerefs2/index.d.ts', primary: true. ========",
718                    "Reusing resolution of module './b2' from 'f2.ts' of old program, it was successfully resolved to 'b2.ts'.",
719                    "Reusing resolution of module './f1' from 'f2.ts' of old program, it was successfully resolved to 'f1.ts'."
720                ], "program2: reuse module resolutions in f2 since it is unchanged");
721            }
722
723            const program3 = updateProgram(program2, program2.getRootFileNames(), options, f => {
724                const newSourceText = f[indexOfF1].text.updateReferences(`/// <reference path="a1.ts"/>`);
725                f[indexOfF1] = { name: "f1.ts", text: newSourceText };
726            });
727
728            {
729                const program3Diagnostics = program3.getSemanticDiagnostics(program3.getSourceFile("f2.ts"));
730                assert.lengthOf(program3Diagnostics, expectedErrors, `typerefs2 was unused, so diagnostics should be unaffected.`);
731
732                assert.deepEqual(program3.host.getTrace(), [
733                    "======== Resolving module './b1' from 'f1.ts'. ========",
734                    "Explicitly specified module resolution kind: 'Classic'.",
735                    "File 'b1.ts' exist - use it as a name resolution result.",
736                    "======== Module name './b1' was successfully resolved to 'b1.ts'. ========",
737                    "======== Resolving type reference directive 'typerefs2', containing file 'f2.ts', root directory 'node_modules/@types'. ========",
738                    "Resolving with primary search path 'node_modules/@types'.",
739                    "File 'node_modules/@types/typerefs2/package.json' does not exist.",
740                    "File 'node_modules/@types/typerefs2/index.d.ts' exist - use it as a name resolution result.",
741                    "======== Type reference directive 'typerefs2' was successfully resolved to 'node_modules/@types/typerefs2/index.d.ts', primary: true. ========",
742                    "Reusing resolution of module './b2' from 'f2.ts' of old program, it was successfully resolved to 'b2.ts'.",
743                    "Reusing resolution of module './f1' from 'f2.ts' of old program, it was successfully resolved to 'f1.ts'."
744                ], "program3: reuse module resolutions in f2 since it is unchanged");
745            }
746
747
748            const program4 = updateProgram(program3, program3.getRootFileNames(), options, f => {
749                const newSourceText = f[indexOfF1].text.updateReferences("");
750                f[indexOfF1] = { name: "f1.ts", text: newSourceText };
751            });
752
753            {
754                const program4Diagnostics = program4.getSemanticDiagnostics(program4.getSourceFile("f2.ts"));
755                assert.lengthOf(program4Diagnostics, expectedErrors, `a1.ts was unused, so diagnostics should be unaffected.`);
756
757                assert.deepEqual(program4.host.getTrace(), [
758                    "======== Resolving module './b1' from 'f1.ts'. ========",
759                    "Explicitly specified module resolution kind: 'Classic'.",
760                    "File 'b1.ts' exist - use it as a name resolution result.",
761                    "======== Module name './b1' was successfully resolved to 'b1.ts'. ========",
762                    "======== Resolving type reference directive 'typerefs2', containing file 'f2.ts', root directory 'node_modules/@types'. ========",
763                    "Resolving with primary search path 'node_modules/@types'.",
764                    "File 'node_modules/@types/typerefs2/package.json' does not exist.",
765                    "File 'node_modules/@types/typerefs2/index.d.ts' exist - use it as a name resolution result.",
766                    "======== Type reference directive 'typerefs2' was successfully resolved to 'node_modules/@types/typerefs2/index.d.ts', primary: true. ========",
767                    "Reusing resolution of module './b2' from 'f2.ts' of old program, it was successfully resolved to 'b2.ts'.",
768                    "Reusing resolution of module './f1' from 'f2.ts' of old program, it was successfully resolved to 'f1.ts'.",
769                ], "program_4: reuse module resolutions in f2 since it is unchanged");
770            }
771
772            const program5 = updateProgram(program4, program4.getRootFileNames(), options, f => {
773                const newSourceText = f[indexOfF1].text.updateImportsAndExports(`import { B } from './b1';`);
774                f[indexOfF1] = { name: "f1.ts", text: newSourceText };
775            });
776
777            {
778                const program5Diagnostics = program5.getSemanticDiagnostics(program5.getSourceFile("f2.ts"));
779                assert.lengthOf(program5Diagnostics, ++expectedErrors, `import of BB in f1 fails. BB is of type any. Add one error`);
780
781                assert.deepEqual(program5.host.getTrace(), [
782                    "======== Resolving module './b1' from 'f1.ts'. ========",
783                    "Explicitly specified module resolution kind: 'Classic'.",
784                    "File 'b1.ts' exist - use it as a name resolution result.",
785                    "======== Module name './b1' was successfully resolved to 'b1.ts'. ========"
786                ], "program_5: exports do not affect program structure, so f2's resolutions are silently reused.");
787            }
788
789            const program6 = updateProgram(program5, program5.getRootFileNames(), options, f => {
790                const newSourceText = f[indexOfF1].text.updateProgram("");
791                f[indexOfF1] = { name: "f1.ts", text: newSourceText };
792            });
793
794            {
795                const program6Diagnostics = program6.getSemanticDiagnostics(program6.getSourceFile("f2.ts"));
796                assert.lengthOf(program6Diagnostics, expectedErrors, `import of BB in f1 fails.`);
797
798                assert.deepEqual(program6.host.getTrace(), [
799                    "======== Resolving module './b1' from 'f1.ts'. ========",
800                    "Explicitly specified module resolution kind: 'Classic'.",
801                    "File 'b1.ts' exist - use it as a name resolution result.",
802                    "======== Module name './b1' was successfully resolved to 'b1.ts'. ========",
803                    "======== Resolving type reference directive 'typerefs2', containing file 'f2.ts', root directory 'node_modules/@types'. ========",
804                    "Resolving with primary search path 'node_modules/@types'.",
805                    "File 'node_modules/@types/typerefs2/package.json' does not exist.",
806                    "File 'node_modules/@types/typerefs2/index.d.ts' exist - use it as a name resolution result.",
807                    "======== Type reference directive 'typerefs2' was successfully resolved to 'node_modules/@types/typerefs2/index.d.ts', primary: true. ========",
808                    "Reusing resolution of module './b2' from 'f2.ts' of old program, it was successfully resolved to 'b2.ts'.",
809                    "Reusing resolution of module './f1' from 'f2.ts' of old program, it was successfully resolved to 'f1.ts'.",
810                ], "program_6: reuse module resolutions in f2 since it is unchanged");
811            }
812
813            const program7 = updateProgram(program6, program6.getRootFileNames(), options, f => {
814                const newSourceText = f[indexOfF1].text.updateImportsAndExports("");
815                f[indexOfF1] = { name: "f1.ts", text: newSourceText };
816            });
817
818            {
819                const program7Diagnostics = program7.getSemanticDiagnostics(program7.getSourceFile("f2.ts"));
820                assert.lengthOf(program7Diagnostics, expectedErrors, `removing import is noop with respect to program, so no change in diagnostics.`);
821
822                assert.deepEqual(program7.host.getTrace(), [
823                    "======== Resolving type reference directive 'typerefs2', containing file 'f2.ts', root directory 'node_modules/@types'. ========",
824                    "Resolving with primary search path 'node_modules/@types'.",
825                    "File 'node_modules/@types/typerefs2/package.json' does not exist.",
826                    "File 'node_modules/@types/typerefs2/index.d.ts' exist - use it as a name resolution result.",
827                    "======== Type reference directive 'typerefs2' was successfully resolved to 'node_modules/@types/typerefs2/index.d.ts', primary: true. ========",
828                    "Reusing resolution of module './b2' from 'f2.ts' of old program, it was successfully resolved to 'b2.ts'.",
829                    "Reusing resolution of module './f1' from 'f2.ts' of old program, it was successfully resolved to 'f1.ts'.",
830                ], "program_7 should reuse module resolutions in f2 since it is unchanged");
831            }
832        });
833
834        describe("redirects", () => {
835            const axIndex = "/node_modules/a/node_modules/x/index.d.ts";
836            const axPackage = "/node_modules/a/node_modules/x/package.json";
837            const bxIndex = "/node_modules/b/node_modules/x/index.d.ts";
838            const bxPackage = "/node_modules/b/node_modules/x/package.json";
839            const root = "/a.ts";
840            const compilerOptions = { target, moduleResolution: ModuleResolutionKind.NodeJs };
841
842            function createRedirectProgram(useGetSourceFileByPath: boolean, options?: { bText: string, bVersion: string }): ProgramWithSourceTexts {
843                const files: NamedSourceText[] = [
844                    {
845                        name: "/node_modules/a/index.d.ts",
846                        text: SourceText.New("", 'import X from "x";', "export function a(x: X): void;"),
847                    },
848                    {
849                        name: axIndex,
850                        text: SourceText.New("", "", "export default class X { private x: number; }"),
851                    },
852                    {
853                        name: axPackage,
854                        text: SourceText.New("", "", JSON.stringify({ name: "x", version: "1.2.3" })),
855                    },
856                    {
857                        name: "/node_modules/b/index.d.ts",
858                        text: SourceText.New("", 'import X from "x";', "export const b: X;"),
859                    },
860                    {
861                        name: bxIndex,
862                        text: SourceText.New("", "", options ? options.bText : "export default class X { private x: number; }"),
863                    },
864                    {
865                        name: bxPackage,
866                        text: SourceText.New("", "", JSON.stringify({ name: "x", version: options ? options.bVersion : "1.2.3" })),
867                    },
868                    {
869                        name: root,
870                        text: SourceText.New("", 'import { a } from "a"; import { b } from "b";', "a(b)"),
871                    },
872                ];
873
874                return newProgram(files, [root], compilerOptions, useGetSourceFileByPath);
875            }
876
877            function updateRedirectProgram(program: ProgramWithSourceTexts, updater: (files: NamedSourceText[]) => void, useGetSourceFileByPath: boolean): ProgramWithSourceTexts {
878                return updateProgram(program, [root], compilerOptions, updater, /*newTexts*/ undefined, useGetSourceFileByPath);
879            }
880
881            function verifyRedirects(useGetSourceFileByPath: boolean) {
882                it("No changes -> redirect not broken", () => {
883                    const program1 = createRedirectProgram(useGetSourceFileByPath);
884
885                    const program2 = updateRedirectProgram(program1, files => {
886                        updateProgramText(files, root, "const x = 1;");
887                    }, useGetSourceFileByPath);
888                    assert.equal(program2.structureIsReused, StructureIsReused.Completely);
889                    assert.lengthOf(program2.getSemanticDiagnostics(), 0);
890                });
891
892                it("Target changes -> redirect broken", () => {
893                    const program1 = createRedirectProgram(useGetSourceFileByPath);
894                    assert.lengthOf(program1.getSemanticDiagnostics(), 0);
895
896                    const program2 = updateRedirectProgram(program1, files => {
897                        updateProgramText(files, axIndex, "export default class X { private x: number; private y: number; }");
898                        updateProgramText(files, axPackage, JSON.stringify('{ name: "x", version: "1.2.4" }'));
899                    }, useGetSourceFileByPath);
900                    assert.equal(program2.structureIsReused, StructureIsReused.Not);
901                    assert.lengthOf(program2.getSemanticDiagnostics(), 1);
902                });
903
904                it("Underlying changes -> redirect broken", () => {
905                    const program1 = createRedirectProgram(useGetSourceFileByPath);
906
907                    const program2 = updateRedirectProgram(program1, files => {
908                        updateProgramText(files, bxIndex, "export default class X { private x: number; private y: number; }");
909                        updateProgramText(files, bxPackage, JSON.stringify({ name: "x", version: "1.2.4" }));
910                    }, useGetSourceFileByPath);
911                    assert.equal(program2.structureIsReused, StructureIsReused.Not);
912                    assert.lengthOf(program2.getSemanticDiagnostics(), 1);
913                });
914
915                it("Previously duplicate packages -> program structure not reused", () => {
916                    const program1 = createRedirectProgram(useGetSourceFileByPath, { bVersion: "1.2.4", bText: "export = class X { private x: number; }" });
917
918                    const program2 = updateRedirectProgram(program1, files => {
919                        updateProgramText(files, bxIndex, "export default class X { private x: number; }");
920                        updateProgramText(files, bxPackage, JSON.stringify({ name: "x", version: "1.2.3" }));
921                    }, useGetSourceFileByPath);
922                    assert.equal(program2.structureIsReused, StructureIsReused.Not);
923                    assert.deepEqual(program2.getSemanticDiagnostics(), []);
924                });
925            }
926
927            describe("when host implements getSourceFile", () => {
928                verifyRedirects(/*useGetSourceFileByPath*/ false);
929            });
930            describe("when host implements getSourceFileByPath", () => {
931                verifyRedirects(/*useGetSourceFileByPath*/ true);
932            });
933        });
934    });
935
936    describe("unittests:: Reuse program structure:: host is optional", () => {
937        it("should work if host is not provided", () => {
938            createProgram([], {});
939        });
940    });
941
942    type File = TestFSWithWatch.File;
943    import createTestSystem = TestFSWithWatch.createWatchedSystem;
944    import libFile = TestFSWithWatch.libFile;
945
946    describe("unittests:: Reuse program structure:: isProgramUptoDate", () => {
947        function getWhetherProgramIsUptoDate(
948            program: Program,
949            newRootFileNames: string[],
950            newOptions: CompilerOptions
951        ) {
952            return isProgramUptoDate(
953                program, newRootFileNames, newOptions,
954                path => program.getSourceFileByPath(path)!.version, /*fileExists*/ returnFalse,
955                /*hasInvalidatedResolutions*/ returnFalse,
956                /*hasChangedAutomaticTypeDirectiveNames*/ undefined,
957                /*getParsedCommandLine*/ returnUndefined,
958                /*projectReferences*/ undefined
959            );
960        }
961
962        function duplicate(options: CompilerOptions): CompilerOptions;
963        function duplicate(fileNames: string[]): string[];
964        function duplicate(filesOrOptions: CompilerOptions | string[]) {
965            return JSON.parse(JSON.stringify(filesOrOptions));
966        }
967
968        describe("should return true when there is no change in compiler options and", () => {
969            function verifyProgramIsUptoDate(
970                program: Program,
971                newRootFileNames: string[],
972                newOptions: CompilerOptions
973            ) {
974                const actual = getWhetherProgramIsUptoDate(program, newRootFileNames, newOptions);
975                assert.isTrue(actual);
976            }
977
978            function verifyProgramWithoutConfigFile(system: System, rootFiles: string[], options: CompilerOptions) {
979                const program = createWatchProgram(createWatchCompilerHostOfFilesAndCompilerOptions({
980                    rootFiles,
981                    options,
982                    watchOptions: undefined,
983                    system
984                })).getCurrentProgram().getProgram();
985                verifyProgramIsUptoDate(program, duplicate(rootFiles), duplicate(options));
986            }
987
988            function verifyProgramWithConfigFile(system: System, configFileName: string) {
989                const program = createWatchProgram(createWatchCompilerHostOfConfigFile({
990                    configFileName,
991                    system
992                })).getCurrentProgram().getProgram();
993                const { fileNames, options } = parseConfigFileWithSystem(configFileName, {}, /*extendedConfigCache*/ undefined, /*watchOptionsToExtend*/ undefined, system, notImplemented)!; // TODO: GH#18217
994                verifyProgramIsUptoDate(program, fileNames, options);
995            }
996
997            function verifyProgram(files: File[], rootFiles: string[], options: CompilerOptions, configFile: string) {
998                const system = createTestSystem(files);
999                verifyProgramWithoutConfigFile(system, rootFiles, options);
1000                verifyProgramWithConfigFile(system, configFile);
1001            }
1002
1003            it("has empty options", () => {
1004                const file1: File = {
1005                    path: "/a/b/file1.ts",
1006                    content: "let x = 1"
1007                };
1008                const file2: File = {
1009                    path: "/a/b/file2.ts",
1010                    content: "let y = 1"
1011                };
1012                const configFile: File = {
1013                    path: "/a/b/tsconfig.json",
1014                    content: "{}"
1015                };
1016                verifyProgram([file1, file2, libFile, configFile], [file1.path, file2.path], {}, configFile.path);
1017            });
1018
1019            it("has lib specified in the options", () => {
1020                const compilerOptions: CompilerOptions = { lib: ["es5", "es2015.promise"] };
1021                const app: File = {
1022                    path: "/src/app.ts",
1023                    content: "var x: Promise<string>;"
1024                };
1025                const configFile: File = {
1026                    path: "/src/tsconfig.json",
1027                    content: JSON.stringify({ compilerOptions })
1028                };
1029                const es5Lib: File = {
1030                    path: "/compiler/lib.es5.d.ts",
1031                    content: "declare const eval: any"
1032                };
1033                const es2015Promise: File = {
1034                    path: "/compiler/lib.es2015.promise.d.ts",
1035                    content: "declare class Promise<T> {}"
1036                };
1037
1038                verifyProgram([app, configFile, es5Lib, es2015Promise], [app.path], compilerOptions, configFile.path);
1039            });
1040
1041            it("has paths specified in the options", () => {
1042                const compilerOptions: CompilerOptions = {
1043                    baseUrl: ".",
1044                    paths: {
1045                        "*": [
1046                            "packages/mail/data/*",
1047                            "packages/styles/*",
1048                            "*"
1049                        ]
1050                    }
1051                };
1052                const app: File = {
1053                    path: "/src/packages/framework/app.ts",
1054                    content: 'import classc from "module1/lib/file1";\
1055                              import classD from "module3/file3";\
1056                              let x = new classc();\
1057                              let y = new classD();'
1058                };
1059                const module1: File = {
1060                    path: "/src/packages/mail/data/module1/lib/file1.ts",
1061                    content: 'import classc from "module2/file2";export default classc;',
1062                };
1063                const module2: File = {
1064                    path: "/src/packages/mail/data/module1/lib/module2/file2.ts",
1065                    content: 'class classc { method2() { return "hello"; } }\nexport default classc',
1066                };
1067                const module3: File = {
1068                    path: "/src/packages/styles/module3/file3.ts",
1069                    content: "class classD { method() { return 10; } }\nexport default classD;"
1070                };
1071                const configFile: File = {
1072                    path: "/src/tsconfig.json",
1073                    content: JSON.stringify({ compilerOptions })
1074                };
1075
1076                verifyProgram([app, module1, module2, module3, libFile, configFile], [app.path], compilerOptions, configFile.path);
1077            });
1078
1079            it("has include paths specified in tsconfig file", () => {
1080                const compilerOptions: CompilerOptions = {
1081                    baseUrl: ".",
1082                    paths: {
1083                        "*": [
1084                            "packages/mail/data/*",
1085                            "packages/styles/*",
1086                            "*"
1087                        ]
1088                    }
1089                };
1090                const app: File = {
1091                    path: "/src/packages/framework/app.ts",
1092                    content: 'import classc from "module1/lib/file1";\
1093                              import classD from "module3/file3";\
1094                              let x = new classc();\
1095                              let y = new classD();'
1096                };
1097                const module1: File = {
1098                    path: "/src/packages/mail/data/module1/lib/file1.ts",
1099                    content: 'import classc from "module2/file2";export default classc;',
1100                };
1101                const module2: File = {
1102                    path: "/src/packages/mail/data/module1/lib/module2/file2.ts",
1103                    content: 'class classc { method2() { return "hello"; } }\nexport default classc',
1104                };
1105                const module3: File = {
1106                    path: "/src/packages/styles/module3/file3.ts",
1107                    content: "class classD { method() { return 10; } }\nexport default classD;"
1108                };
1109                const configFile: File = {
1110                    path: "/src/tsconfig.json",
1111                    content: JSON.stringify({ compilerOptions, include: ["packages/**/*.ts"] })
1112                };
1113                verifyProgramWithConfigFile(createTestSystem([app, module1, module2, module3, libFile, configFile]), configFile.path);
1114            });
1115            it("has the same root file names", () => {
1116                const module1: File = {
1117                    path: "/src/packages/mail/data/module1/lib/file1.ts",
1118                    content: 'import classc from "module2/file2";export default classc;',
1119                };
1120                const module2: File = {
1121                    path: "/src/packages/mail/data/module1/lib/module2/file2.ts",
1122                    content: 'class classc { method2() { return "hello"; } }\nexport default classc',
1123                };
1124                const module3: File = {
1125                    path: "/src/packages/styles/module3/file3.ts",
1126                    content: "class classD { method() { return 10; } }\nexport default classD;"
1127                };
1128                const rootFiles = [module1.path, module2.path, module3.path];
1129                const system = createTestSystem([module1, module2, module3]);
1130                const options = {};
1131                const program = createWatchProgram(createWatchCompilerHostOfFilesAndCompilerOptions({
1132                    rootFiles,
1133                    options,
1134                    watchOptions: undefined,
1135                    system
1136                })).getCurrentProgram().getProgram();
1137                verifyProgramIsUptoDate(program, duplicate(rootFiles), duplicate(options));
1138            });
1139
1140        });
1141        describe("should return false when there is no change in compiler options but", () => {
1142            function verifyProgramIsNotUptoDate(
1143                program: Program,
1144                newRootFileNames: string[],
1145                newOptions: CompilerOptions
1146            ) {
1147                const actual = getWhetherProgramIsUptoDate(program, newRootFileNames, newOptions);
1148                assert.isFalse(actual);
1149            }
1150            it("has more root file names", () => {
1151                const module1: File = {
1152                    path: "/src/packages/mail/data/module1/lib/file1.ts",
1153                    content: 'import classc from "module2/file2";export default classc;',
1154                };
1155                const module2: File = {
1156                    path: "/src/packages/mail/data/module1/lib/module2/file2.ts",
1157                    content: 'class classc { method2() { return "hello"; } }\nexport default classc',
1158                };
1159                const module3: File = {
1160                    path: "/src/packages/styles/module3/file3.ts",
1161                    content: "class classD { method() { return 10; } }\nexport default classD;"
1162                };
1163                const rootFiles = [module1.path, module2.path];
1164                const newRootFiles = [module1.path, module2.path, module3.path];
1165                const system = createTestSystem([module1, module2, module3]);
1166                const options = {};
1167                const program = createWatchProgram(createWatchCompilerHostOfFilesAndCompilerOptions({
1168                    rootFiles,
1169                    options,
1170                    watchOptions: undefined,
1171                    system
1172                })).getCurrentProgram().getProgram();
1173                verifyProgramIsNotUptoDate(program, duplicate(newRootFiles), duplicate(options));
1174            });
1175            it("has one root file replaced by another", () => {
1176                const module1: File = {
1177                    path: "/src/packages/mail/data/module1/lib/file1.ts",
1178                    content: 'import classc from "module2/file2";export default classc;',
1179                };
1180                const module2: File = {
1181                    path: "/src/packages/mail/data/module1/lib/module2/file2.ts",
1182                    content: 'class classc { method2() { return "hello"; } }\nexport default classc',
1183                };
1184                const module3: File = {
1185                    path: "/src/packages/styles/module3/file3.ts",
1186                    content: "class classD { method() { return 10; } }\nexport default classD;"
1187                };
1188                const rootFiles = [module1.path, module2.path];
1189                const newRootFiles = [module2.path, module3.path];
1190                const system = createTestSystem([module1, module2, module3]);
1191                const options = {};
1192                const program = createWatchProgram(createWatchCompilerHostOfFilesAndCompilerOptions({
1193                    rootFiles,
1194                    options,
1195                    watchOptions: undefined,
1196                    system
1197                })).getCurrentProgram().getProgram();
1198                verifyProgramIsNotUptoDate(program, duplicate(newRootFiles), duplicate(options));
1199            });
1200        });
1201    });
1202}
1203