• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1namespace ts {
2    describe("unittests:: tsc:: incremental::", () => {
3        verifyTscWithEdits({
4            scenario: "incremental",
5            subScenario: "when passing filename for buildinfo on commandline",
6            fs: () => loadProjectFromFiles({
7                "/src/project/src/main.ts": "export const x = 10;",
8                "/src/project/tsconfig.json": Utils.dedent`
9                    {
10                        "compilerOptions": {
11                            "target": "es5",
12                            "module": "commonjs",
13                        },
14                        "include": [
15                            "src/**/*.ts"
16                        ]
17                    }`,
18            }),
19            commandLineArgs: ["--incremental", "--p", "src/project", "--tsBuildInfoFile", "src/project/.tsbuildinfo", "--explainFiles"],
20            edits: noChangeOnlyRuns
21        });
22
23        verifyTscWithEdits({
24            scenario: "incremental",
25            subScenario: "when passing rootDir from commandline",
26            fs: () => loadProjectFromFiles({
27                "/src/project/src/main.ts": "export const x = 10;",
28                "/src/project/tsconfig.json": Utils.dedent`
29                    {
30                        "compilerOptions": {
31                            "incremental": true,
32                            "outDir": "dist",
33                        },
34                    }`,
35            }),
36            commandLineArgs: ["--p", "src/project", "--rootDir", "src/project/src"],
37            edits: noChangeOnlyRuns
38        });
39
40        verifyTscWithEdits({
41            scenario: "incremental",
42            subScenario: "with only dts files",
43            fs: () => loadProjectFromFiles({
44                "/src/project/src/main.d.ts": "export const x = 10;",
45                "/src/project/src/another.d.ts": "export const y = 10;",
46                "/src/project/tsconfig.json": "{}",
47            }),
48            commandLineArgs: ["--incremental", "--p", "src/project"],
49            edits: [
50                noChangeRun,
51                {
52                    subScenario: "incremental-declaration-doesnt-change",
53                    modifyFs: fs => appendText(fs, "/src/project/src/main.d.ts", "export const xy = 100;")
54                }
55            ]
56        });
57
58        verifyTscWithEdits({
59            scenario: "incremental",
60            subScenario: "when passing rootDir is in the tsconfig",
61            fs: () => loadProjectFromFiles({
62                "/src/project/src/main.ts": "export const x = 10;",
63                "/src/project/tsconfig.json": Utils.dedent`
64                    {
65                        "compilerOptions": {
66                            "incremental": true,
67                            "outDir": "./built",
68                            "rootDir": "./"
69                        },
70                    }`,
71            }),
72            commandLineArgs: ["--p", "src/project"],
73            edits: noChangeOnlyRuns
74        });
75
76        verifyTscWithEdits({
77            scenario: "incremental",
78            subScenario: "tsbuildinfo has error",
79            fs: () => loadProjectFromFiles({
80                "/src/project/main.ts": "export const x = 10;",
81                "/src/project/tsconfig.json": "{}",
82                "/src/project/tsconfig.tsbuildinfo": "Some random string",
83            }),
84            commandLineArgs: ["--p", "src/project", "-i"],
85            edits: [{
86                subScenario: "tsbuildinfo written has error",
87                modifyFs: fs => prependText(fs, "/src/project/tsconfig.tsbuildinfo", "Some random string"),
88            }]
89        });
90
91        describe("with noEmitOnError", () => {
92            let projFs: vfs.FileSystem;
93            before(() => {
94                projFs = loadProjectFromDisk("tests/projects/noEmitOnError");
95            });
96            after(() => {
97                projFs = undefined!;
98            });
99
100            function verifyNoEmitOnError(subScenario: string, fixModifyFs: TestTscEdit["modifyFs"], modifyFs?: TestTscEdit["modifyFs"]) {
101                verifyTscWithEdits({
102                    scenario: "incremental",
103                    subScenario,
104                    fs: () => projFs,
105                    commandLineArgs: ["--incremental", "-p", "src"],
106                    modifyFs,
107                    edits: [
108                        noChangeWithExportsDiscrepancyRun,
109                        {
110                            subScenario: "incremental-declaration-doesnt-change",
111                            modifyFs: fixModifyFs
112                        },
113                        noChangeRun,
114                    ],
115                    baselinePrograms: true
116                });
117            }
118            verifyNoEmitOnError(
119                "with noEmitOnError syntax errors",
120                fs => fs.writeFileSync("/src/src/main.ts", `import { A } from "../shared/types/db";
121const a = {
122    lastName: 'sdsd'
123};`, "utf-8")
124            );
125
126            verifyNoEmitOnError(
127                "with noEmitOnError semantic errors",
128                fs => fs.writeFileSync("/src/src/main.ts", `import { A } from "../shared/types/db";
129const a: string = "hello";`, "utf-8"),
130                fs => fs.writeFileSync("/src/src/main.ts", `import { A } from "../shared/types/db";
131const a: string = 10;`, "utf-8"),
132            );
133        });
134
135        describe("when noEmit changes between compilation", () => {
136            verifyNoEmitChanges({ incremental: true });
137            verifyNoEmitChanges({ incremental: true, declaration: true });
138            verifyNoEmitChanges({ composite: true });
139
140            function verifyNoEmitChanges(compilerOptions: CompilerOptions) {
141                const discrepancyExplanation = () => [
142                    ...noChangeWithExportsDiscrepancyRun.discrepancyExplanation!(),
143                    "Clean build will not have latestChangedDtsFile as there was no emit and emitSignatures as undefined for files",
144                    "Incremental will store the past latestChangedDtsFile and emitSignatures",
145                ];
146                const discrepancyIfNoDtsEmit = getEmitDeclarations(compilerOptions) ?
147                    undefined :
148                    noChangeWithExportsDiscrepancyRun.discrepancyExplanation;
149                const noChangeRunWithNoEmit: TestTscEdit = {
150                    ...noChangeRun,
151                    subScenario: "No Change run with noEmit",
152                    commandLineArgs: ["--p", "src/project", "--noEmit"],
153                    discrepancyExplanation: compilerOptions.composite ?
154                        discrepancyExplanation :
155                        !compilerOptions.declaration ?
156                            noChangeWithExportsDiscrepancyRun.discrepancyExplanation :
157                            undefined,
158                };
159                const noChangeRunWithEmit: TestTscEdit = {
160                    ...noChangeRun,
161                    subScenario: "No Change run with emit",
162                    commandLineArgs: ["--p", "src/project"],
163                    discrepancyExplanation: discrepancyIfNoDtsEmit,
164                };
165                let optionsString = "";
166                for (const key in compilerOptions) {
167                    if (hasProperty(compilerOptions, key)) {
168                        optionsString += ` ${key}`;
169                    }
170                }
171
172                verifyTscWithEdits({
173                    scenario: "incremental",
174                    subScenario: `noEmit changes${optionsString}`,
175                    commandLineArgs: ["--p", "src/project"],
176                    fs,
177                    edits: [
178                        noChangeRunWithNoEmit,
179                        noChangeRunWithNoEmit,
180                        {
181                            subScenario: "Introduce error but still noEmit",
182                            commandLineArgs: ["--p", "src/project", "--noEmit"],
183                            modifyFs: fs => replaceText(fs, "/src/project/src/class.ts", "prop", "prop1"),
184                            discrepancyExplanation: compilerOptions.composite ?
185                                discrepancyExplanation :
186                                compilerOptions.declaration ?
187                                    noChangeWithExportsDiscrepancyRun.discrepancyExplanation :
188                                    undefined,
189                        },
190                        {
191                            subScenario: "Fix error and emit",
192                            modifyFs: fs => replaceText(fs, "/src/project/src/class.ts", "prop1", "prop"),
193                            discrepancyExplanation: discrepancyIfNoDtsEmit,
194                        },
195                        noChangeRunWithEmit,
196                        noChangeRunWithNoEmit,
197                        noChangeRunWithNoEmit,
198                        noChangeRunWithEmit,
199                        {
200                            subScenario: "Introduce error and emit",
201                            modifyFs: fs => replaceText(fs, "/src/project/src/class.ts", "prop", "prop1"),
202                            discrepancyExplanation: discrepancyIfNoDtsEmit,
203                        },
204                        noChangeRunWithEmit,
205                        noChangeRunWithNoEmit,
206                        noChangeRunWithNoEmit,
207                        noChangeRunWithEmit,
208                        {
209                            subScenario: "Fix error and no emit",
210                            commandLineArgs: ["--p", "src/project", "--noEmit"],
211                            modifyFs: fs => replaceText(fs, "/src/project/src/class.ts", "prop1", "prop"),
212                            discrepancyExplanation: compilerOptions.composite ?
213                                discrepancyExplanation :
214                                noChangeWithExportsDiscrepancyRun.discrepancyExplanation,
215                        },
216                        noChangeRunWithEmit,
217                        noChangeRunWithNoEmit,
218                        noChangeRunWithNoEmit,
219                        noChangeRunWithEmit,
220                    ],
221                });
222
223                verifyTscWithEdits({
224                    scenario: "incremental",
225                    subScenario: `noEmit changes with initial noEmit${optionsString}`,
226                    commandLineArgs: ["--p", "src/project", "--noEmit"],
227                    fs,
228                    edits: [
229                        noChangeRunWithEmit,
230                        {
231                            subScenario: "Introduce error with emit",
232                            commandLineArgs: ["--p", "src/project"],
233                            modifyFs: fs => replaceText(fs, "/src/project/src/class.ts", "prop", "prop1"),
234                        },
235                        {
236                            subScenario: "Fix error and no emit",
237                            modifyFs: fs => replaceText(fs, "/src/project/src/class.ts", "prop1", "prop"),
238                            discrepancyExplanation: compilerOptions.composite ?
239                                discrepancyExplanation :
240                                noChangeWithExportsDiscrepancyRun.discrepancyExplanation,
241                        },
242                        noChangeRunWithEmit,
243                    ],
244                });
245
246                function fs() {
247                    return loadProjectFromFiles({
248                        "/src/project/src/class.ts": Utils.dedent`
249                            export class classC {
250                                prop = 1;
251                            }`,
252                        "/src/project/src/indirectClass.ts": Utils.dedent`
253                            import { classC } from './class';
254                            export class indirectClass {
255                                classC = new classC();
256                            }`,
257                        "/src/project/src/directUse.ts": Utils.dedent`
258                            import { indirectClass } from './indirectClass';
259                            new indirectClass().classC.prop;`,
260                        "/src/project/src/indirectUse.ts": Utils.dedent`
261                            import { indirectClass } from './indirectClass';
262                            new indirectClass().classC.prop;`,
263                        "/src/project/src/noChangeFile.ts": Utils.dedent`
264                            export function writeLog(s: string) {
265                            }`,
266                        "/src/project/src/noChangeFileWithEmitSpecificError.ts": Utils.dedent`
267                            function someFunc(arguments: boolean, ...rest: any[]) {
268                            }`,
269                        "/src/project/tsconfig.json": JSON.stringify({ compilerOptions }),
270                    });
271                }
272            }
273        });
274
275        verifyTscWithEdits({
276            scenario: "incremental",
277            subScenario: `when global file is added, the signatures are updated`,
278            fs: () => loadProjectFromFiles({
279                "/src/project/src/main.ts": Utils.dedent`
280                    /// <reference path="./filePresent.ts"/>
281                    /// <reference path="./fileNotFound.ts"/>
282                    function main() { }
283                `,
284                "/src/project/src/anotherFileWithSameReferenes.ts": Utils.dedent`
285                    /// <reference path="./filePresent.ts"/>
286                    /// <reference path="./fileNotFound.ts"/>
287                    function anotherFileWithSameReferenes() { }
288                `,
289                "/src/project/src/filePresent.ts": `function something() { return 10; }`,
290                "/src/project/tsconfig.json": JSON.stringify({
291                    compilerOptions: { composite: true, },
292                    include: ["src/**/*.ts"]
293                }),
294            }),
295            commandLineArgs: ["--p", "src/project"],
296            edits: [
297                noChangeRun,
298                {
299                    subScenario: "Modify main file",
300                    modifyFs: fs => appendText(fs, `/src/project/src/main.ts`, `something();`),
301                },
302                {
303                    subScenario: "Modify main file again",
304                    modifyFs: fs => appendText(fs, `/src/project/src/main.ts`, `something();`),
305                },
306                {
307                    subScenario: "Add new file and update main file",
308                    modifyFs: fs => {
309                        fs.writeFileSync(`/src/project/src/newFile.ts`, "function foo() { return 20; }");
310                        prependText(fs, `/src/project/src/main.ts`, `/// <reference path="./newFile.ts"/>
311`);
312                        appendText(fs, `/src/project/src/main.ts`, `foo();`);
313                    },
314                },
315                {
316                    subScenario: "Write file that could not be resolved",
317                    modifyFs: fs => fs.writeFileSync(`/src/project/src/fileNotFound.ts`, "function something2() { return 20; }"),
318                },
319                {
320                    subScenario: "Modify main file",
321                    modifyFs: fs => appendText(fs, `/src/project/src/main.ts`, `something();`),
322                },
323            ],
324            baselinePrograms: true,
325        });
326
327        describe("when synthesized imports are added to files", () => {
328            function getJsxLibraryContent() {
329                return `
330export {};
331declare global {
332    namespace JSX {
333        interface Element {}
334        interface IntrinsicElements {
335            div: {
336                propA?: boolean;
337            };
338        }
339    }
340}`;
341            }
342
343            verifyTsc({
344                scenario: "react-jsx-emit-mode",
345                subScenario: "with no backing types found doesn't crash",
346                fs: () => loadProjectFromFiles({
347                    "/src/project/node_modules/react/jsx-runtime.js": "export {}", // js needs to be present so there's a resolution result
348                    "/src/project/node_modules/@types/react/index.d.ts": getJsxLibraryContent(), // doesn't contain a jsx-runtime definition
349                    "/src/project/src/index.tsx": `export const App = () => <div propA={true}></div>;`,
350                    "/src/project/tsconfig.json": JSON.stringify({ compilerOptions: { module: "commonjs", jsx: "react-jsx", incremental: true, jsxImportSource: "react" } })
351                }),
352                commandLineArgs: ["--p", "src/project"]
353            });
354
355            verifyTsc({
356                scenario: "react-jsx-emit-mode",
357                subScenario: "with no backing types found doesn't crash under --strict",
358                fs: () => loadProjectFromFiles({
359                    "/src/project/node_modules/react/jsx-runtime.js": "export {}", // js needs to be present so there's a resolution result
360                    "/src/project/node_modules/@types/react/index.d.ts": getJsxLibraryContent(), // doesn't contain a jsx-runtime definition
361                    "/src/project/src/index.tsx": `export const App = () => <div propA={true}></div>;`,
362                    "/src/project/tsconfig.json": JSON.stringify({ compilerOptions: { module: "commonjs", jsx: "react-jsx", incremental: true, jsxImportSource: "react" } })
363                }),
364                commandLineArgs: ["--p", "src/project", "--strict"]
365            });
366        });
367
368        verifyTscWithEdits({
369            scenario: "incremental",
370            subScenario: "when new file is added to the referenced project",
371            commandLineArgs: ["-i", "-p", `src/projects/project2`],
372            fs: () => loadProjectFromFiles({
373                "/src/projects/project1/tsconfig.json": JSON.stringify({
374                    compilerOptions: {
375                        module: "none",
376                        composite: true
377                    },
378                    exclude: ["temp"]
379                }),
380                "/src/projects/project1/class1.ts": `class class1 {}`,
381                "/src/projects/project1/class1.d.ts": `declare class class1 {}`,
382                "/src/projects/project2/tsconfig.json": JSON.stringify({
383                    compilerOptions: {
384                        module: "none",
385                        composite: true
386                    },
387                    references: [
388                        { path: "../project1" }
389                    ]
390                }),
391                "/src/projects/project2/class2.ts": `class class2 {}`,
392            }),
393            edits: [
394                {
395                    subScenario: "Add class3 to project1 and build it",
396                    modifyFs: fs => fs.writeFileSync("/src/projects/project1/class3.ts", `class class3 {}`, "utf-8"),
397                    discrepancyExplanation: () => [
398                        "Ts buildinfo will not be updated in incremental build so it will have semantic diagnostics cached from previous build",
399                        "But in clean build because of global diagnostics, semantic diagnostics are not queried so not cached in tsbuildinfo",
400                    ],
401                },
402                {
403                    subScenario: "Add output of class3",
404                    modifyFs: fs => fs.writeFileSync("/src/projects/project1/class3.d.ts", `declare class class3 {}`, "utf-8"),
405                },
406                {
407                    subScenario: "Add excluded file to project1",
408                    modifyFs: fs => {
409                        fs.mkdirSync("/src/projects/project1/temp");
410                        fs.writeFileSync("/src/projects/project1/temp/file.d.ts", `declare class file {}`, "utf-8");
411                    },
412                },
413                {
414                    subScenario: "Delete output for class3",
415                    modifyFs: fs => fs.unlinkSync("/src/projects/project1/class3.d.ts"),
416                    discrepancyExplanation: () => [
417                        "Ts buildinfo will be updated but will retain lib file errors from previous build and not others because they are emitted because of change which results in clearing their semantic diagnostics cache",
418                        "But in clean build because of global diagnostics, semantic diagnostics are not queried so not cached in tsbuildinfo",
419                    ],
420                },
421                {
422                    subScenario: "Create output for class3",
423                    modifyFs: fs => fs.writeFileSync("/src/projects/project1/class3.d.ts", `declare class class3 {}`, "utf-8"),
424                },
425            ]
426        });
427
428
429        verifyTscWithEdits({
430            scenario: "incremental",
431            subScenario: "when project has strict true",
432            commandLineArgs: ["-noEmit", "-p", `src/project`],
433            fs: () => loadProjectFromFiles({
434                "/src/project/tsconfig.json": JSON.stringify({
435                    compilerOptions: {
436                        incremental: true,
437                        strict: true,
438                    },
439                }),
440                "/src/project/class1.ts": `export class class1 {}`,
441            }),
442            edits: noChangeOnlyRuns,
443            baselinePrograms: true
444        });
445
446        verifyTscWithEdits({
447            scenario: "incremental",
448            subScenario: "serializing error chains",
449            commandLineArgs: ["-p", `src/project`],
450            fs: () => loadProjectFromFiles({
451                "/src/project/tsconfig.json": JSON.stringify({
452                    compilerOptions: {
453                        incremental: true,
454                        strict: true,
455                        jsx: "react",
456                        module: "esnext",
457                    },
458                }),
459                "/src/project/index.tsx": Utils.dedent`
460                    declare namespace JSX {
461                        interface ElementChildrenAttribute { children: {}; }
462                        interface IntrinsicElements { div: {} }
463                    }
464
465                    declare var React: any;
466
467                    declare function Component(props: never): any;
468                    declare function Component(props: { children?: number }): any;
469                    (<Component>
470                        <div />
471                        <div />
472                    </Component>)`
473            }, `\ninterface ReadonlyArray<T> { readonly length: number }`),
474            edits: noChangeOnlyRuns,
475        });
476
477        verifyTsc({
478            scenario: "incremental",
479            subScenario: "ts file with no-default-lib that augments the global scope",
480            fs: () => loadProjectFromFiles({
481                "/src/project/src/main.ts": Utils.dedent`
482                    /// <reference no-default-lib="true"/>
483                    /// <reference lib="esnext" />
484
485                    declare global {
486                        interface Test {
487                        }
488                    }
489
490                    export {};
491                `,
492                "/src/project/tsconfig.json": Utils.dedent`
493                    {
494                        "compilerOptions": {
495                            "target": "ESNext",
496                            "module": "ESNext",
497                            "incremental": true,
498                            "outDir": "dist",
499                        },
500                    }`,
501            }),
502            commandLineArgs: ["--p", "src/project", "--rootDir", "src/project/src"],
503            modifyFs: (fs) => {
504                fs.writeFileSync("/lib/lib.esnext.d.ts", libContent);
505            }
506        });
507
508        verifyTscWithEdits({
509            scenario: "incremental",
510            subScenario: "change to type that gets used as global through export in another file",
511            commandLineArgs: ["-p", `src/project`],
512            fs: () => loadProjectFromFiles({
513                "/src/project/tsconfig.json": JSON.stringify({ compilerOptions: { composite: true }, }),
514                "/src/project/class1.ts": `const a: MagicNumber = 1;
515console.log(a);`,
516                "/src/project/constants.ts": "export default 1;",
517                "/src/project/types.d.ts": `type MagicNumber = typeof import('./constants').default`,
518            }),
519            edits: [{
520                subScenario: "Modify imports used in global file",
521                modifyFs: fs => fs.writeFileSync("/src/project/constants.ts", "export default 2;"),
522            }],
523        });
524
525        verifyTscWithEdits({
526            scenario: "incremental",
527            subScenario: "change to type that gets used as global through export in another file through indirect import",
528            commandLineArgs: ["-p", `src/project`],
529            fs: () => loadProjectFromFiles({
530                "/src/project/tsconfig.json": JSON.stringify({ compilerOptions: { composite: true }, }),
531                "/src/project/class1.ts": `const a: MagicNumber = 1;
532console.log(a);`,
533                "/src/project/constants.ts": "export default 1;",
534                "/src/project/reexport.ts": `export { default as ConstantNumber } from "./constants"`,
535                "/src/project/types.d.ts": `type MagicNumber = typeof import('./reexport').ConstantNumber`,
536            }),
537            edits: [{
538                subScenario: "Modify imports used in global file",
539                modifyFs: fs => fs.writeFileSync("/src/project/constants.ts", "export default 2;"),
540            }],
541        });
542
543        function verifyModifierChange(declaration: boolean) {
544            verifyTscWithEdits({
545                scenario: "incremental",
546                subScenario: `change to modifier of class expression field${declaration ? " with declaration emit enabled" : ""}`,
547                commandLineArgs: ["-p", "src/project", "--incremental"],
548                fs: () => loadProjectFromFiles({
549                    "/src/project/tsconfig.json": JSON.stringify({ compilerOptions: { declaration } }),
550                    "/src/project/main.ts": Utils.dedent`
551                        import MessageablePerson from './MessageablePerson.js';
552                        function logMessage( person: MessageablePerson ) {
553                            console.log( person.message );
554                        }`,
555                    "/src/project/MessageablePerson.ts": Utils.dedent`
556                        const Messageable = () => {
557                            return class MessageableClass {
558                                public message = 'hello';
559                            }
560                        };
561                        const wrapper = () => Messageable();
562                        type MessageablePerson = InstanceType<ReturnType<typeof wrapper>>;
563                        export default MessageablePerson;`,
564                }),
565                modifyFs: fs => appendText(fs, "/lib/lib.d.ts", Utils.dedent`
566                    type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
567                    type InstanceType<T extends abstract new (...args: any) => any> = T extends abstract new (...args: any) => infer R ? R : any;`
568                ),
569                edits: [
570                    {
571                        subScenario: "modify public to protected",
572                        modifyFs: fs => replaceText(fs, "/src/project/MessageablePerson.ts", "public", "protected"),
573                    },
574                    {
575                        subScenario: "modify protected to public",
576                        modifyFs: fs => replaceText(fs, "/src/project/MessageablePerson.ts", "protected", "public"),
577                    },
578                ],
579            });
580        }
581        verifyModifierChange(/*declaration*/ false);
582        verifyModifierChange(/*declaration*/ true);
583    });
584}
585