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