• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1namespace ts.tscWatch {
2    describe("unittests:: tsc-watch:: emit file --incremental", () => {
3        const project = "/users/username/projects/project";
4
5        const configFile: File = {
6            path: `${project}/tsconfig.json`,
7            content: JSON.stringify({ compilerOptions: { incremental: true } })
8        };
9
10        interface VerifyIncrementalWatchEmitInput {
11            subScenario: string;
12            files: () => readonly File[];
13            optionsToExtend?: readonly string[];
14            modifyFs?: (host: WatchedSystem) => void;
15        }
16        function verifyIncrementalWatchEmit(input: VerifyIncrementalWatchEmitInput) {
17            describe(input.subScenario, () => {
18                it("with tsc --w", () => {
19                    verifyIncrementalWatchEmitWorker(input, /*incremental*/ false);
20                });
21                it("with tsc", () => {
22                    verifyIncrementalWatchEmitWorker(input, /*incremental*/ true);
23                });
24            });
25        }
26
27        function verifyIncrementalWatchEmitWorker(
28            { subScenario, files, optionsToExtend, modifyFs }: VerifyIncrementalWatchEmitInput,
29            incremental: boolean
30        ) {
31            const { sys, baseline, oldSnap } = createBaseline(createWatchedSystem(files(), { currentDirectory: project }));
32            if (incremental) sys.exit = exitCode => sys.exitCode = exitCode;
33            const argsToPass = [incremental ? "-i" : "-w", ...(optionsToExtend || emptyArray)];
34            baseline.push(`${sys.getExecutingFilePath()} ${argsToPass.join(" ")}`);
35            const { cb, getPrograms } = commandLineCallbacks(sys);
36            build(oldSnap);
37
38            if (modifyFs) {
39                const oldSnap = applyChange(sys, baseline, modifyFs);
40                build(oldSnap);
41            }
42
43            Harness.Baseline.runBaseline(`${isBuild(argsToPass) ? "tsbuild/watchMode" : "tscWatch"}/incremental/${subScenario.split(" ").join("-")}-${incremental ? "incremental" : "watch"}.js`, baseline.join("\r\n"));
44
45            function build(oldSnap: SystemSnap) {
46                const closer = executeCommandLine(
47                    sys,
48                    cb,
49                    argsToPass,
50                );
51                watchBaseline({
52                    baseline,
53                    getPrograms,
54                    sys,
55                    oldSnap
56                });
57                if (closer) closer.close();
58            }
59        }
60
61        describe("non module compilation", () => {
62            const file1: File = {
63                path: `${project}/file1.ts`,
64                content: "const x = 10;"
65            };
66            const file2: File = {
67                path: `${project}/file2.ts`,
68                content: "const y = 20;"
69            };
70            describe("own file emit without errors", () => {
71                function verify(subScenario: string, optionsToExtend?: readonly string[]) {
72                    const modifiedFile2Content = file2.content.replace("y", "z").replace("20", "10");
73                    verifyIncrementalWatchEmit({
74                        files: () => [libFile, file1, file2, configFile],
75                        optionsToExtend,
76                        subScenario: `own file emit without errors/${subScenario}`,
77                        modifyFs: host => host.writeFile(file2.path, modifiedFile2Content),
78                    });
79                }
80                verify("without commandline options");
81                verify("with commandline parameters that are not relative", ["-p", "tsconfig.json"]);
82            });
83
84            verifyIncrementalWatchEmit({
85                files: () => [libFile, file1, configFile, {
86                    path: file2.path,
87                    content: `const y: string = 20;`
88                }],
89                subScenario: "own file emit with errors",
90                modifyFs: host => host.writeFile(file1.path, file1.content.replace("x", "z")),
91            });
92
93            verifyIncrementalWatchEmit({
94                files: () => [libFile, file1, file2, {
95                    path: configFile.path,
96                    content: JSON.stringify({ compilerOptions: { incremental: true, outFile: "out.js" } })
97                }],
98                subScenario: "with --out",
99            });
100        });
101
102        describe("module compilation", () => {
103            const file1: File = {
104                path: `${project}/file1.ts`,
105                content: "export const x = 10;"
106            };
107            const file2: File = {
108                path: `${project}/file2.ts`,
109                content: "export const y = 20;"
110            };
111            const config: File = {
112                path: configFile.path,
113                content: JSON.stringify({ compilerOptions: { incremental: true, module: "amd" } })
114            };
115
116            verifyIncrementalWatchEmit({
117                files: () => [libFile, file1, file2, config],
118                subScenario: "module compilation/own file emit without errors",
119                modifyFs: host => host.writeFile(file2.path, file2.content.replace("y", "z").replace("20", "10")),
120            });
121
122            describe("own file emit with errors", () => {
123                const fileModified: File = {
124                    path: file2.path,
125                    content: `export const y: string = 20;`
126                };
127
128                verifyIncrementalWatchEmit({
129                    files: () => [libFile, file1, fileModified, config],
130                    subScenario: "module compilation/own file emit with errors",
131                    modifyFs: host => host.writeFile(file1.path, file1.content.replace("x = 10", "z = 10")),
132                });
133
134                it("verify that state is read correctly", () => {
135                    const system = createWatchedSystem([libFile, file1, fileModified, config], { currentDirectory: project });
136                    const reportDiagnostic = createDiagnosticReporter(system);
137                    const parsedConfig = parseConfigFileWithSystem("tsconfig.json", {}, /*watchOptionsToExtend*/ undefined, system, reportDiagnostic)!;
138                    performIncrementalCompilation({
139                        rootNames: parsedConfig.fileNames,
140                        options: parsedConfig.options,
141                        projectReferences: parsedConfig.projectReferences,
142                        configFileParsingDiagnostics: getConfigFileParsingDiagnostics(parsedConfig),
143                        reportDiagnostic,
144                        system
145                    });
146
147                    const command = parseConfigFileWithSystem("tsconfig.json", {}, /*watchOptionsToExtend*/ undefined, system, noop)!;
148                    const builderProgram = createIncrementalProgram({
149                        rootNames: command.fileNames,
150                        options: command.options,
151                        projectReferences: command.projectReferences,
152                        configFileParsingDiagnostics: getConfigFileParsingDiagnostics(command),
153                        host: createIncrementalCompilerHost(command.options, system)
154                    });
155
156                    const state = builderProgram.getState();
157                    assert.equal(state.changedFilesSet!.size, 0, "changes");
158
159                    assert.equal(state.fileInfos.size, 3, "FileInfo size");
160                    assert.deepEqual(state.fileInfos.get(libFile.path as Path), {
161                        version: system.createHash(libFile.content),
162                        signature: system.createHash(libFile.content),
163                        affectsGlobalScope: true,
164                    });
165                    assert.deepEqual(state.fileInfos.get(file1.path as Path), {
166                        version: system.createHash(file1.content),
167                        signature: system.createHash(`${file1.content.replace("export ", "export declare ")}\n`),
168                        affectsGlobalScope: false,
169                    });
170                    assert.deepEqual(state.fileInfos.get(file2.path as Path), {
171                        version: system.createHash(fileModified.content),
172                        signature: system.createHash("export declare const y: string;\n"),
173                        affectsGlobalScope: false,
174                    });
175
176                    assert.deepEqual(state.compilerOptions, {
177                        incremental: true,
178                        module: ModuleKind.AMD,
179                        configFilePath: config.path
180                    });
181
182                    assert.equal(state.referencedMap!.size, 0);
183                    assert.equal(state.exportedModulesMap!.size, 0);
184
185                    assert.equal(state.semanticDiagnosticsPerFile!.size, 3);
186                    assert.deepEqual(state.semanticDiagnosticsPerFile!.get(libFile.path as Path), emptyArray);
187                    assert.deepEqual(state.semanticDiagnosticsPerFile!.get(file1.path as Path), emptyArray);
188                    assert.deepEqual(state.semanticDiagnosticsPerFile!.get(file2.path as Path), [{
189                        file: state.program!.getSourceFileByPath(file2.path as Path)!,
190                        start: 13,
191                        length: 1,
192                        code: Diagnostics.Type_0_is_not_assignable_to_type_1.code,
193                        category: Diagnostics.Type_0_is_not_assignable_to_type_1.category,
194                        messageText: "Type 'number' is not assignable to type 'string'.",
195                        relatedInformation: undefined,
196                        reportsUnnecessary: undefined,
197                        reportsDeprecated: undefined,
198                        source: undefined,
199                        skippedOn: undefined,
200                    }]);
201                });
202            });
203
204            verifyIncrementalWatchEmit({
205                files: () => [libFile, file1, file2, {
206                    path: configFile.path,
207                    content: JSON.stringify({ compilerOptions: { incremental: true, module: "amd", outFile: "out.js" } })
208                }],
209                subScenario: "module compilation/with --out",
210            });
211        });
212
213        verifyIncrementalWatchEmit({
214            files: () => {
215                const config: File = {
216                    path: configFile.path,
217                    content: JSON.stringify({
218                        compilerOptions: {
219                            incremental: true,
220                            target: "es5",
221                            module: "commonjs",
222                            declaration: true,
223                            emitDeclarationOnly: true
224                        }
225                    })
226                };
227                const aTs: File = {
228                    path: `${project}/a.ts`,
229                    content: `import { B } from "./b";
230export interface A {
231    b: B;
232}
233`
234                };
235                const bTs: File = {
236                    path: `${project}/b.ts`,
237                    content: `import { C } from "./c";
238export interface B {
239    b: C;
240}
241`
242                };
243                const cTs: File = {
244                    path: `${project}/c.ts`,
245                    content: `import { A } from "./a";
246export interface C {
247    a: A;
248}
249`
250                };
251                const indexTs: File = {
252                    path: `${project}/index.ts`,
253                    content: `export { A } from "./a";
254export { B } from "./b";
255export { C } from "./c";
256`
257                };
258                return [libFile, aTs, bTs, cTs, indexTs, config];
259            },
260            subScenario: "incremental with circular references",
261            modifyFs: host => host.writeFile(`${project}/a.ts`, `import { B } from "./b";
262export interface A {
263    b: B;
264    foo: any;
265}
266`)
267        });
268
269        verifyIncrementalWatchEmit({
270            subScenario: "when file with ambient global declaration file is deleted",
271            files: () => [
272                { path: libFile.path, content: libContent },
273                { path: `${project}/globals.d.ts`, content: `declare namespace Config { const value: string;} ` },
274                { path: `${project}/index.ts`, content: `console.log(Config.value);` },
275                { path: configFile.path, content: JSON.stringify({ compilerOptions: { incremental: true, } }) }
276            ],
277            modifyFs: host => host.deleteFile(`${project}/globals.d.ts`)
278        });
279
280        describe("with option jsxImportSource", () => {
281            const jsxImportSourceOptions = { module: "commonjs", jsx: "react-jsx", incremental: true, jsxImportSource: "react" };
282            const jsxLibraryContent = `export namespace JSX {
283    interface Element {}
284    interface IntrinsicElements {
285        div: {
286            propA?: boolean;
287        };
288    }
289}
290export function jsx(...args: any[]): void;
291export function jsxs(...args: any[]): void;
292export const Fragment: unique symbol;
293`;
294
295            verifyIncrementalWatchEmit({
296                subScenario: "jsxImportSource option changed",
297                files: () => [
298                    { path: libFile.path, content: libContent },
299                    { path: `${project}/node_modules/react/jsx-runtime/index.d.ts`, content: jsxLibraryContent },
300                    { path: `${project}/node_modules/react/package.json`, content: JSON.stringify({ name: "react", version: "0.0.1" }) },
301                    { path: `${project}/node_modules/preact/jsx-runtime/index.d.ts`, content: jsxLibraryContent.replace("propA", "propB") },
302                    { path: `${project}/node_modules/preact/package.json`, content: JSON.stringify({ name: "preact", version: "0.0.1" }) },
303                    { path: `${project}/index.tsx`, content: `export const App = () => <div propA={true}></div>;` },
304                    { path: configFile.path, content: JSON.stringify({ compilerOptions: jsxImportSourceOptions }) }
305                ],
306                modifyFs: host => host.writeFile(configFile.path, JSON.stringify({ compilerOptions: { ...jsxImportSourceOptions, jsxImportSource: "preact" } })),
307                optionsToExtend: ["--explainFiles"]
308            });
309
310            verifyIncrementalWatchEmit({
311                subScenario: "jsxImportSource backing types added",
312                files: () => [
313                    { path: libFile.path, content: libContent },
314                    { path: `${project}/index.tsx`, content: `export const App = () => <div propA={true}></div>;` },
315                    { path: configFile.path, content: JSON.stringify({ compilerOptions: jsxImportSourceOptions }) }
316                ],
317                modifyFs: host => {
318                    host.createDirectory(`${project}/node_modules`);
319                    host.createDirectory(`${project}/node_modules/react`);
320                    host.createDirectory(`${project}/node_modules/react/jsx-runtime`);
321                    host.writeFile(`${project}/node_modules/react/jsx-runtime/index.d.ts`, jsxLibraryContent);
322                    host.writeFile(`${project}/node_modules/react/package.json`, JSON.stringify({ name: "react", version: "0.0.1" }));
323                }
324            });
325
326            verifyIncrementalWatchEmit({
327                subScenario: "jsxImportSource backing types removed",
328                files: () => [
329                    { path: libFile.path, content: libContent },
330                    { path: `${project}/node_modules/react/jsx-runtime/index.d.ts`, content: jsxLibraryContent },
331                    { path: `${project}/node_modules/react/package.json`, content: JSON.stringify({ name: "react", version: "0.0.1" }) },
332                    { path: `${project}/index.tsx`, content: `export const App = () => <div propA={true}></div>;` },
333                    { path: configFile.path, content: JSON.stringify({ compilerOptions: jsxImportSourceOptions }) }
334                ],
335                modifyFs: host => {
336                    host.deleteFile(`${project}/node_modules/react/jsx-runtime/index.d.ts`);
337                    host.deleteFile(`${project}/node_modules/react/package.json`);
338                }
339            });
340
341            verifyIncrementalWatchEmit({
342                subScenario: "importHelpers backing types removed",
343                files: () => [
344                    { path: libFile.path, content: libContent },
345                    { path: `${project}/node_modules/tslib/index.d.ts`, content: "export function __assign(...args: any[]): any;" },
346                    { path: `${project}/node_modules/tslib/package.json`, content: JSON.stringify({ name: "tslib", version: "0.0.1" }) },
347                    { path: `${project}/index.tsx`, content: `export const x = {...{}};` },
348                    { path: configFile.path, content: JSON.stringify({ compilerOptions: { importHelpers: true } }) }
349                ],
350                modifyFs: host => {
351                    host.deleteFile(`${project}/node_modules/tslib/index.d.ts`);
352                    host.deleteFile(`${project}/node_modules/tslib/package.json`);
353                }
354            });
355        });
356    });
357}
358