• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1namespace ts.tscWatch {
2    import projectsLocation = TestFSWithWatch.tsbuildProjectsLocation;
3    import getFilePathInProject = TestFSWithWatch.getTsBuildProjectFilePath;
4    import getFileFromProject = TestFSWithWatch.getTsBuildProjectFile;
5    type TsBuildWatchSystem = TestFSWithWatch.TestServerHostTrackingWrittenFiles;
6
7    function createTsBuildWatchSystem(fileOrFolderList: readonly TestFSWithWatch.FileOrFolderOrSymLink[], params?: TestFSWithWatch.TestServerHostCreationParameters) {
8        return TestFSWithWatch.changeToHostTrackingWrittenFiles(
9            createWatchedSystem(fileOrFolderList, params)
10        );
11    }
12
13    export function createSolutionBuilder(system: WatchedSystem, rootNames: readonly string[], defaultOptions?: BuildOptions) {
14        const host = createSolutionBuilderHost(system);
15        return ts.createSolutionBuilder(host, rootNames, defaultOptions || {});
16    }
17
18    export function ensureErrorFreeBuild(host: WatchedSystem, rootNames: readonly string[]) {
19        // ts build should succeed
20        const solutionBuilder = createSolutionBuilder(host, rootNames, {});
21        solutionBuilder.build();
22        assert.equal(host.getOutput().length, 0, JSON.stringify(host.getOutput(), /*replacer*/ undefined, " "));
23    }
24
25    type OutputFileStamp = [string, Date | undefined, boolean];
26    function transformOutputToOutputFileStamp(f: string, host: TsBuildWatchSystem): OutputFileStamp {
27        return [f, host.getModifiedTime(f), host.writtenFiles.has(host.toFullPath(f))] as OutputFileStamp;
28    }
29
30    describe("unittests:: tsbuild:: watchMode:: program updates", () => {
31        const scenario = "programUpdates";
32        const project = "sample1";
33        const enum SubProject {
34            core = "core",
35            logic = "logic",
36            tests = "tests",
37            ui = "ui"
38        }
39        type ReadonlyFile = Readonly<File>;
40        /** [tsconfig, index] | [tsconfig, index, anotherModule, someDecl] */
41        type SubProjectFiles = [ReadonlyFile, ReadonlyFile] | [ReadonlyFile, ReadonlyFile, ReadonlyFile, ReadonlyFile];
42        function getProjectPath(project: string) {
43            return `${projectsLocation}/${project}`;
44        }
45
46        function projectPath(subProject: SubProject) {
47            return getFilePathInProject(project, subProject);
48        }
49
50        function projectFilePath(subProject: SubProject, baseFileName: string) {
51            return `${projectPath(subProject)}/${baseFileName.toLowerCase()}`;
52        }
53
54        function projectFileName(subProject: SubProject, baseFileName: string) {
55            return `${projectPath(subProject)}/${baseFileName}`;
56        }
57
58        function projectFile(subProject: SubProject, baseFileName: string): File {
59            return getFileFromProject(project, `${subProject}/${baseFileName}`);
60        }
61
62        function subProjectFiles(subProject: SubProject, anotherModuleAndSomeDecl?: true): SubProjectFiles {
63            const tsconfig = projectFile(subProject, "tsconfig.json");
64            const index = projectFile(subProject, "index.ts");
65            if (!anotherModuleAndSomeDecl) {
66                return [tsconfig, index];
67            }
68            const anotherModule = projectFile(SubProject.core, "anotherModule.ts");
69            const someDecl = projectFile(SubProject.core, "some_decl.ts");
70            return [tsconfig, index, anotherModule, someDecl];
71        }
72
73        function getOutputFileNames(subProject: SubProject, baseFileNameWithoutExtension: string) {
74            const file = projectFilePath(subProject, baseFileNameWithoutExtension);
75            return [`${file}.js`, `${file}.d.ts`];
76        }
77
78        function getOutputStamps(host: TsBuildWatchSystem, subProject: SubProject, baseFileNameWithoutExtension: string): OutputFileStamp[] {
79            return getOutputFileNames(subProject, baseFileNameWithoutExtension).map(f => transformOutputToOutputFileStamp(f, host));
80        }
81
82        function getOutputFileStamps(host: TsBuildWatchSystem, additionalFiles?: readonly [SubProject, string][]): OutputFileStamp[] {
83            const result = [
84                ...getOutputStamps(host, SubProject.core, "anotherModule"),
85                ...getOutputStamps(host, SubProject.core, "index"),
86                ...getOutputStamps(host, SubProject.logic, "index"),
87                ...getOutputStamps(host, SubProject.tests, "index"),
88            ];
89            if (additionalFiles) {
90                additionalFiles.forEach(([subProject, baseFileNameWithoutExtension]) => result.push(...getOutputStamps(host, subProject, baseFileNameWithoutExtension)));
91            }
92            host.writtenFiles.clear();
93            return result;
94        }
95
96        function changeFile(fileName: string | (() => string), content: string | (() => string), caption: string): TscWatchCompileChange {
97            return {
98                caption,
99                change: sys => sys.writeFile(isString(fileName) ? fileName : fileName(), isString(content) ? content : content()),
100                timeouts: checkSingleTimeoutQueueLengthAndRun, // Builds core
101            };
102        }
103
104        function changeCore(content: () => string, caption: string) {
105            return changeFile(() => core[1].path, content, caption);
106        }
107
108        let core: SubProjectFiles;
109        let logic: SubProjectFiles;
110        let tests: SubProjectFiles;
111        let ui: SubProjectFiles;
112        let allFiles: readonly File[];
113        let testProjectExpectedWatchedFiles: string[];
114        let testProjectExpectedWatchedDirectoriesRecursive: string[];
115
116        before(() => {
117            core = subProjectFiles(SubProject.core, /*anotherModuleAndSomeDecl*/ true);
118            logic = subProjectFiles(SubProject.logic);
119            tests = subProjectFiles(SubProject.tests);
120            ui = subProjectFiles(SubProject.ui);
121            allFiles = [libFile, ...core, ...logic, ...tests, ...ui];
122            testProjectExpectedWatchedFiles = [core[0], core[1], core[2]!, ...logic, ...tests].map(f => f.path.toLowerCase());
123            testProjectExpectedWatchedDirectoriesRecursive = [projectPath(SubProject.core), projectPath(SubProject.logic)];
124        });
125
126        after(() => {
127            core = undefined!;
128            logic = undefined!;
129            tests = undefined!;
130            ui = undefined!;
131            allFiles = undefined!;
132            testProjectExpectedWatchedFiles = undefined!;
133            testProjectExpectedWatchedDirectoriesRecursive = undefined!;
134        });
135
136        verifyTscWatch({
137            scenario,
138            subScenario: "creates solution in watch mode",
139            commandLineArgs: ["-b", "-w", `${project}/${SubProject.tests}`],
140            sys: () => createWatchedSystem(allFiles, { currentDirectory: projectsLocation }),
141            changes: emptyArray
142        });
143
144        it("verify building references watches only those projects", () => {
145            const system = createTsBuildWatchSystem(allFiles, { currentDirectory: projectsLocation });
146            const host = createSolutionBuilderWithWatchHost(system);
147            const solutionBuilder = createSolutionBuilderWithWatch(host, [`${project}/${SubProject.tests}`], { watch: true });
148            solutionBuilder.buildReferences(`${project}/${SubProject.tests}`);
149
150            checkWatchedFiles(system, testProjectExpectedWatchedFiles.slice(0, testProjectExpectedWatchedFiles.length - tests.length));
151            checkWatchedDirectories(system, emptyArray, /*recursive*/ false);
152            checkWatchedDirectories(system, testProjectExpectedWatchedDirectoriesRecursive, /*recursive*/ true);
153
154            checkOutputErrorsInitial(system, emptyArray);
155            const testOutput = getOutputStamps(system, SubProject.tests, "index");
156            const outputFileStamps = getOutputFileStamps(system);
157            for (const stamp of outputFileStamps.slice(0, outputFileStamps.length - testOutput.length)) {
158                assert.isDefined(stamp[1], `${stamp[0]} expected to be present`);
159            }
160            for (const stamp of testOutput) {
161                assert.isUndefined(stamp[1], `${stamp[0]} expected to be missing`);
162            }
163            return system;
164        });
165
166        const buildTests: TscWatchCompileChange = {
167            caption: "Build Tests",
168            change: noop,
169            // Build tests
170            timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout,
171        };
172
173        describe("validates the changes and watched files", () => {
174            const newFileWithoutExtension = "newFile";
175            const newFile: File = {
176                path: projectFilePath(SubProject.core, `${newFileWithoutExtension}.ts`),
177                content: `export const newFileConst = 30;`
178            };
179
180            function verifyProjectChanges(subScenario: string, allFilesGetter: () => readonly File[]) {
181                const buildLogicOrUpdateTimeStamps: TscWatchCompileChange = {
182                    caption: "Build logic or update time stamps",
183                    change: noop,
184                    timeouts: checkSingleTimeoutQueueLengthAndRun, // Builds logic or updates timestamps
185                };
186
187                verifyTscWatch({
188                    scenario,
189                    subScenario: `${subScenario}/change builds changes and reports found errors message`,
190                    commandLineArgs: ["-b", "-w", `${project}/${SubProject.tests}`],
191                    sys: () => createWatchedSystem(
192                        allFilesGetter(),
193                        { currentDirectory: projectsLocation }
194                    ),
195                    changes: [
196                        changeCore(() => `${core[1].content}
197export class someClass { }`, "Make change to core"),
198                        buildLogicOrUpdateTimeStamps,
199                        buildTests,
200                        // Another change requeues and builds it
201                        changeCore(() => core[1].content, "Revert core file"),
202                        buildLogicOrUpdateTimeStamps,
203                        buildTests,
204                        {
205                            caption: "Make two changes",
206                            change: sys => {
207                                const change1 = `${core[1].content}
208export class someClass { }`;
209                                sys.writeFile(core[1].path, change1);
210                                assert.equal(sys.writtenFiles.size, 1);
211                                sys.writtenFiles.clear();
212                                sys.writeFile(core[1].path, `${change1}
213export class someClass2 { }`);
214                            },
215                            timeouts: checkSingleTimeoutQueueLengthAndRun, // Builds core
216                        },
217                        buildLogicOrUpdateTimeStamps,
218                        buildTests,
219                    ]
220                });
221
222                verifyTscWatch({
223                    scenario,
224                    subScenario: `${subScenario}/non local change does not start build of referencing projects`,
225                    commandLineArgs: ["-b", "-w", `${project}/${SubProject.tests}`],
226                    sys: () => createWatchedSystem(
227                        allFilesGetter(),
228                        { currentDirectory: projectsLocation }
229                    ),
230                    changes: [
231                        changeCore(() => `${core[1].content}
232function foo() { }`, "Make local change to core"),
233                        buildLogicOrUpdateTimeStamps,
234                        buildTests
235                    ]
236                });
237
238                function changeNewFile(newFileContent: string) {
239                    return changeFile(newFile.path, newFileContent, "Change to new File and build core");
240                }
241                verifyTscWatch({
242                    scenario,
243                    subScenario: `${subScenario}/builds when new file is added, and its subsequent updates`,
244                    commandLineArgs: ["-b", "-w", `${project}/${SubProject.tests}`],
245                    sys: () => createWatchedSystem(
246                        allFilesGetter(),
247                        { currentDirectory: projectsLocation }
248                    ),
249                    changes: [
250                        changeNewFile(newFile.content),
251                        buildLogicOrUpdateTimeStamps,
252                        buildTests,
253                        changeNewFile(`${newFile.content}
254export class someClass2 { }`),
255                        buildLogicOrUpdateTimeStamps,
256                        buildTests
257                    ]
258                });
259            }
260
261            describe("with simple project reference graph", () => {
262                verifyProjectChanges(
263                    "with simple project reference graph",
264                    () => allFiles
265                );
266            });
267
268            describe("with circular project reference", () => {
269                verifyProjectChanges(
270                    "with circular project reference",
271                    () => {
272                        const [coreTsconfig, ...otherCoreFiles] = core;
273                        const circularCoreConfig: File = {
274                            path: coreTsconfig.path,
275                            content: JSON.stringify({
276                                compilerOptions: { composite: true, declaration: true },
277                                references: [{ path: "../tests", circular: true }]
278                            })
279                        };
280                        return [libFile, circularCoreConfig, ...otherCoreFiles, ...logic, ...tests];
281                    }
282                );
283            });
284        });
285
286        verifyTscWatch({
287            scenario,
288            subScenario: "watches config files that are not present",
289            commandLineArgs: ["-b", "-w", `${project}/${SubProject.tests}`],
290            sys: () => createWatchedSystem(
291                [libFile, ...core, logic[1], ...tests],
292                { currentDirectory: projectsLocation }
293            ),
294            changes: [
295                {
296                    caption: "Write logic tsconfig and build logic",
297                    change: sys => sys.writeFile(logic[0].path, logic[0].content),
298                    timeouts: checkSingleTimeoutQueueLengthAndRun, // Builds logic
299                },
300                buildTests
301            ]
302        });
303
304        describe("when referenced using prepend, builds referencing project even for non local change", () => {
305            let coreIndex: File;
306            before(() => {
307                coreIndex = {
308                    path: core[1].path,
309                    content: `function foo() { return 10; }`
310                };
311            });
312            after(() => {
313                coreIndex = undefined!;
314            });
315            const buildLogic: TscWatchCompileChange = {
316                caption: "Build logic",
317                change: noop,
318                // Builds logic
319                timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout,
320            };
321            verifyTscWatch({
322                scenario,
323                subScenario: "when referenced using prepend builds referencing project even for non local change",
324                commandLineArgs: ["-b", "-w", `${project}/${SubProject.logic}`],
325                sys: () => {
326                    const coreTsConfig: File = {
327                        path: core[0].path,
328                        content: JSON.stringify({
329                            compilerOptions: { composite: true, declaration: true, outFile: "index.js" }
330                        })
331                    };
332                    const logicTsConfig: File = {
333                        path: logic[0].path,
334                        content: JSON.stringify({
335                            compilerOptions: { composite: true, declaration: true, outFile: "index.js" },
336                            references: [{ path: "../core", prepend: true }]
337                        })
338                    };
339                    const logicIndex: File = {
340                        path: logic[1].path,
341                        content: `function bar() { return foo() + 1 };`
342                    };
343                    return createWatchedSystem([libFile, coreTsConfig, coreIndex, logicTsConfig, logicIndex], { currentDirectory: projectsLocation });
344                },
345                changes: [
346                    changeCore(() => `${coreIndex.content}
347function myFunc() { return 10; }`, "Make non local change and build core"),
348                    buildLogic,
349                    changeCore(() => `${coreIndex.content}
350function myFunc() { return 100; }`, "Make local change and build core"),
351                    buildLogic,
352                ]
353            });
354        });
355
356        describe("when referenced project change introduces error in the down stream project and then fixes it", () => {
357            const subProjectLibrary = `${projectsLocation}/${project}/Library`;
358            const libraryTs: File = {
359                path: `${subProjectLibrary}/library.ts`,
360                content: `
361interface SomeObject
362{
363    message: string;
364}
365
366export function createSomeObject(): SomeObject
367{
368    return {
369        message: "new Object"
370    };
371}`
372            };
373            verifyTscWatch({
374                scenario,
375                subScenario: "when referenced project change introduces error in the down stream project and then fixes it",
376                commandLineArgs: ["-b", "-w", "App"],
377                sys: () => {
378                    const libraryTsconfig: File = {
379                        path: `${subProjectLibrary}/tsconfig.json`,
380                        content: JSON.stringify({ compilerOptions: { composite: true } })
381                    };
382                    const subProjectApp = `${projectsLocation}/${project}/App`;
383                    const appTs: File = {
384                        path: `${subProjectApp}/app.ts`,
385                        content: `import { createSomeObject } from "../Library/library";
386createSomeObject().message;`
387                    };
388                    const appTsconfig: File = {
389                        path: `${subProjectApp}/tsconfig.json`,
390                        content: JSON.stringify({ references: [{ path: "../Library" }] })
391                    };
392
393                    const files = [libFile, libraryTs, libraryTsconfig, appTs, appTsconfig];
394                    return createWatchedSystem(files, { currentDirectory: `${projectsLocation}/${project}` });
395                },
396                changes: [
397                    {
398                        caption: "Introduce error",
399                        // Change message in library to message2
400                        change: sys => sys.writeFile(libraryTs.path, libraryTs.content.replace(/message/g, "message2")),
401                        timeouts: sys => {
402                            sys.checkTimeoutQueueLengthAndRun(1); // Build library
403                            sys.checkTimeoutQueueLengthAndRun(1); // Build App
404                        },
405                    },
406                    {
407                        caption: "Fix error",
408                        // Revert library changes
409                        change: sys => sys.writeFile(libraryTs.path, libraryTs.content),
410                        timeouts: sys => {
411                            sys.checkTimeoutQueueLengthAndRun(1); // Build library
412                            sys.checkTimeoutQueueLengthAndRun(1); // Build App
413                        },
414                    },
415                ]
416            });
417
418        });
419
420        describe("reports errors in all projects on incremental compile", () => {
421            function verifyIncrementalErrors(subScenario: string, buildOptions: readonly string[]) {
422                verifyTscWatch({
423                    scenario,
424                    subScenario: `reportErrors/${subScenario}`,
425                    commandLineArgs: ["-b", "-w", `${project}/${SubProject.tests}`, ...buildOptions],
426                    sys: () => createWatchedSystem(allFiles, { currentDirectory: projectsLocation }),
427                    changes: [
428                        {
429                            caption: "change logic",
430                            change: sys => sys.writeFile(logic[1].path, `${logic[1].content}
431let y: string = 10;`),
432                            // Builds logic
433                            timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout,
434                        },
435                        {
436                            caption: "change core",
437                            change: sys => sys.writeFile(core[1].path, `${core[1].content}
438let x: string = 10;`),
439                            // Builds core
440                            timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout,
441                        }
442                    ]
443                });
444            }
445            verifyIncrementalErrors("when preserveWatchOutput is not used", emptyArray);
446            verifyIncrementalErrors("when preserveWatchOutput is passed on command line", ["--preserveWatchOutput"]);
447
448            describe("when declaration emit errors are present", () => {
449                const solution = "solution";
450                const subProject = "app";
451                const subProjectLocation = `${projectsLocation}/${solution}/${subProject}`;
452                const fileWithError: File = {
453                    path: `${subProjectLocation}/fileWithError.ts`,
454                    content: `export var myClassWithError = class {
455        tags() { }
456        private p = 12
457    };`
458                };
459                const fileWithFixedError: File = {
460                    path: fileWithError.path,
461                    content: fileWithError.content.replace("private p = 12", "")
462                };
463                const fileWithoutError: File = {
464                    path: `${subProjectLocation}/fileWithoutError.ts`,
465                    content: `export class myClass { }`
466                };
467                const tsconfig: File = {
468                    path: `${subProjectLocation}/tsconfig.json`,
469                    content: JSON.stringify({ compilerOptions: { composite: true } })
470                };
471
472                function incrementalBuild(sys: WatchedSystem) {
473                    sys.checkTimeoutQueueLengthAndRun(1); // Build the app
474                    sys.checkTimeoutQueueLength(0);
475                }
476
477                const fixError: TscWatchCompileChange = {
478                    caption: "Fix error in fileWithError",
479                    // Fix error
480                    change: sys => sys.writeFile(fileWithError.path, fileWithFixedError.content),
481                    timeouts: incrementalBuild
482                };
483
484                const changeFileWithoutError: TscWatchCompileChange = {
485                    caption: "Change fileWithoutError",
486                    change: sys => sys.writeFile(fileWithoutError.path, fileWithoutError.content.replace(/myClass/g, "myClass2")),
487                    timeouts: incrementalBuild
488                };
489
490                verifyTscWatch({
491                    scenario,
492                    subScenario: "reportErrors/declarationEmitErrors/when fixing error files all files are emitted",
493                    commandLineArgs: ["-b", "-w", subProject],
494                    sys: () => createWatchedSystem(
495                        [libFile, fileWithError, fileWithoutError, tsconfig],
496                        { currentDirectory: `${projectsLocation}/${solution}` }
497                    ),
498                    changes: [
499                        fixError
500                    ]
501                });
502
503                verifyTscWatch({
504                    scenario,
505                    subScenario: "reportErrors/declarationEmitErrors/when file with no error changes",
506                    commandLineArgs: ["-b", "-w", subProject],
507                    sys: () => createWatchedSystem(
508                        [libFile, fileWithError, fileWithoutError, tsconfig],
509                        { currentDirectory: `${projectsLocation}/${solution}` }
510                    ),
511                    changes: [
512                        changeFileWithoutError
513                    ]
514                });
515
516                describe("when reporting errors on introducing error", () => {
517                    const introduceError: TscWatchCompileChange = {
518                        caption: "Introduce error",
519                        change: sys => sys.writeFile(fileWithError.path, fileWithError.content),
520                        timeouts: incrementalBuild,
521                    };
522
523                    verifyTscWatch({
524                        scenario,
525                        subScenario: "reportErrors/declarationEmitErrors/introduceError/when fixing errors only changed file is emitted",
526                        commandLineArgs: ["-b", "-w", subProject],
527                        sys: () => createWatchedSystem(
528                            [libFile, fileWithFixedError, fileWithoutError, tsconfig],
529                            { currentDirectory: `${projectsLocation}/${solution}` }
530                        ),
531                        changes: [
532                            introduceError,
533                            fixError
534                        ]
535                    });
536
537                    verifyTscWatch({
538                        scenario,
539                        subScenario: "reportErrors/declarationEmitErrors/introduceError/when file with no error changes",
540                        commandLineArgs: ["-b", "-w", subProject],
541                        sys: () => createWatchedSystem(
542                            [libFile, fileWithFixedError, fileWithoutError, tsconfig],
543                            { currentDirectory: `${projectsLocation}/${solution}` }
544                        ),
545                        changes: [
546                            introduceError,
547                            changeFileWithoutError
548                        ]
549                    });
550                });
551            });
552        });
553
554        describe("tsc-watch and tsserver works with project references", () => {
555            describe("invoking when references are already built", () => {
556                function verifyWatchesOfProject(host: TsBuildWatchSystem, expectedWatchedFiles: readonly string[], expectedWatchedDirectoriesRecursive: readonly string[], expectedWatchedDirectories?: readonly string[]) {
557                    checkWatchedFilesDetailed(host, expectedWatchedFiles, 1);
558                    checkWatchedDirectoriesDetailed(host, expectedWatchedDirectories || emptyArray, 1, /*recursive*/ false);
559                    checkWatchedDirectoriesDetailed(host, expectedWatchedDirectoriesRecursive, 1, /*recursive*/ true);
560                }
561
562                function createSolutionOfProject(allFiles: readonly File[],
563                    currentDirectory: string,
564                    solutionBuilderconfig: string,
565                    getOutputFileStamps: (host: TsBuildWatchSystem) => readonly OutputFileStamp[]) {
566                    // Build the composite project
567                    const host = createTsBuildWatchSystem(allFiles, { currentDirectory });
568                    const solutionBuilder = createSolutionBuilder(host, [solutionBuilderconfig], {});
569                    solutionBuilder.build();
570                    const outputFileStamps = getOutputFileStamps(host);
571                    for (const stamp of outputFileStamps) {
572                        assert.isDefined(stamp[1], `${stamp[0]} expected to be present`);
573                    }
574                    return { host, solutionBuilder };
575                }
576
577                function createSolutionAndWatchModeOfProject(
578                    allFiles: readonly File[],
579                    currentDirectory: string,
580                    solutionBuilderconfig: string,
581                    watchConfig: string,
582                    getOutputFileStamps: (host: TsBuildWatchSystem) => readonly OutputFileStamp[]) {
583                    // Build the composite project
584                    const { host, solutionBuilder } = createSolutionOfProject(allFiles, currentDirectory, solutionBuilderconfig, getOutputFileStamps);
585
586                    // Build in watch mode
587                    const watch = createWatchOfConfigFile(watchConfig, host);
588                    checkOutputErrorsInitial(host, emptyArray);
589
590                    return { host, solutionBuilder, watch };
591                }
592
593                function createSolutionAndServiceOfProject(allFiles: readonly File[],
594                    currentDirectory: string,
595                    solutionBuilderconfig: string,
596                    openFileName: string,
597                    getOutputFileStamps: (host: TsBuildWatchSystem) => readonly OutputFileStamp[]) {
598                    // Build the composite project
599                    const { host, solutionBuilder } = createSolutionOfProject(allFiles, currentDirectory, solutionBuilderconfig, getOutputFileStamps);
600
601                    // service
602                    const service = projectSystem.createProjectService(host);
603                    service.openClientFile(openFileName);
604
605                    return { host, solutionBuilder, service };
606                }
607
608                function checkProjectActualFiles(service: projectSystem.TestProjectService, configFile: string, expectedFiles: readonly string[]) {
609                    projectSystem.checkNumberOfProjects(service, { configuredProjects: 1 });
610                    projectSystem.checkProjectActualFiles(service.configuredProjects.get(configFile.toLowerCase())!, expectedFiles);
611                }
612
613                function verifyDependencies(watch: Watch, filePath: string, expected: readonly string[]) {
614                    checkArray(`${filePath} dependencies`, watch.getCurrentProgram().getAllDependencies(watch.getCurrentProgram().getSourceFile(filePath)!), expected);
615                }
616
617                describe("on sample project", () => {
618                    const coreIndexDts = projectFileName(SubProject.core, "index.d.ts");
619                    const coreAnotherModuleDts = projectFileName(SubProject.core, "anotherModule.d.ts");
620                    const logicIndexDts = projectFileName(SubProject.logic, "index.d.ts");
621                    const expectedWatchedDirectoriesRecursive = projectSystem.getTypeRootsFromLocation(projectPath(SubProject.tests));
622                    const expectedProjectFiles = () => [libFile, ...tests, ...logic.slice(1), ...core.slice(1, core.length - 1)].map(f => f.path);
623                    const expectedProgramFiles = () => [tests[1].path, libFile.path, coreIndexDts, coreAnotherModuleDts, logicIndexDts];
624
625                    function createSolutionAndWatchMode() {
626                        return createSolutionAndWatchModeOfProject(allFiles, projectsLocation, `${project}/${SubProject.tests}`, tests[0].path, getOutputFileStamps);
627                    }
628
629                    function createSolutionAndService() {
630                        return createSolutionAndServiceOfProject(allFiles, projectsLocation, `${project}/${SubProject.tests}`, tests[1].path, getOutputFileStamps);
631                    }
632
633                    function verifyWatches(host: TsBuildWatchSystem, withTsserver?: boolean) {
634                        verifyWatchesOfProject(
635                            host,
636                            withTsserver ?
637                                [...core.slice(0, core.length - 1), ...logic, tests[0], libFile].map(f => f.path.toLowerCase()) :
638                                [core[0], logic[0], ...tests, libFile].map(f => f.path).concat([coreIndexDts, coreAnotherModuleDts, logicIndexDts].map(f => f.toLowerCase())),
639                            expectedWatchedDirectoriesRecursive
640                        );
641                    }
642
643                    function verifyScenario(
644                        edit: (host: TsBuildWatchSystem, solutionBuilder: SolutionBuilder<EmitAndSemanticDiagnosticsBuilderProgram>) => void,
645                        expectedProgramFilesAfterEdit: () => readonly string[],
646                        expectedProjectFilesAfterEdit: () => readonly string[]
647                    ) {
648                        it("with tsc-watch", () => {
649                            const { host, solutionBuilder, watch } = createSolutionAndWatchMode();
650
651                            edit(host, solutionBuilder);
652
653                            host.checkTimeoutQueueLengthAndRun(1);
654                            checkOutputErrorsIncremental(host, emptyArray);
655                            checkProgramActualFiles(watch.getCurrentProgram().getProgram(), expectedProgramFilesAfterEdit());
656
657                        });
658
659                        it("with tsserver", () => {
660                            const { host, solutionBuilder, service } = createSolutionAndService();
661
662                            edit(host, solutionBuilder);
663
664                            host.checkTimeoutQueueLengthAndRun(2);
665                            checkProjectActualFiles(service, tests[0].path, expectedProjectFilesAfterEdit());
666                        });
667                    }
668
669                    describe("verifies dependencies and watches", () => {
670                        it("with tsc-watch", () => {
671                            const { host, watch } = createSolutionAndWatchMode();
672                            verifyWatches(host);
673                            verifyDependencies(watch, coreIndexDts, [coreIndexDts]);
674                            verifyDependencies(watch, coreAnotherModuleDts, [coreAnotherModuleDts]);
675                            verifyDependencies(watch, logicIndexDts, [logicIndexDts, coreAnotherModuleDts]);
676                            verifyDependencies(watch, tests[1].path, expectedProgramFiles().filter(f => f !== libFile.path));
677                        });
678
679                        it("with tsserver", () => {
680                            const { host } = createSolutionAndService();
681                            verifyWatches(host, /*withTsserver*/ true);
682                        });
683                    });
684
685                    describe("local edit in ts file, result in watch compilation because logic.d.ts is written", () => {
686                        verifyScenario((host, solutionBuilder) => {
687                            host.writeFile(logic[1].path, `${logic[1].content}
688function foo() {
689}`);
690                            solutionBuilder.invalidateProject(logic[0].path.toLowerCase() as ResolvedConfigFilePath);
691                            solutionBuilder.buildNextInvalidatedProject();
692
693                            // not ideal, but currently because of d.ts but no new file is written
694                            // There will be timeout queued even though file contents are same
695                        }, expectedProgramFiles, expectedProjectFiles);
696                    });
697
698                    describe("non local edit in ts file, rebuilds in watch compilation", () => {
699                        verifyScenario((host, solutionBuilder) => {
700                            host.writeFile(logic[1].path, `${logic[1].content}
701export function gfoo() {
702}`);
703                            solutionBuilder.invalidateProject(logic[0].path.toLowerCase() as ResolvedConfigFilePath);
704                            solutionBuilder.buildNextInvalidatedProject();
705                        }, expectedProgramFiles, expectedProjectFiles);
706                    });
707
708                    describe("change in project reference config file builds correctly", () => {
709                        verifyScenario((host, solutionBuilder) => {
710                            host.writeFile(logic[0].path, JSON.stringify({
711                                compilerOptions: { composite: true, declaration: true, declarationDir: "decls" },
712                                references: [{ path: "../core" }]
713                            }));
714                            solutionBuilder.invalidateProject(logic[0].path.toLowerCase() as ResolvedConfigFilePath, ConfigFileProgramReloadLevel.Full);
715                            solutionBuilder.buildNextInvalidatedProject();
716                        }, () => [tests[1].path, libFile.path, coreIndexDts, coreAnotherModuleDts, projectFilePath(SubProject.logic, "decls/index.d.ts")], expectedProjectFiles);
717                    });
718                });
719
720                describe("on transitive references", () => {
721                    const project = "transitiveReferences";
722                    const aTsFile = getFileFromProject(project, "a.ts");
723                    const bTsFile = getFileFromProject(project, "b.ts");
724                    const cTsFile = getFileFromProject(project, "c.ts");
725                    const aTsconfigFile = getFileFromProject(project, "tsconfig.a.json");
726                    const bTsconfigFile = getFileFromProject(project, "tsconfig.b.json");
727                    const cTsconfigFile = getFileFromProject(project, "tsconfig.c.json");
728                    const refs = getFileFromProject(project, "refs/a.d.ts");
729
730                    function getRootFile(multiFolder: boolean, fileFromDisk: File, multiFolderPath: string): File {
731                        return multiFolder ? {
732                            path: getFilePathInProject(project, multiFolderPath),
733                            content: fileFromDisk.content
734                                // Replace the relative imports
735                                .replace("./", "../")
736                        } : fileFromDisk;
737                    }
738
739                    function dtsFile(extensionLessFile: string) {
740                        return getFilePathInProject(project, `${extensionLessFile}.d.ts`);
741                    }
742
743                    function jsFile(extensionLessFile: string) {
744                        return getFilePathInProject(project, `${extensionLessFile}.js`);
745                    }
746
747                    function verifyWatchState(
748                        host: TsBuildWatchSystem,
749                        watch: Watch,
750                        expectedProgramFiles: readonly string[],
751                        expectedWatchedFiles: readonly string[],
752                        expectedWatchedDirectoriesRecursive: readonly string[],
753                        dependencies: readonly [string, readonly string[]][],
754                        expectedWatchedDirectories?: readonly string[]) {
755                        checkProgramActualFiles(watch.getCurrentProgram().getProgram(), expectedProgramFiles);
756                        verifyWatchesOfProject(host, expectedWatchedFiles, expectedWatchedDirectoriesRecursive, expectedWatchedDirectories);
757                        for (const [file, deps] of dependencies) {
758                            verifyDependencies(watch, file, deps);
759                        }
760                    }
761
762                    function getTsConfigFile(multiFolder: boolean, fileFromDisk: File, folder: string): File {
763                        if (!multiFolder) return fileFromDisk;
764
765                        return {
766                            path: getFilePathInProject(project, `${folder}/tsconfig.json`),
767                            content: fileFromDisk.content
768                                // Replace files array
769                                .replace(`${folder}.ts`, "index.ts")
770                                // Replace path mappings
771                                .replace("./*", "../*")
772                                .replace("./refs", "../refs")
773                                // Replace references
774                                .replace("tsconfig.a.json", "../a")
775                                .replace("tsconfig.b.json", "../b")
776                        };
777                    }
778
779                    // function writeFile(file: File) {
780                    //     Harness.IO.writeFile(file.path.replace(projectsLocation, "c:/temp"), file.content);
781                    // }
782
783                    function verifyTransitiveReferences(multiFolder: boolean) {
784                        const aTs = getRootFile(multiFolder, aTsFile, "a/index.ts");
785                        const bTs = getRootFile(multiFolder, bTsFile, "b/index.ts");
786                        const cTs = getRootFile(multiFolder, cTsFile, "c/index.ts");
787
788                        const configToBuild = multiFolder ? "c/tsconfig.json" : "tsconfig.c.json";
789                        const aTsconfig = getTsConfigFile(multiFolder, aTsconfigFile, "a");
790                        const bTsconfig = getTsConfigFile(multiFolder, bTsconfigFile, "b");
791                        const cTsconfig = getTsConfigFile(multiFolder, cTsconfigFile, "c");
792
793                        // if (multiFolder) {
794                        //     writeFile(aTs);
795                        //     writeFile(bTs);
796                        //     writeFile(cTs);
797                        //     writeFile(aTsconfig);
798                        //     writeFile(bTsconfig);
799                        //     writeFile(cTsconfig);
800                        // }
801
802                        const allFiles = [libFile, aTs, bTs, cTs, aTsconfig, bTsconfig, cTsconfig, refs];
803                        const aDts = dtsFile(multiFolder ? "a/index" : "a"), bDts = dtsFile(multiFolder ? "b/index" : "b");
804                        const expectedFiles = [jsFile(multiFolder ? "a/index" : "a"), aDts, jsFile(multiFolder ? "b/index" : "b"), bDts, jsFile(multiFolder ? "c/index" : "c")];
805                        const expectedProgramFiles = [cTs.path, libFile.path, aDts, refs.path, bDts];
806                        const expectedProjectFiles = [cTs.path, libFile.path, aTs.path, refs.path, bTs.path];
807                        const expectedWatchedFiles = expectedProgramFiles.concat(cTsconfig.path, bTsconfig.path, aTsconfig.path).map(s => s.toLowerCase());
808                        const expectedProjectWatchedFiles = expectedProjectFiles.concat(cTsconfig.path, bTsconfig.path, aTsconfig.path).map(s => s.toLowerCase());
809                        const expectedWatchedDirectories = multiFolder ? [
810                            getProjectPath(project).toLowerCase() // watches for directories created for resolution of b
811                        ] : emptyArray;
812                        const nrefsPath = multiFolder ? ["../nrefs/*"] : ["./nrefs/*"];
813                        const expectedWatchedDirectoriesRecursive = [
814                            ...(multiFolder ? [
815                                getFilePathInProject(project, "a"), // Failed to package json
816                                getFilePathInProject(project, "b"), // Failed to package json
817                            ] : []),
818                            getFilePathInProject(project, "refs"), // Failed lookup since refs/a.ts does not exist
819                            ...projectSystem.getTypeRootsFromLocation(multiFolder ? getFilePathInProject(project, "c") : getProjectPath(project))
820                        ].map(s => s.toLowerCase());
821
822                        const defaultDependencies: readonly [string, readonly string[]][] = [
823                            [aDts, [aDts]],
824                            [bDts, [bDts, aDts]],
825                            [refs.path, [refs.path]],
826                            [cTs.path, [cTs.path, refs.path, bDts, aDts]]
827                        ];
828
829                        function createSolutionAndWatchMode() {
830                            return createSolutionAndWatchModeOfProject(allFiles, getProjectPath(project), configToBuild, configToBuild, getOutputFileStamps);
831                        }
832
833                        function createSolutionAndService() {
834                            return createSolutionAndServiceOfProject(allFiles, getProjectPath(project), configToBuild, cTs.path, getOutputFileStamps);
835                        }
836
837                        function getOutputFileStamps(host: TsBuildWatchSystem) {
838                            return expectedFiles.map(file => transformOutputToOutputFileStamp(file, host));
839                        }
840
841                        function verifyProgram(host: TsBuildWatchSystem, watch: Watch) {
842                            verifyWatchState(host, watch, expectedProgramFiles, expectedWatchedFiles, expectedWatchedDirectoriesRecursive, defaultDependencies, expectedWatchedDirectories);
843                        }
844
845                        function verifyProject(host: TsBuildWatchSystem, service: projectSystem.TestProjectService, orphanInfos?: readonly string[]) {
846                            verifyServerState({ host, service, expectedProjectFiles, expectedProjectWatchedFiles, expectedWatchedDirectoriesRecursive, orphanInfos });
847                        }
848
849                        interface VerifyServerState {
850                            host: TsBuildWatchSystem;
851                            service: projectSystem.TestProjectService;
852                            expectedProjectFiles: readonly string[];
853                            expectedProjectWatchedFiles: readonly string[];
854                            expectedWatchedDirectoriesRecursive: readonly string[];
855                            orphanInfos?: readonly string[];
856                        }
857                        function verifyServerState({ host, service, expectedProjectFiles, expectedProjectWatchedFiles, expectedWatchedDirectoriesRecursive, orphanInfos }: VerifyServerState) {
858                            checkProjectActualFiles(service, cTsconfig.path, expectedProjectFiles.concat(cTsconfig.path));
859                            const watchedFiles = expectedProjectWatchedFiles.filter(f => f !== cTs.path.toLowerCase());
860                            const actualOrphan = arrayFrom(mapDefinedIterator(
861                                service.filenameToScriptInfo.values(),
862                                v => v.containingProjects.length === 0 ? v.fileName : undefined
863                            ));
864                            assert.equal(actualOrphan.length, orphanInfos ? orphanInfos.length : 0, `Orphans found: ${JSON.stringify(actualOrphan, /*replacer*/ undefined, " ")}`);
865                            if (orphanInfos && orphanInfos.length) {
866                                for (const orphan of orphanInfos) {
867                                    const info = service.getScriptInfoForPath(orphan as Path);
868                                    assert.isDefined(info, `${orphan} expected to be present. Actual: ${JSON.stringify(actualOrphan, /*replacer*/ undefined, " ")}`);
869                                    assert.equal(info!.containingProjects.length, 0);
870                                    watchedFiles.push(orphan);
871                                }
872                            }
873                            verifyWatchesOfProject(host, watchedFiles, expectedWatchedDirectoriesRecursive, expectedWatchedDirectories);
874                        }
875
876                        interface VerifyScenario {
877                            edit: (host: TsBuildWatchSystem, solutionBuilder: SolutionBuilder<EmitAndSemanticDiagnosticsBuilderProgram>) => void;
878                            schedulesFailedWatchUpdate?: boolean;
879                            expectedEditErrors: readonly string[];
880                            expectedProgramFiles: readonly string[];
881                            expectedProjectFiles: readonly string[];
882                            expectedWatchedFiles: readonly string[];
883                            expectedProjectWatchedFiles: readonly string[];
884                            expectedWatchedDirectoriesRecursive: readonly string[];
885                            dependencies: readonly [string, readonly string[]][];
886                            revert?: (host: TsBuildWatchSystem) => void;
887                            orphanInfosAfterEdit?: readonly string[];
888                            orphanInfosAfterRevert?: readonly string[];
889                        }
890                        function verifyScenario({ edit, schedulesFailedWatchUpdate, expectedEditErrors, expectedProgramFiles, expectedProjectFiles, expectedWatchedFiles, expectedProjectWatchedFiles, expectedWatchedDirectoriesRecursive, dependencies, revert, orphanInfosAfterEdit, orphanInfosAfterRevert }: VerifyScenario) {
891                            it("with tsc-watch", () => {
892                                const { host, solutionBuilder, watch } = createSolutionAndWatchMode();
893
894                                edit(host, solutionBuilder);
895
896                                host.checkTimeoutQueueLengthAndRun(schedulesFailedWatchUpdate ? 2 : 1);
897                                checkOutputErrorsIncremental(host, expectedEditErrors);
898                                verifyWatchState(host, watch, expectedProgramFiles, expectedWatchedFiles, expectedWatchedDirectoriesRecursive, dependencies, expectedWatchedDirectories);
899
900                                if (revert) {
901                                    revert(host);
902
903                                    host.checkTimeoutQueueLengthAndRun(schedulesFailedWatchUpdate ? 2 : 1);
904                                    checkOutputErrorsIncremental(host, emptyArray);
905                                    verifyProgram(host, watch);
906                                }
907                            });
908
909                            if (!multiFolder) return; // With side by side file open is in inferred project without any settings
910
911                            it("with tsserver", () => {
912                                const { host, solutionBuilder, service } = createSolutionAndService();
913
914                                edit(host, solutionBuilder);
915
916                                host.checkTimeoutQueueLengthAndRun(schedulesFailedWatchUpdate ? 3 : 2);
917                                verifyServerState({ host, service, expectedProjectFiles, expectedProjectWatchedFiles, expectedWatchedDirectoriesRecursive, orphanInfos: orphanInfosAfterEdit });
918
919                                if (revert) {
920                                    revert(host);
921
922                                    host.checkTimeoutQueueLengthAndRun(schedulesFailedWatchUpdate ? 3 : 2);
923                                    verifyProject(host, service, orphanInfosAfterRevert);
924                                }
925                            });
926                        }
927
928                        describe("verifies dependencies and watches", () => {
929                            // Initial build
930                            it("with tsc-watch", () => {
931                                const { host, watch } = createSolutionAndWatchMode();
932                                verifyProgram(host, watch);
933                            });
934                            if (!multiFolder) return;
935                            it("with tsserver", () => {
936                                const { host, service } = createSolutionAndService();
937                                verifyProject(host, service);
938                            });
939                        });
940
941                        describe("non local edit updates the program and watch correctly", () => {
942                            verifyScenario({
943                                edit: (host, solutionBuilder) => {
944                                    // edit
945                                    host.writeFile(bTs.path, `${bTs.content}\nexport function gfoo() {\n}`);
946                                    solutionBuilder.invalidateProject((bTsconfig.path.toLowerCase() as ResolvedConfigFilePath));
947                                    solutionBuilder.buildNextInvalidatedProject();
948                                },
949                                expectedEditErrors: emptyArray,
950                                expectedProgramFiles,
951                                expectedProjectFiles,
952                                expectedWatchedFiles,
953                                expectedProjectWatchedFiles,
954                                expectedWatchedDirectoriesRecursive,
955                                dependencies: defaultDependencies
956                            });
957                        });
958
959                        describe("edit on config file", () => {
960                            const nrefReplacer = (f: string) => f.replace("refs", "nrefs");
961                            const nrefs: File = {
962                                path: getFilePathInProject(project, "nrefs/a.d.ts"),
963                                content: refs.content
964                            };
965                            verifyScenario({
966                                edit: host => {
967                                    const cTsConfigJson = JSON.parse(cTsconfig.content);
968                                    host.ensureFileOrFolder(nrefs);
969                                    cTsConfigJson.compilerOptions.paths = { "@ref/*": nrefsPath };
970                                    host.writeFile(cTsconfig.path, JSON.stringify(cTsConfigJson));
971                                },
972                                expectedEditErrors: emptyArray,
973                                expectedProgramFiles: expectedProgramFiles.map(nrefReplacer),
974                                expectedProjectFiles: expectedProjectFiles.map(nrefReplacer),
975                                expectedWatchedFiles: expectedWatchedFiles.map(nrefReplacer),
976                                expectedProjectWatchedFiles: expectedProjectWatchedFiles.map(nrefReplacer),
977                                expectedWatchedDirectoriesRecursive: expectedWatchedDirectoriesRecursive.map(nrefReplacer),
978                                dependencies: [
979                                    [aDts, [aDts]],
980                                    [bDts, [bDts, aDts]],
981                                    [nrefs.path, [nrefs.path]],
982                                    [cTs.path, [cTs.path, nrefs.path, bDts, aDts]]
983                                ],
984                                // revert the update
985                                revert: host => host.writeFile(cTsconfig.path, cTsconfig.content),
986                                // AfterEdit:: Extra watched files on server since the script infos arent deleted till next file open
987                                orphanInfosAfterEdit: [refs.path.toLowerCase()],
988                                // AfterRevert:: Extra watched files on server since the script infos arent deleted till next file open
989                                orphanInfosAfterRevert: [nrefs.path.toLowerCase()]
990                            });
991                        });
992
993                        describe("edit in referenced config file", () => {
994                            const nrefs: File = {
995                                path: getFilePathInProject(project, "nrefs/a.d.ts"),
996                                content: "export declare class A {}"
997                            };
998                            const expectedProgramFiles = [cTs.path, bDts, nrefs.path, refs.path, libFile.path];
999                            const expectedProjectFiles = [cTs.path, bTs.path, nrefs.path, refs.path, libFile.path];
1000                            const [, ...expectedWatchedDirectoriesRecursiveWithoutA] = expectedWatchedDirectoriesRecursive; // Not looking in a folder for resolution in multi folder scenario
1001                            verifyScenario({
1002                                edit: host => {
1003                                    const bTsConfigJson = JSON.parse(bTsconfig.content);
1004                                    host.ensureFileOrFolder(nrefs);
1005                                    bTsConfigJson.compilerOptions.paths = { "@ref/*": nrefsPath };
1006                                    host.writeFile(bTsconfig.path, JSON.stringify(bTsConfigJson));
1007                                },
1008                                expectedEditErrors: emptyArray,
1009                                expectedProgramFiles,
1010                                expectedProjectFiles,
1011                                expectedWatchedFiles: expectedProgramFiles.concat(cTsconfig.path, bTsconfig.path, aTsconfig.path).map(s => s.toLowerCase()),
1012                                expectedProjectWatchedFiles: expectedProjectFiles.concat(cTsconfig.path, bTsconfig.path, aTsconfig.path).map(s => s.toLowerCase()),
1013                                expectedWatchedDirectoriesRecursive: (multiFolder ? expectedWatchedDirectoriesRecursiveWithoutA : expectedWatchedDirectoriesRecursive).concat(getFilePathInProject(project, "nrefs").toLowerCase()),
1014                                dependencies: [
1015                                    [nrefs.path, [nrefs.path]],
1016                                    [bDts, [bDts, nrefs.path]],
1017                                    [refs.path, [refs.path]],
1018                                    [cTs.path, [cTs.path, refs.path, bDts, nrefs.path]],
1019                                ],
1020                                // revert the update
1021                                revert: host => host.writeFile(bTsconfig.path, bTsconfig.content),
1022                                // AfterEdit:: Extra watched files on server since the script infos arent deleted till next file open
1023                                orphanInfosAfterEdit: [aTs.path.toLowerCase()],
1024                                // AfterRevert:: Extra watched files on server since the script infos arent deleted till next file open
1025                                orphanInfosAfterRevert: [nrefs.path.toLowerCase()]
1026                            });
1027                        });
1028
1029                        describe("deleting referenced config file", () => {
1030                            const expectedProgramFiles = [cTs.path, bTs.path, refs.path, libFile.path];
1031                            const expectedWatchedFiles = expectedProgramFiles.concat(cTsconfig.path, bTsconfig.path).map(s => s.toLowerCase());
1032                            const [, ...expectedWatchedDirectoriesRecursiveWithoutA] = expectedWatchedDirectoriesRecursive; // Not looking in a folder for resolution in multi folder scenario
1033                            // Resolutions should change now
1034                            // Should map to b.ts instead with options from our own config
1035                            verifyScenario({
1036                                edit: host => host.deleteFile(bTsconfig.path),
1037                                schedulesFailedWatchUpdate: multiFolder,
1038                                expectedEditErrors: [
1039                                    `${multiFolder ? "c/tsconfig.json" : "tsconfig.c.json"}(9,21): error TS6053: File '/user/username/projects/transitiveReferences/${multiFolder ? "b" : "tsconfig.b.json"}' not found.\n`
1040                                ],
1041                                expectedProgramFiles,
1042                                expectedProjectFiles: expectedProgramFiles,
1043                                expectedWatchedFiles,
1044                                expectedProjectWatchedFiles: expectedWatchedFiles,
1045                                expectedWatchedDirectoriesRecursive: multiFolder ? expectedWatchedDirectoriesRecursiveWithoutA : expectedWatchedDirectoriesRecursive,
1046                                dependencies: [
1047                                    [bTs.path, [bTs.path, refs.path]],
1048                                    [refs.path, [refs.path]],
1049                                    [cTs.path, [cTs.path, refs.path, bTs.path]],
1050                                ],
1051                                // revert the update
1052                                revert: host => host.writeFile(bTsconfig.path, bTsconfig.content),
1053                                // AfterEdit:: Extra watched files on server since the script infos arent deleted till next file open
1054                                orphanInfosAfterEdit: [aTs.path.toLowerCase(), aTsconfig.path.toLowerCase()],
1055                            });
1056                        });
1057
1058                        describe("deleting transitively referenced config file", () => {
1059                            verifyScenario({
1060                                edit: host => host.deleteFile(aTsconfig.path),
1061                                schedulesFailedWatchUpdate: multiFolder,
1062                                expectedEditErrors: [
1063                                    `${multiFolder ? "b/tsconfig.json" : "tsconfig.b.json"}(10,21): error TS6053: File '/user/username/projects/transitiveReferences/${multiFolder ? "a" : "tsconfig.a.json"}' not found.\n`
1064                                ],
1065                                expectedProgramFiles: expectedProgramFiles.map(s => s.replace(aDts, aTs.path)),
1066                                expectedProjectFiles,
1067                                expectedWatchedFiles: expectedWatchedFiles.map(s => s.replace(aDts.toLowerCase(), aTs.path.toLocaleLowerCase())),
1068                                expectedProjectWatchedFiles,
1069                                expectedWatchedDirectoriesRecursive,
1070                                dependencies: [
1071                                    [aTs.path, [aTs.path]],
1072                                    [bDts, [bDts, aTs.path]],
1073                                    [refs.path, [refs.path]],
1074                                    [cTs.path, [cTs.path, refs.path, bDts, aTs.path]],
1075                                ],
1076                                // revert the update
1077                                revert: host => host.writeFile(aTsconfig.path, aTsconfig.content),
1078                            });
1079                        });
1080                    }
1081
1082                    describe("when config files are side by side", () => {
1083                        verifyTransitiveReferences(/*multiFolder*/ false);
1084
1085                        it("when referenced project uses different module resolution", () => {
1086                            const bTs: File = {
1087                                path: bTsFile.path,
1088                                content: `import {A} from "a";export const b = new A();`
1089                            };
1090                            const bTsconfig: File = {
1091                                path: bTsconfigFile.path,
1092                                content: JSON.stringify({
1093                                    compilerOptions: { composite: true, moduleResolution: "classic" },
1094                                    files: ["b.ts"],
1095                                    references: [{ path: "tsconfig.a.json" }]
1096                                })
1097                            };
1098                            const allFiles = [libFile, aTsFile, bTs, cTsFile, aTsconfigFile, bTsconfig, cTsconfigFile, refs];
1099                            const aDts = dtsFile("a"), bDts = dtsFile("b");
1100                            const expectedFiles = [jsFile("a"), aDts, jsFile("b"), bDts, jsFile("c")];
1101                            const expectedProgramFiles = [cTsFile.path, libFile.path, aDts, refs.path, bDts];
1102                            const expectedWatchedFiles = expectedProgramFiles.concat(cTsconfigFile.path, bTsconfigFile.path, aTsconfigFile.path).map(s => s.toLowerCase());
1103                            const expectedWatchedDirectoriesRecursive = [
1104                                getFilePathInProject(project, "refs"), // Failed lookup since refs/a.ts does not exist
1105                                ...projectSystem.getTypeRootsFromLocation(getProjectPath(project))
1106                            ].map(s => s.toLowerCase());
1107
1108                            const defaultDependencies: readonly [string, readonly string[]][] = [
1109                                [aDts, [aDts]],
1110                                [bDts, [bDts, aDts]],
1111                                [refs.path, [refs.path]],
1112                                [cTsFile.path, [cTsFile.path, refs.path, bDts, aDts]]
1113                            ];
1114                            function getOutputFileStamps(host: TsBuildWatchSystem) {
1115                                return expectedFiles.map(file => transformOutputToOutputFileStamp(file, host));
1116                            }
1117                            const { host, watch } = createSolutionAndWatchModeOfProject(allFiles, getProjectPath(project), "tsconfig.c.json", "tsconfig.c.json", getOutputFileStamps);
1118                            verifyWatchState(host, watch, expectedProgramFiles, expectedWatchedFiles, expectedWatchedDirectoriesRecursive, defaultDependencies);
1119                        });
1120                    });
1121                    describe("when config files are in side by side folders", () => {
1122                        verifyTransitiveReferences(/*multiFolder*/ true);
1123                    });
1124                });
1125            });
1126        });
1127
1128        verifyTscWatch({
1129            scenario,
1130            subScenario: "incremental updates in verbose mode",
1131            commandLineArgs: ["-b", "-w", `${project}/${SubProject.tests}`, "-verbose"],
1132            sys: () => createWatchedSystem(allFiles, { currentDirectory: projectsLocation }),
1133            changes: [
1134                {
1135                    caption: "Make non dts change",
1136                    change: sys => sys.writeFile(logic[1].path, `${logic[1].content}
1137function someFn() { }`),
1138                    timeouts: sys => {
1139                        sys.checkTimeoutQueueLengthAndRun(1); // build logic
1140                        sys.checkTimeoutQueueLengthAndRun(1); // build tests
1141                    },
1142                },
1143                {
1144                    caption: "Make dts change",
1145                    change: sys => sys.writeFile(logic[1].path, `${logic[1].content}
1146export function someFn() { }`),
1147                    timeouts: sys => {
1148                        sys.checkTimeoutQueueLengthAndRun(1); // build logic
1149                        sys.checkTimeoutQueueLengthAndRun(1); // build tests
1150                    },
1151                }
1152            ],
1153        });
1154
1155        verifyTscWatch({
1156            scenario,
1157            subScenario: "works when noUnusedParameters changes to false",
1158            commandLineArgs: ["-b", "-w"],
1159            sys: () => {
1160                const index: File = {
1161                    path: `${projectRoot}/index.ts`,
1162                    content: `const fn = (a: string, b: string) => b;`
1163                };
1164                const configFile: File = {
1165                    path: `${projectRoot}/tsconfig.json`,
1166                    content: JSON.stringify({
1167                        compilerOptions: {
1168                            noUnusedParameters: true
1169                        }
1170                    })
1171                };
1172                return createWatchedSystem([index, configFile, libFile], { currentDirectory: projectRoot });
1173            },
1174            changes: [
1175                {
1176                    caption: "Change tsconfig to set noUnusedParameters to false",
1177                    change: sys => sys.writeFile(`${projectRoot}/tsconfig.json`, JSON.stringify({
1178                        compilerOptions: {
1179                            noUnusedParameters: false
1180                        }
1181                    })),
1182                    timeouts: runQueuedTimeoutCallbacks,
1183                },
1184            ]
1185        });
1186
1187        verifyTscWatch({
1188            scenario,
1189            subScenario: "should not trigger recompilation because of program emit",
1190            commandLineArgs: ["-b", "-w", `${project}/${SubProject.core}`, "-verbose"],
1191            sys: () => createWatchedSystem([libFile, ...core], { currentDirectory: projectsLocation }),
1192            changes: [
1193                noopChange,
1194                {
1195                    caption: "Add new file",
1196                    change: sys => sys.writeFile(`${project}/${SubProject.core}/file3.ts`, `export const y = 10;`),
1197                    timeouts: checkSingleTimeoutQueueLengthAndRun
1198                },
1199                noopChange,
1200            ]
1201        });
1202
1203        verifyTscWatch({
1204            scenario,
1205            subScenario: "should not trigger recompilation because of program emit with outDir specified",
1206            commandLineArgs: ["-b", "-w", `${project}/${SubProject.core}`, "-verbose"],
1207            sys: () => {
1208                const [coreConfig, ...rest] = core;
1209                const newCoreConfig: File = { path: coreConfig.path, content: JSON.stringify({ compilerOptions: { composite: true, outDir: "outDir" } }) };
1210                return createWatchedSystem([libFile, newCoreConfig, ...rest], { currentDirectory: projectsLocation });
1211            },
1212            changes: [
1213                noopChange,
1214                {
1215                    caption: "Add new file",
1216                    change: sys => sys.writeFile(`${project}/${SubProject.core}/file3.ts`, `export const y = 10;`),
1217                    timeouts: checkSingleTimeoutQueueLengthAndRun
1218                },
1219                noopChange
1220            ]
1221        });
1222
1223        verifyTscWatch({
1224            scenario,
1225            subScenario: "works with extended source files",
1226            commandLineArgs: ["-b", "-w", "-v", "project1.tsconfig.json", "project2.tsconfig.json"],
1227            sys: () => {
1228                const alphaExtendedConfigFile: File = {
1229                    path: "/a/b/alpha.tsconfig.json",
1230                    content: "{}"
1231                };
1232                const project1Config: File = {
1233                    path: "/a/b/project1.tsconfig.json",
1234                    content: JSON.stringify({
1235                        extends: "./alpha.tsconfig.json",
1236                        compilerOptions: {
1237                            composite: true,
1238                        },
1239                        files: [commonFile1.path, commonFile2.path]
1240                    })
1241                };
1242                const bravoExtendedConfigFile: File = {
1243                    path: "/a/b/bravo.tsconfig.json",
1244                    content: JSON.stringify({
1245                        extends: "./alpha.tsconfig.json"
1246                    })
1247                };
1248                const otherFile: File = {
1249                    path: "/a/b/other.ts",
1250                    content: "let z = 0;",
1251                };
1252                const project2Config: File = {
1253                    path: "/a/b/project2.tsconfig.json",
1254                    content: JSON.stringify({
1255                        extends: "./bravo.tsconfig.json",
1256                        compilerOptions: {
1257                            composite: true,
1258                        },
1259                        files: [otherFile.path]
1260                    })
1261                };
1262                return createWatchedSystem([
1263                    libFile,
1264                    alphaExtendedConfigFile, project1Config, commonFile1, commonFile2,
1265                    bravoExtendedConfigFile, project2Config, otherFile
1266                ], { currentDirectory: "/a/b" });
1267            },
1268            changes: [
1269                {
1270                    caption: "Modify alpha config",
1271                    change: sys => sys.writeFile("/a/b/alpha.tsconfig.json", JSON.stringify({
1272                        compilerOptions: { strict: true }
1273                    })),
1274                    timeouts: checkSingleTimeoutQueueLengthAndRun // Build project1
1275                },
1276                {
1277                    caption: "Build project 2",
1278                    change: noop,
1279                    timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout // Build project2
1280                },
1281                {
1282                    caption: "change bravo config",
1283                    change: sys => sys.writeFile("/a/b/bravo.tsconfig.json", JSON.stringify({
1284                        extends: "./alpha.tsconfig.json",
1285                        compilerOptions: { strict: false }
1286                    })),
1287                    timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout // Build project2
1288                },
1289                {
1290                    caption: "project 2 extends alpha",
1291                    change: sys => sys.writeFile("/a/b/project2.tsconfig.json", JSON.stringify({
1292                        extends: "./alpha.tsconfig.json",
1293                    })),
1294                    timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout // Build project2
1295                },
1296                {
1297                    caption: "update aplha config",
1298                    change: sys => sys.writeFile("/a/b/alpha.tsconfig.json", "{}"),
1299                    timeouts: checkSingleTimeoutQueueLengthAndRun, // build project1
1300                },
1301                {
1302                    caption: "Build project 2",
1303                    change: noop,
1304                    timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout // Build project2
1305                },
1306            ]
1307        });
1308
1309        verifyTscWatch({
1310            scenario,
1311            subScenario: "works correctly when project with extended config is removed",
1312            commandLineArgs: ["-b", "-w", "-v"],
1313            sys: () => {
1314                const alphaExtendedConfigFile: File = {
1315                    path: "/a/b/alpha.tsconfig.json",
1316                    content: JSON.stringify({
1317                        strict: true
1318                    })
1319                };
1320                const project1Config: File = {
1321                    path: "/a/b/project1.tsconfig.json",
1322                    content: JSON.stringify({
1323                        extends: "./alpha.tsconfig.json",
1324                        compilerOptions: {
1325                            composite: true,
1326                        },
1327                        files: [commonFile1.path, commonFile2.path]
1328                    })
1329                };
1330                const bravoExtendedConfigFile: File = {
1331                    path: "/a/b/bravo.tsconfig.json",
1332                    content: JSON.stringify({
1333                        strict: true
1334                    })
1335                };
1336                const otherFile: File = {
1337                    path: "/a/b/other.ts",
1338                    content: "let z = 0;",
1339                };
1340                const project2Config: File = {
1341                    path: "/a/b/project2.tsconfig.json",
1342                    content: JSON.stringify({
1343                        extends: "./bravo.tsconfig.json",
1344                        compilerOptions: {
1345                            composite: true,
1346                        },
1347                        files: [otherFile.path]
1348                    })
1349                };
1350                const configFile: File = {
1351                    path: "/a/b/tsconfig.json",
1352                    content: JSON.stringify({
1353                        references: [
1354                            {
1355                                path: "./project1.tsconfig.json",
1356                            },
1357                            {
1358                                path: "./project2.tsconfig.json",
1359                            },
1360                        ],
1361                        files: [],
1362                    })
1363                };
1364                return createWatchedSystem([
1365                    libFile, configFile,
1366                    alphaExtendedConfigFile, project1Config, commonFile1, commonFile2,
1367                    bravoExtendedConfigFile, project2Config, otherFile
1368                ], { currentDirectory: "/a/b" });
1369            },
1370            changes: [
1371                {
1372                    caption: "Remove project2 from base config",
1373                    change: sys => sys.modifyFile("/a/b/tsconfig.json", JSON.stringify({
1374                        references: [
1375                            {
1376                                path: "./project1.tsconfig.json",
1377                            },
1378                        ],
1379                        files: [],
1380                    })),
1381                    timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout,
1382                }
1383            ]
1384        });
1385    });
1386
1387    describe("unittests:: tsbuild:: watchMode:: with demo project", () => {
1388        const projectLocation = `${projectsLocation}/demo`;
1389        let coreFiles: File[];
1390        let animalFiles: File[];
1391        let zooFiles: File[];
1392        let solutionFile: File;
1393        let baseConfig: File;
1394        let allFiles: File[];
1395        before(() => {
1396            coreFiles = subProjectFiles("core", ["tsconfig.json", "utilities.ts"]);
1397            animalFiles = subProjectFiles("animals", ["tsconfig.json", "animal.ts", "dog.ts", "index.ts"]);
1398            zooFiles = subProjectFiles("zoo", ["tsconfig.json", "zoo.ts"]);
1399            solutionFile = projectFile("tsconfig.json");
1400            baseConfig = projectFile("tsconfig-base.json");
1401            allFiles = [...coreFiles, ...animalFiles, ...zooFiles, solutionFile, baseConfig, { path: libFile.path, content: libContent }];
1402        });
1403
1404        after(() => {
1405            coreFiles = undefined!;
1406            animalFiles = undefined!;
1407            zooFiles = undefined!;
1408            solutionFile = undefined!;
1409            baseConfig = undefined!;
1410            allFiles = undefined!;
1411        });
1412
1413        verifyTscWatch({
1414            scenario: "demo",
1415            subScenario: "updates with circular reference",
1416            commandLineArgs: ["-b", "-w", "-verbose"],
1417            sys: () => {
1418                const sys = createWatchedSystem(allFiles, { currentDirectory: projectLocation });
1419                sys.writeFile(coreFiles[0].path, coreFiles[0].content.replace(
1420                    "}",
1421                    `},
1422  "references": [
1423    {
1424      "path": "../zoo"
1425    }
1426  ]`
1427                ));
1428                return sys;
1429            },
1430            changes: [
1431                {
1432                    caption: "Fix error",
1433                    change: sys => sys.writeFile(coreFiles[0].path, coreFiles[0].content),
1434                    timeouts: sys => {
1435                        sys.checkTimeoutQueueLengthAndRun(1); // build core
1436                        sys.checkTimeoutQueueLengthAndRun(1); // build animals
1437                        sys.checkTimeoutQueueLengthAndRun(1); // build zoo
1438                        sys.checkTimeoutQueueLengthAndRun(1); // build solution
1439                        sys.checkTimeoutQueueLength(0);
1440                    },
1441                }
1442            ]
1443        });
1444
1445        verifyTscWatch({
1446            scenario: "demo",
1447            subScenario: "updates with bad reference",
1448            commandLineArgs: ["-b", "-w", "-verbose"],
1449            sys: () => {
1450                const sys = createWatchedSystem(allFiles, { currentDirectory: projectLocation });
1451                sys.writeFile(coreFiles[1].path, `import * as A from '../animals';
1452${coreFiles[1].content}`);
1453                return sys;
1454            },
1455            changes: [
1456                {
1457                    caption: "Prepend a line",
1458                    change: sys => sys.writeFile(coreFiles[1].path, `
1459import * as A from '../animals';
1460${coreFiles[1].content}`),
1461                    // build core
1462                    timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout,
1463                }
1464            ]
1465        });
1466
1467        function subProjectFiles(subProject: string, fileNames: readonly string[]): File[] {
1468            return fileNames.map(file => projectFile(`${subProject}/${file}`));
1469        }
1470
1471        function projectFile(fileName: string): File {
1472            return getFileFromProject("demo", fileName);
1473        }
1474    });
1475
1476    describe("unittests:: tsbuild:: watchMode:: with noEmitOnError", () => {
1477        function change(caption: string, content: string): TscWatchCompileChange {
1478            return {
1479                caption,
1480                change: sys => sys.writeFile(`${projectsLocation}/noEmitOnError/src/main.ts`, content),
1481                // build project
1482                timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout,
1483            };
1484        }
1485
1486        const noChange: TscWatchCompileChange = {
1487            caption: "No change",
1488            change: sys => sys.writeFile(`${projectsLocation}/noEmitOnError/src/main.ts`, sys.readFile(`${projectsLocation}/noEmitOnError/src/main.ts`)!),
1489            // build project
1490            timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout,
1491        };
1492        verifyTscWatch({
1493            scenario: "noEmitOnError",
1494            subScenario: "does not emit any files on error",
1495            commandLineArgs: ["-b", "-w", "-verbose"],
1496            sys: () => createWatchedSystem(
1497                [
1498                    ...["tsconfig.json", "shared/types/db.ts", "src/main.ts", "src/other.ts"]
1499                        .map(f => getFileFromProject("noEmitOnError", f)),
1500                    { path: libFile.path, content: libContent }
1501                ],
1502                { currentDirectory: `${projectsLocation}/noEmitOnError` }
1503            ),
1504            changes: [
1505                noChange,
1506                change("Fix Syntax error", `import { A } from "../shared/types/db";
1507const a = {
1508    lastName: 'sdsd'
1509};`),
1510                change("Semantic Error", `import { A } from "../shared/types/db";
1511const a: string = 10;`),
1512                noChange,
1513                change("Fix Semantic Error", `import { A } from "../shared/types/db";
1514const a: string = "hello";`),
1515                noChange,
1516            ],
1517            baselineIncremental: true
1518        });
1519    });
1520
1521    describe("unittests:: tsbuild:: watchMode:: with reexport when referenced project reexports definitions from another file", () => {
1522        function build(sys: WatchedSystem) {
1523            sys.checkTimeoutQueueLengthAndRun(1); // build src/pure
1524            sys.checkTimeoutQueueLengthAndRun(1); // build src/main
1525            sys.checkTimeoutQueueLengthAndRun(1); // build src
1526            sys.checkTimeoutQueueLength(0);
1527        }
1528        verifyTscWatch({
1529            scenario: "reexport",
1530            subScenario: "Reports errors correctly",
1531            commandLineArgs: ["-b", "-w", "-verbose", "src"],
1532            sys: () => createWatchedSystem(
1533                [
1534                    ...[
1535                        "src/tsconfig.json",
1536                        "src/main/tsconfig.json", "src/main/index.ts",
1537                        "src/pure/tsconfig.json", "src/pure/index.ts", "src/pure/session.ts"
1538                    ]
1539                        .map(f => getFileFromProject("reexport", f)),
1540                    { path: libFile.path, content: libContent }
1541                ],
1542                { currentDirectory: `${projectsLocation}/reexport` }
1543            ),
1544            changes: [
1545                {
1546                    caption: "Introduce error",
1547                    change: sys => replaceFileText(sys, `${projectsLocation}/reexport/src/pure/session.ts`, "// ", ""),
1548                    timeouts: build,
1549                },
1550                {
1551                    caption: "Fix error",
1552                    change: sys => replaceFileText(sys, `${projectsLocation}/reexport/src/pure/session.ts`, "bar: ", "// bar: "),
1553                    timeouts: build
1554                }
1555            ]
1556        });
1557    });
1558
1559    describe("unittests:: tsbuild:: watchMode:: configFileErrors:: reports syntax errors in config file", () => {
1560        function build(sys: WatchedSystem) {
1561            sys.checkTimeoutQueueLengthAndRun(1); // build the project
1562            sys.checkTimeoutQueueLength(0);
1563        }
1564        verifyTscWatch({
1565            scenario: "configFileErrors",
1566            subScenario: "reports syntax errors in config file",
1567            sys: () => createWatchedSystem(
1568                [
1569                    { path: `${projectRoot}/a.ts`, content: "export function foo() { }" },
1570                    { path: `${projectRoot}/b.ts`, content: "export function bar() { }" },
1571                    {
1572                        path: `${projectRoot}/tsconfig.json`,
1573                        content: Utils.dedent`
1574{
1575    "compilerOptions": {
1576        "composite": true,
1577    },
1578    "files": [
1579        "a.ts"
1580        "b.ts"
1581    ]
1582}`
1583                    },
1584                    libFile
1585                ],
1586                { currentDirectory: projectRoot }
1587            ),
1588            commandLineArgs: ["--b", "-w"],
1589            changes: [
1590                {
1591                    caption: "reports syntax errors after change to config file",
1592                    change: sys => replaceFileText(sys, `${projectRoot}/tsconfig.json`, ",", `,
1593        "declaration": true,`),
1594                    timeouts: build,
1595                },
1596                {
1597                    caption: "reports syntax errors after change to ts file",
1598                    change: sys => replaceFileText(sys, `${projectRoot}/a.ts`, "foo", "fooBar"),
1599                    timeouts: build,
1600                },
1601                {
1602                    caption: "reports error when there is no change to tsconfig file",
1603                    change: sys => replaceFileText(sys, `${projectRoot}/tsconfig.json`, "", ""),
1604                    timeouts: build,
1605                },
1606                {
1607                    caption: "builds after fixing config file errors",
1608                    change: sys => sys.writeFile(`${projectRoot}/tsconfig.json`, JSON.stringify({
1609                        compilerOptions: { composite: true, declaration: true },
1610                        files: ["a.ts", "b.ts"]
1611                    })),
1612                    timeouts: build,
1613                }
1614            ]
1615        });
1616    });
1617}
1618