• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1namespace ts.tscWatch {
2    describe("unittests:: tsc-watch:: watchAPI:: tsc-watch with custom module resolution", () => {
3        const configFileJson: any = {
4            compilerOptions: { module: "commonjs", resolveJsonModule: true },
5            files: ["index.ts"]
6        };
7        const mainFile: File = {
8            path: `${projectRoot}/index.ts`,
9            content: "import settings from './settings.json';"
10        };
11        const config: File = {
12            path: `${projectRoot}/tsconfig.json`,
13            content: JSON.stringify(configFileJson)
14        };
15        const settingsJson: File = {
16            path: `${projectRoot}/settings.json`,
17            content: JSON.stringify({ content: "Print this" })
18        };
19
20        it("verify that module resolution with json extension works when returned without extension", () => {
21            const files = [libFile, mainFile, config, settingsJson];
22            const host = createWatchedSystem(files, { currentDirectory: projectRoot });
23            const compilerHost = createWatchCompilerHostOfConfigFile({
24                configFileName: config.path,
25                system: host
26            });
27            const parsedCommandResult = parseJsonConfigFileContent(configFileJson, host, config.path);
28            compilerHost.resolveModuleNames = (moduleNames, containingFile) => moduleNames.map(m => {
29                const result = resolveModuleName(m, containingFile, parsedCommandResult.options, compilerHost);
30                const resolvedModule = result.resolvedModule!;
31                return {
32                    resolvedFileName: resolvedModule.resolvedFileName,
33                    isExternalLibraryImport: resolvedModule.isExternalLibraryImport,
34                    originalFileName: resolvedModule.originalPath,
35                };
36            });
37            const watch = createWatchProgram(compilerHost);
38            const program = watch.getCurrentProgram().getProgram();
39            checkProgramActualFiles(program, [mainFile.path, libFile.path, settingsJson.path]);
40        });
41    });
42
43    describe("unittests:: tsc-watch:: watchAPI:: tsc-watch expose error count to watch status reporter", () => {
44        const configFileJson: any = {
45            compilerOptions: { module: "commonjs" },
46            files: ["index.ts"]
47        };
48        const config: File = {
49            path: `${projectRoot}/tsconfig.json`,
50            content: JSON.stringify(configFileJson)
51        };
52        const mainFile: File = {
53            path: `${projectRoot}/index.ts`,
54            content: "let compiler = new Compiler(); for (let i = 0; j < 5; i++) {}"
55        };
56
57        it("verify that the error count is correctly passed down to the watch status reporter", () => {
58            const files = [libFile, mainFile, config];
59            const host = createWatchedSystem(files, { currentDirectory: projectRoot });
60            let watchedErrorCount;
61            const reportWatchStatus: WatchStatusReporter = (_, __, ___, errorCount) => {
62                watchedErrorCount = errorCount;
63            };
64            const compilerHost = createWatchCompilerHostOfConfigFile({
65                configFileName: config.path,
66                system: host,
67                reportWatchStatus
68            });
69            createWatchProgram(compilerHost);
70            assert.equal(watchedErrorCount, 2, "The error count was expected to be 2 for the file change");
71        });
72    });
73
74    describe("unittests:: tsc-watch:: watchAPI:: when watchHost does not implement setTimeout or clearTimeout", () => {
75        it("verifies that getProgram gets updated program if new file is added to the program", () => {
76            const config: File = {
77                path: `${projectRoot}/tsconfig.json`,
78                content: "{}"
79            };
80            const mainFile: File = {
81                path: `${projectRoot}/main.ts`,
82                content: "const x = 10;"
83            };
84            const sys = createWatchedSystem([config, mainFile, libFile]);
85            const watchCompilerHost = createWatchCompilerHost(config.path, {}, sys);
86            watchCompilerHost.setTimeout = undefined;
87            watchCompilerHost.clearTimeout = undefined;
88            const watch = createWatchProgram(watchCompilerHost);
89            checkProgramActualFiles(watch.getProgram().getProgram(), [mainFile.path, libFile.path]);
90            // Write new file
91            const barPath = `${projectRoot}/bar.ts`;
92            sys.writeFile(barPath, "const y =10;");
93            checkProgramActualFiles(watch.getProgram().getProgram(), [mainFile.path, barPath, libFile.path]);
94        });
95    });
96
97    describe("unittests:: tsc-watch:: watchAPI:: when watchHost can add extraFileExtensions to process", () => {
98        it("verifies that extraFileExtensions are supported to get the program with other extensions", () => {
99            const config: File = {
100                path: `${projectRoot}/tsconfig.json`,
101                content: "{}"
102            };
103            const mainFile: File = {
104                path: `${projectRoot}/main.ts`,
105                content: "const x = 10;"
106            };
107            const otherFile: File = {
108                path: `${projectRoot}/other.vue`,
109                content: ""
110            };
111            const sys = createWatchedSystem([config, mainFile, otherFile, libFile]);
112            const watchCompilerHost = createWatchCompilerHost(
113                config.path,
114                { allowNonTsExtensions: true },
115                sys,
116                /*createProgram*/ undefined,
117                /*reportDiagnostics*/ undefined,
118                /*reportWatchStatus*/ undefined,
119                /*watchOptionsToExtend*/ undefined,
120                [{ extension: ".vue", isMixedContent: true, scriptKind: ScriptKind.Deferred }]
121            );
122            const watch = createWatchProgram(watchCompilerHost);
123            checkProgramActualFiles(watch.getProgram().getProgram(), [mainFile.path, otherFile.path, libFile.path]);
124
125            const other2 = `${projectRoot}/other2.vue`;
126            sys.writeFile(other2, otherFile.content);
127            checkSingleTimeoutQueueLengthAndRun(sys);
128            checkProgramActualFiles(watch.getProgram().getProgram(), [mainFile.path, otherFile.path, libFile.path, other2]);
129        });
130    });
131
132    describe("unittests:: tsc-watch:: watchAPI:: when watchHost uses createSemanticDiagnosticsBuilderProgram", () => {
133        function getWatch<T extends BuilderProgram>(config: File, optionsToExtend: CompilerOptions | undefined, sys: System, createProgram: CreateProgram<T>) {
134            const watchCompilerHost = createWatchCompilerHost(config.path, optionsToExtend, sys, createProgram);
135            return createWatchProgram(watchCompilerHost);
136        }
137
138        function setup<T extends BuilderProgram>(createProgram: CreateProgram<T>, configText: string) {
139            const config: File = {
140                path: `${projectRoot}/tsconfig.json`,
141                content: configText
142            };
143            const mainFile: File = {
144                path: `${projectRoot}/main.ts`,
145                content: "export const x = 10;"
146            };
147            const otherFile: File = {
148                path: `${projectRoot}/other.ts`,
149                content: "export const y = 10;"
150            };
151            const sys = createWatchedSystem([config, mainFile, otherFile, libFile]);
152            const watch = getWatch(config, { noEmit: true }, sys, createProgram);
153            return { sys, watch, mainFile, otherFile, config };
154        }
155
156        function verifyOutputs(sys: System, emitSys: System) {
157            for (const output of [`${projectRoot}/main.js`, `${projectRoot}/main.d.ts`, `${projectRoot}/other.js`, `${projectRoot}/other.d.ts`, `${projectRoot}/tsconfig.tsbuildinfo`]) {
158                assert.strictEqual(sys.readFile(output), emitSys.readFile(output), `Output file text for ${output}`);
159            }
160        }
161
162        function verifyBuilder<T extends BuilderProgram, U extends BuilderProgram>(config: File, sys: System, emitSys: System, createProgram: CreateProgram<T>, createEmitProgram: CreateProgram<U>, optionsToExtend?: CompilerOptions) {
163            const watch = getWatch(config, /*optionsToExtend*/ optionsToExtend, sys, createProgram);
164            const emitWatch = getWatch(config, /*optionsToExtend*/ optionsToExtend, emitSys, createEmitProgram);
165            verifyOutputs(sys, emitSys);
166            watch.close();
167            emitWatch.close();
168        }
169
170        it("verifies that noEmit is handled on createSemanticDiagnosticsBuilderProgram and typechecking happens only on affected files", () => {
171            const { sys, watch, mainFile, otherFile } = setup(createSemanticDiagnosticsBuilderProgram, "{}");
172            checkProgramActualFiles(watch.getProgram().getProgram(), [mainFile.path, otherFile.path, libFile.path]);
173            sys.appendFile(mainFile.path, "\n// SomeComment");
174            sys.runQueuedTimeoutCallbacks();
175            const program = watch.getProgram().getProgram();
176            assert.deepEqual(program.getCachedSemanticDiagnostics(program.getSourceFile(mainFile.path)), []);
177            // Should not retrieve diagnostics for other file thats not changed
178            assert.deepEqual(program.getCachedSemanticDiagnostics(program.getSourceFile(otherFile.path)), /*expected*/ undefined);
179        });
180
181        it("noEmit with composite writes the tsbuildinfo with pending affected files correctly", () => {
182            const configText = JSON.stringify({ compilerOptions: { composite: true } });
183            const { sys, watch, config, mainFile } = setup(createSemanticDiagnosticsBuilderProgram, configText);
184            const { sys: emitSys, watch: emitWatch } = setup(createEmitAndSemanticDiagnosticsBuilderProgram, configText);
185            verifyOutputs(sys, emitSys);
186
187            watch.close();
188            emitWatch.close();
189
190            // Emit on both sys should result in same output
191            verifyBuilder(config, sys, emitSys, createEmitAndSemanticDiagnosticsBuilderProgram, createEmitAndSemanticDiagnosticsBuilderProgram);
192
193            // Change file
194            sys.appendFile(mainFile.path, "\n// SomeComment");
195            emitSys.appendFile(mainFile.path, "\n// SomeComment");
196
197            // Verify noEmit results in same output
198            verifyBuilder(config, sys, emitSys, createSemanticDiagnosticsBuilderProgram, createEmitAndSemanticDiagnosticsBuilderProgram, { noEmit: true });
199
200            // Emit on both sys should result in same output
201            verifyBuilder(config, sys, emitSys, createEmitAndSemanticDiagnosticsBuilderProgram, createEmitAndSemanticDiagnosticsBuilderProgram);
202
203            // Change file
204            sys.appendFile(mainFile.path, "\n// SomeComment");
205            emitSys.appendFile(mainFile.path, "\n// SomeComment");
206
207            // Emit on both the builders should result in same files
208            verifyBuilder(config, sys, emitSys, createSemanticDiagnosticsBuilderProgram, createEmitAndSemanticDiagnosticsBuilderProgram);
209        });
210
211        it("noEmitOnError with composite writes the tsbuildinfo with pending affected files correctly", () => {
212            const config: File = {
213                path: `${projectRoot}/tsconfig.json`,
214                content: JSON.stringify({ compilerOptions: { composite: true } })
215            };
216            const mainFile: File = {
217                path: `${projectRoot}/main.ts`,
218                content: "export const x: string = 10;"
219            };
220            const otherFile: File = {
221                path: `${projectRoot}/other.ts`,
222                content: "export const y = 10;"
223            };
224            const sys = createWatchedSystem([config, mainFile, otherFile, libFile]);
225            const emitSys = createWatchedSystem([config, mainFile, otherFile, libFile]);
226
227            // Verify noEmit results in same output
228            verifyBuilder(config, sys, emitSys, createSemanticDiagnosticsBuilderProgram, createEmitAndSemanticDiagnosticsBuilderProgram, { noEmitOnError: true });
229
230            // Change file
231            sys.appendFile(mainFile.path, "\n// SomeComment");
232            emitSys.appendFile(mainFile.path, "\n// SomeComment");
233
234            // Verify noEmit results in same output
235            verifyBuilder(config, sys, emitSys, createSemanticDiagnosticsBuilderProgram, createEmitAndSemanticDiagnosticsBuilderProgram, { noEmitOnError: true });
236
237            // Fix error
238            const fixed = "export const x = 10;";
239            sys.appendFile(mainFile.path, fixed);
240            emitSys.appendFile(mainFile.path, fixed);
241
242            // Emit on both the builders should result in same files
243            verifyBuilder(config, sys, emitSys, createSemanticDiagnosticsBuilderProgram, createEmitAndSemanticDiagnosticsBuilderProgram, { noEmitOnError: true });
244        });
245    });
246}
247