• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1namespace ts.projectSystem {
2    import Tsc_WatchDirectory = TestFSWithWatch.Tsc_WatchDirectory;
3    describe("unittests:: tsserver:: watchEnvironment:: tsserverProjectSystem watchDirectories implementation", () => {
4        function verifyCompletionListWithNewFileInSubFolder(tscWatchDirectory: Tsc_WatchDirectory) {
5            const projectFolder = "/a/username/project";
6            const projectSrcFolder = `${projectFolder}/src`;
7            const configFile: File = {
8                path: `${projectFolder}/tsconfig.json`,
9                content: JSON.stringify({
10                    watchOptions: {
11                        synchronousWatchDirectory: true
12                    }
13                })
14            };
15            const index: File = {
16                path: `${projectSrcFolder}/index.ts`,
17                content: `import {} from "./"`
18            };
19            const file1: File = {
20                path: `${projectSrcFolder}/file1.ts`,
21                content: ""
22            };
23
24            const files = [index, file1, configFile, libFile];
25            const fileNames = files.map(file => file.path);
26            // All closed files(files other than index), project folder, project/src folder and project/node_modules/@types folder
27            const expectedWatchedFiles = arrayToMap(fileNames.slice(1), s => s, () => 1);
28            const expectedWatchedDirectories = new Map<string, number>();
29            const mapOfDirectories = tscWatchDirectory === Tsc_WatchDirectory.NonRecursiveWatchDirectory ?
30                expectedWatchedDirectories :
31                tscWatchDirectory === Tsc_WatchDirectory.WatchFile ?
32                    expectedWatchedFiles :
33                    new Map();
34            // For failed resolution lookup and tsconfig files => cached so only watched only once
35            mapOfDirectories.set(projectFolder, 1);
36            // Through above recursive watches
37            mapOfDirectories.set(projectSrcFolder, 1);
38            // node_modules/@types folder
39            mapOfDirectories.set(`${projectFolder}/${nodeModulesAtTypes}`, 1);
40            const expectedCompletions = ["file1"];
41            const completionPosition = index.content.lastIndexOf('"');
42            const environmentVariables = new Map<string, string>();
43            environmentVariables.set("TSC_WATCHDIRECTORY", tscWatchDirectory);
44            const host = createServerHost(files, { environmentVariables });
45            const projectService = createProjectService(host);
46            projectService.openClientFile(index.path);
47
48            const project = Debug.checkDefined(projectService.configuredProjects.get(configFile.path));
49            verifyProjectAndCompletions();
50
51            // Add file2
52            const file2: File = {
53                path: `${projectSrcFolder}/file2.ts`,
54                content: ""
55            };
56            fileNames.push(file2.path);
57            expectedWatchedFiles.set(file2.path, 1);
58            expectedCompletions.push("file2");
59            host.writeFile(file2.path, file2.content);
60            host.runQueuedTimeoutCallbacks();
61            assert.equal(projectService.configuredProjects.get(configFile.path), project);
62            verifyProjectAndCompletions();
63
64            function verifyProjectAndCompletions() {
65                const completions = project.getLanguageService().getCompletionsAtPosition(index.path, completionPosition, { includeExternalModuleExports: false, includeInsertTextCompletions: false })!;
66                checkArray("Completion Entries", completions.entries.map(e => e.name), expectedCompletions);
67
68                checkWatchedDirectories(host, emptyArray, /*recursive*/ true);
69
70                checkWatchedFilesDetailed(host, expectedWatchedFiles);
71                checkWatchedDirectoriesDetailed(host, expectedWatchedDirectories, /*recursive*/ false);
72                checkProjectActualFiles(project, fileNames);
73            }
74        }
75
76        it("uses watchFile when file is added to subfolder, completion list has new file", () => {
77            verifyCompletionListWithNewFileInSubFolder(Tsc_WatchDirectory.WatchFile);
78        });
79
80        it("uses non recursive watchDirectory when file is added to subfolder, completion list has new file", () => {
81            verifyCompletionListWithNewFileInSubFolder(Tsc_WatchDirectory.NonRecursiveWatchDirectory);
82        });
83
84        it("uses dynamic polling when file is added to subfolder, completion list has new file", () => {
85            verifyCompletionListWithNewFileInSubFolder(Tsc_WatchDirectory.DynamicPolling);
86        });
87    });
88
89    describe("unittests:: tsserver:: watchEnvironment:: tsserverProjectSystem Watched recursive directories with windows style file system", () => {
90        function verifyWatchedDirectories(rootedPath: string, useProjectAtRoot: boolean) {
91            const root = useProjectAtRoot ? rootedPath : `${rootedPath}myfolder/allproject/`;
92            const configFile: File = {
93                path: root + "project/tsconfig.json",
94                content: "{}"
95            };
96            const file1: File = {
97                path: root + "project/file1.ts",
98                content: "let x = 10;"
99            };
100            const file2: File = {
101                path: root + "project/file2.ts",
102                content: "let y = 10;"
103            };
104            const files = [configFile, file1, file2, libFile];
105            const host = createServerHost(files, { windowsStyleRoot: "c:/" });
106            const projectService = createProjectService(host);
107            projectService.openClientFile(file1.path);
108            const project = projectService.configuredProjects.get(configFile.path)!;
109            assert.isDefined(project);
110            const winsowsStyleLibFilePath = "c:/" + libFile.path.substring(1);
111            checkProjectActualFiles(project, files.map(f => f === libFile ? winsowsStyleLibFilePath : f.path));
112            checkWatchedFiles(host, mapDefined(files, f => f === libFile ? winsowsStyleLibFilePath : f === file1 ? undefined : f.path));
113            checkWatchedDirectories(host, [], /*recursive*/ false);
114            checkWatchedDirectories(host, [
115                root + "project",
116                root + "project/node_modules/@types"
117            ].concat(useProjectAtRoot ? [] : [root + nodeModulesAtTypes]), /*recursive*/ true);
118        }
119
120        function verifyRootedDirectoryWatch(rootedPath: string) {
121            it("When project is in rootFolder of style c:/", () => {
122                verifyWatchedDirectories(rootedPath, /*useProjectAtRoot*/ true);
123            });
124
125            it("When files at some folder other than root", () => {
126                verifyWatchedDirectories(rootedPath, /*useProjectAtRoot*/ false);
127            });
128        }
129
130        describe("for rootFolder of style c:/", () => {
131            verifyRootedDirectoryWatch("c:/");
132        });
133
134        describe("for rootFolder of style c:/users/username", () => {
135            verifyRootedDirectoryWatch("c:/users/username/");
136        });
137    });
138
139    it(`unittests:: tsserver:: watchEnvironment:: tsserverProjectSystem recursive watch directory implementation does not watch files/directories in node_modules starting with "."`, () => {
140        const projectFolder = "/a/username/project";
141        const projectSrcFolder = `${projectFolder}/src`;
142        const configFile: File = {
143            path: `${projectFolder}/tsconfig.json`,
144            content: "{}"
145        };
146        const index: File = {
147            path: `${projectSrcFolder}/index.ts`,
148            content: `import {} from "file"`
149        };
150        const file1: File = {
151            path: `${projectSrcFolder}/file1.ts`,
152            content: ""
153        };
154        const nodeModulesExistingUnusedFile: File = {
155            path: `${projectFolder}/node_modules/someFile.d.ts`,
156            content: ""
157        };
158
159        const fileNames = [index, file1, configFile, libFile].map(file => file.path);
160        // All closed files(files other than index), project folder, project/src folder and project/node_modules/@types folder
161        const expectedWatchedFiles = arrayToMap(fileNames.slice(1), identity, () => 1);
162        const expectedWatchedDirectories = arrayToMap([projectFolder, projectSrcFolder, `${projectFolder}/${nodeModules}`, `${projectFolder}/${nodeModulesAtTypes}`], identity, () => 1);
163
164        const environmentVariables = new Map<string, string>();
165        environmentVariables.set("TSC_WATCHDIRECTORY", Tsc_WatchDirectory.NonRecursiveWatchDirectory);
166        const host = createServerHost([index, file1, configFile, libFile, nodeModulesExistingUnusedFile], { environmentVariables });
167        const projectService = createProjectService(host);
168        projectService.openClientFile(index.path);
169
170        const project = Debug.checkDefined(projectService.configuredProjects.get(configFile.path));
171        verifyProject();
172
173        const nodeModulesIgnoredFileFromIgnoreDirectory: File = {
174            path: `${projectFolder}/node_modules/.cache/someFile.d.ts`,
175            content: ""
176        };
177
178        const nodeModulesIgnoredFile: File = {
179            path: `${projectFolder}/node_modules/.cacheFile.ts`,
180            content: ""
181        };
182
183        const gitIgnoredFileFromIgnoreDirectory: File = {
184            path: `${projectFolder}/.git/someFile.d.ts`,
185            content: ""
186        };
187
188        const gitIgnoredFile: File = {
189            path: `${projectFolder}/.gitCache.d.ts`,
190            content: ""
191        };
192        const emacsIgnoredFileFromIgnoreDirectory: File = {
193            path: `${projectFolder}/src/.#field.ts`,
194            content: ""
195        };
196
197        [
198            nodeModulesIgnoredFileFromIgnoreDirectory,
199            nodeModulesIgnoredFile,
200            gitIgnoredFileFromIgnoreDirectory,
201            gitIgnoredFile,
202            emacsIgnoredFileFromIgnoreDirectory
203        ].forEach(ignoredEntity => {
204            host.ensureFileOrFolder(ignoredEntity);
205            host.checkTimeoutQueueLength(0);
206            verifyProject();
207        });
208
209        function verifyProject() {
210            checkWatchedDirectories(host, emptyArray, /*recursive*/ true);
211            checkWatchedFilesDetailed(host, expectedWatchedFiles);
212            checkWatchedDirectoriesDetailed(host, expectedWatchedDirectories, /*recursive*/ false);
213            checkProjectActualFiles(project, fileNames);
214        }
215    });
216
217    describe("unittests:: tsserver:: watchEnvironment:: tsserverProjectSystem watching files with network style paths", () => {
218        function verifyFilePathStyle(path: string) {
219            const windowsStyleRoot = path.substr(0, getRootLength(path));
220            const host = createServerHost(
221                [libFile, { path, content: "const x = 10" }],
222                { windowsStyleRoot }
223            );
224            const service = createProjectService(host);
225            service.openClientFile(path);
226            checkNumberOfProjects(service, { inferredProjects: 1 });
227            const libPath = `${windowsStyleRoot}${libFile.path.substring(1)}`;
228            checkProjectActualFiles(service.inferredProjects[0], [path, libPath]);
229            checkWatchedFiles(host, [libPath, `${getDirectoryPath(path)}/tsconfig.json`, `${getDirectoryPath(path)}/jsconfig.json`]);
230        }
231
232        it("for file of style c:/myprojects/project/x.js", () => {
233            verifyFilePathStyle("c:/myprojects/project/x.js");
234        });
235
236        it("for file of style //vda1cs4850/myprojects/project/x.js", () => {
237            verifyFilePathStyle("//vda1cs4850/myprojects/project/x.js");
238        });
239
240        it("for file of style //vda1cs4850/c$/myprojects/project/x.js", () => {
241            verifyFilePathStyle("//vda1cs4850/c$/myprojects/project/x.js");
242        });
243
244        it("for file of style c:/users/username/myprojects/project/x.js", () => {
245            verifyFilePathStyle("c:/users/username/myprojects/project/x.js");
246        });
247
248        it("for file of style //vda1cs4850/c$/users/username/myprojects/project/x.js", () => {
249            verifyFilePathStyle("//vda1cs4850/c$/users/username/myprojects/project/x.js");
250        });
251    });
252
253    describe("unittests:: tsserver:: watchEnvironment:: handles watch compiler options", () => {
254        it("with watchFile option as host configuration", () => {
255            const configFile: File = {
256                path: "/a/b/tsconfig.json",
257                content: "{}"
258            };
259            const files = [libFile, commonFile2, configFile];
260            const host = createServerHost(files.concat(commonFile1));
261            const session = createSession(host);
262            session.executeCommandSeq<protocol.ConfigureRequest>({
263                command: protocol.CommandTypes.Configure,
264                arguments: {
265                    watchOptions: {
266                        watchFile: protocol.WatchFileKind.UseFsEvents
267                    }
268                }
269            });
270            const service = session.getProjectService();
271            openFilesForSession([{ file: commonFile1, projectRootPath: "/a/b" }], session);
272            checkProjectActualFiles(
273                service.configuredProjects.get(configFile.path)!,
274                files.map(f => f.path).concat(commonFile1.path)
275            );
276
277            // Instead of polling watch (= watchedFiles), uses fsWatch
278            checkWatchedFiles(host, emptyArray);
279            checkWatchedDirectoriesDetailed(
280                host,
281                files.map(f => f.path.toLowerCase()),
282                1,
283                /*recursive*/ false,
284                arrayToMap(
285                    files,
286                    f => f.path.toLowerCase(),
287                    f => [{
288                        directoryName: f.path,
289                        fallbackPollingInterval: f === configFile ? PollingInterval.High : PollingInterval.Medium,
290                        fallbackOptions: { watchFile: WatchFileKind.PriorityPollingInterval }
291                    }]
292                )
293            );
294            checkWatchedDirectoriesDetailed(
295                host,
296                ["/a/b", "/a/b/node_modules/@types"],
297                1,
298                /*recursive*/ true,
299                arrayToMap(
300                    ["/a/b", "/a/b/node_modules/@types"],
301                    identity,
302                    directoryName => [{
303                        directoryName,
304                        fallbackPollingInterval: PollingInterval.Medium,
305                        fallbackOptions: { watchFile: WatchFileKind.PriorityPollingInterval }
306                    }]
307                )
308            );
309        });
310
311        it("with watchDirectory option as host configuration", () => {
312            const configFile: File = {
313                path: "/a/b/tsconfig.json",
314                content: "{}"
315            };
316            const files = [libFile, commonFile2, configFile];
317            const host = createServerHost(files.concat(commonFile1), { runWithoutRecursiveWatches: true });
318            const session = createSession(host);
319            session.executeCommandSeq<protocol.ConfigureRequest>({
320                command: protocol.CommandTypes.Configure,
321                arguments: {
322                    watchOptions: {
323                        watchDirectory: protocol.WatchDirectoryKind.UseFsEvents
324                    }
325                }
326            });
327            const service = session.getProjectService();
328            openFilesForSession([{ file: commonFile1, projectRootPath: "/a/b" }], session);
329            checkProjectActualFiles(
330                service.configuredProjects.get(configFile.path)!,
331                files.map(f => f.path).concat(commonFile1.path)
332            );
333
334            checkWatchedFilesDetailed(
335                host,
336                files.map(f => f.path.toLowerCase()),
337                1,
338                arrayToMap(
339                    files,
340                    f => f.path.toLowerCase(),
341                    f => [{
342                        fileName: f.path,
343                        pollingInterval: PollingInterval.Low
344                    }]
345                )
346            );
347            checkWatchedDirectoriesDetailed(
348                host,
349                ["/a/b", "/a/b/node_modules/@types"],
350                1,
351                /*recursive*/ false,
352                arrayToMap(
353                    ["/a/b", "/a/b/node_modules/@types"],
354                    identity,
355                    directoryName => [{
356                        directoryName,
357                        fallbackPollingInterval: PollingInterval.Medium,
358                        fallbackOptions: { watchFile: WatchFileKind.PriorityPollingInterval }
359                    }]
360                )
361            );
362            checkWatchedDirectories(host, emptyArray, /*recursive*/ true);
363        });
364
365        it("with fallbackPolling option as host configuration", () => {
366            const configFile: File = {
367                path: "/a/b/tsconfig.json",
368                content: "{}"
369            };
370            const files = [libFile, commonFile2, configFile];
371            const host = createServerHost(files.concat(commonFile1), { runWithoutRecursiveWatches: true, runWithFallbackPolling: true });
372            const session = createSession(host);
373            session.executeCommandSeq<protocol.ConfigureRequest>({
374                command: protocol.CommandTypes.Configure,
375                arguments: {
376                    watchOptions: {
377                        fallbackPolling: protocol.PollingWatchKind.PriorityInterval
378                    }
379                }
380            });
381            const service = session.getProjectService();
382            openFilesForSession([{ file: commonFile1, projectRootPath: "/a/b" }], session);
383            checkProjectActualFiles(
384                service.configuredProjects.get(configFile.path)!,
385                files.map(f => f.path).concat(commonFile1.path)
386            );
387
388            const filePaths = files.map(f => f.path);
389            const allFilePaths = filePaths.concat(["/a/b", "/a/b/node_modules/@types"]);
390            checkWatchedFilesDetailed(
391                host,
392                allFilePaths.map(toLowerCase),
393                1,
394                arrayToMap(
395                    allFilePaths,
396                    toLowerCase,
397                    fileName => [{
398                        fileName,
399                        pollingInterval: contains(filePaths, fileName) ? PollingInterval.Low : PollingInterval.Medium
400                    }]
401                )
402            );
403            checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
404            checkWatchedDirectories(host, emptyArray, /*recursive*/ true);
405        });
406
407        it("with watchFile option in configFile", () => {
408            const configFile: File = {
409                path: "/a/b/tsconfig.json",
410                content: JSON.stringify({
411                    watchOptions: {
412                        watchFile: "UseFsEvents"
413                    }
414                })
415            };
416            const files = [libFile, commonFile2, configFile];
417            const host = createServerHost(files.concat(commonFile1));
418            const session = createSession(host);
419            const service = session.getProjectService();
420            openFilesForSession([{ file: commonFile1, projectRootPath: "/a/b" }], session);
421            checkProjectActualFiles(
422                service.configuredProjects.get(configFile.path)!,
423                files.map(f => f.path).concat(commonFile1.path)
424            );
425
426            // The closed script infos are watched using host settings
427            checkWatchedFilesDetailed(
428                host,
429                [libFile, commonFile2].map(f => f.path.toLowerCase()),
430                1,
431                arrayToMap(
432                    [libFile, commonFile2],
433                    f => f.path.toLowerCase(),
434                    f => [{
435                        fileName: f.path,
436                        pollingInterval: PollingInterval.Low
437                    }]
438                )
439            );
440            // Config file with the setting with fsWatch
441            checkWatchedDirectoriesDetailed(
442                host,
443                [configFile.path.toLowerCase()],
444                1,
445                /*recursive*/ false,
446                arrayToMap(
447                    [configFile.path],
448                    toLowerCase,
449                    directoryName => [{
450                        directoryName,
451                        fallbackPollingInterval: PollingInterval.High,
452                        fallbackOptions: { watchFile: WatchFileKind.PriorityPollingInterval }
453                    }]
454                )
455            );
456            checkWatchedDirectoriesDetailed(
457                host,
458                ["/a/b", "/a/b/node_modules/@types"],
459                1,
460                /*recursive*/ true,
461                arrayToMap(
462                    ["/a/b", "/a/b/node_modules/@types"],
463                    identity,
464                    directoryName => [{
465                        directoryName,
466                        fallbackPollingInterval: PollingInterval.Medium,
467                        fallbackOptions: { watchFile: WatchFileKind.PriorityPollingInterval }
468                    }]
469                )
470            );
471        });
472
473        it("with watchDirectory option in configFile", () => {
474            const configFile: File = {
475                path: "/a/b/tsconfig.json",
476                content: JSON.stringify({
477                    watchOptions: {
478                        watchDirectory: "UseFsEvents"
479                    }
480                })
481            };
482            const files = [libFile, commonFile2, configFile];
483            const host = createServerHost(files.concat(commonFile1), { runWithoutRecursiveWatches: true });
484            const session = createSession(host);
485            const service = session.getProjectService();
486            openFilesForSession([{ file: commonFile1, projectRootPath: "/a/b" }], session);
487            checkProjectActualFiles(
488                service.configuredProjects.get(configFile.path)!,
489                files.map(f => f.path).concat(commonFile1.path)
490            );
491
492            checkWatchedFilesDetailed(
493                host,
494                files.map(f => f.path.toLowerCase()),
495                1,
496                arrayToMap(
497                    files,
498                    f => f.path.toLowerCase(),
499                    f => [{
500                        fileName: f.path,
501                        pollingInterval: PollingInterval.Low
502                    }]
503                )
504            );
505            checkWatchedDirectoriesDetailed(
506                host,
507                ["/a/b", "/a/b/node_modules/@types"],
508                1,
509                /*recursive*/ false,
510                arrayToMap(
511                    ["/a/b", "/a/b/node_modules/@types"],
512                    identity,
513                    directoryName => [{
514                        directoryName,
515                        fallbackPollingInterval: PollingInterval.Medium,
516                        fallbackOptions: { watchFile: WatchFileKind.PriorityPollingInterval }
517                    }]
518                )
519            );
520            checkWatchedDirectories(host, emptyArray, /*recursive*/ true);
521        });
522
523        it("with fallbackPolling option in configFile", () => {
524            const configFile: File = {
525                path: "/a/b/tsconfig.json",
526                content: JSON.stringify({
527                    watchOptions: {
528                        fallbackPolling: "PriorityInterval"
529                    }
530                })
531            };
532            const files = [libFile, commonFile2, configFile];
533            const host = createServerHost(files.concat(commonFile1), { runWithoutRecursiveWatches: true, runWithFallbackPolling: true });
534            const session = createSession(host);
535            session.executeCommandSeq<protocol.ConfigureRequest>({
536                command: protocol.CommandTypes.Configure,
537                arguments: {
538                    watchOptions: {
539                        fallbackPolling: protocol.PollingWatchKind.PriorityInterval
540                    }
541                }
542            });
543            const service = session.getProjectService();
544            openFilesForSession([{ file: commonFile1, projectRootPath: "/a/b" }], session);
545            checkProjectActualFiles(
546                service.configuredProjects.get(configFile.path)!,
547                files.map(f => f.path).concat(commonFile1.path)
548            );
549
550            const filePaths = files.map(f => f.path);
551            const allFilePaths = filePaths.concat(["/a/b", "/a/b/node_modules/@types"]);
552            checkWatchedFilesDetailed(
553                host,
554                allFilePaths.map(toLowerCase),
555                1,
556                arrayToMap(
557                    allFilePaths,
558                    toLowerCase,
559                    fileName => [{
560                        fileName,
561                        pollingInterval: contains(filePaths, fileName) ? PollingInterval.Low : PollingInterval.Medium
562                    }]
563                )
564            );
565            checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
566            checkWatchedDirectories(host, emptyArray, /*recursive*/ true);
567        });
568
569        describe("excludeDirectories", () => {
570            function setupFiles() {
571                const main: File = {
572                    path: `${tscWatch.projectRoot}/src/main.ts`,
573                    content: `import { foo } from "bar"; foo();`
574                };
575                const bar: File = {
576                    path: `${tscWatch.projectRoot}/node_modules/bar/index.d.ts`,
577                    content: `export { foo } from "./foo";`
578                };
579                const foo: File = {
580                    path: `${tscWatch.projectRoot}/node_modules/bar/foo.d.ts`,
581                    content: `export function foo(): string;`
582                };
583                return { main, bar, foo };
584            }
585
586            function setupConfigureHost(service: TestProjectService, configureHost: boolean | undefined) {
587                if (configureHost) {
588                    service.setHostConfiguration({
589                        watchOptions: { excludeDirectories: ["node_modules"] }
590                    });
591                }
592            }
593            function setup(configureHost?: boolean) {
594                const configFile: File = {
595                    path: `${tscWatch.projectRoot}/tsconfig.json`,
596                    content: JSON.stringify({ include: ["src"], watchOptions: { excludeDirectories: ["node_modules"] } })
597                };
598                const { main, bar, foo } = setupFiles();
599                const files = [libFile, main, bar, foo, configFile];
600                const host = createServerHost(files, { currentDirectory: tscWatch.projectRoot });
601                const service = createProjectService(host);
602                setupConfigureHost(service, configureHost);
603                service.openClientFile(main.path);
604                return { host, configFile };
605            }
606
607            it("with excludeDirectories option in configFile", () => {
608                const { host, configFile } = setup();
609                checkWatchedFilesDetailed(host, [configFile.path, libFile.path], 1);
610                checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
611                checkWatchedDirectoriesDetailed(
612                    host,
613                    arrayToMap(
614                        [`${tscWatch.projectRoot}/src`, `${tscWatch.projectRoot}/node_modules`],
615                        identity,
616                        f => f === `${tscWatch.projectRoot}/node_modules` ? 1 : 2,
617                    ),
618                    /*recursive*/ true,
619                );
620            });
621
622            it("with excludeDirectories option in configuration", () => {
623                const { host, configFile } = setup(/*configureHost*/ true);
624                checkWatchedFilesDetailed(host, [configFile.path, libFile.path], 1);
625                checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
626                checkWatchedDirectoriesDetailed(
627                    host,
628                    [`${tscWatch.projectRoot}/src`],
629                    2,
630                    /*recursive*/ true,
631                );
632            });
633
634            function setupExternalProject(configureHost?: boolean) {
635                const { main, bar, foo } = setupFiles();
636                const files = [libFile, main, bar, foo];
637                const host = createServerHost(files, { currentDirectory: tscWatch.projectRoot });
638                const service = createProjectService(host);
639                setupConfigureHost(service, configureHost);
640                service.openExternalProject(<protocol.ExternalProject>{
641                    projectFileName: `${tscWatch.projectRoot}/project.csproj`,
642                    rootFiles: toExternalFiles([main.path, bar.path, foo.path]),
643                    options: { excludeDirectories: ["node_modules"] }
644                });
645                service.openClientFile(main.path);
646                return host;
647            }
648
649            it("external project watch options", () => {
650                const host = setupExternalProject();
651                checkWatchedFilesDetailed(host, [libFile.path], 1);
652                checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
653                checkWatchedDirectoriesDetailed(
654                    host,
655                    [`${tscWatch.projectRoot}/src`, `${tscWatch.projectRoot}/node_modules`],
656                    1,
657                    /*recursive*/ true,
658                );
659            });
660
661            it("external project watch options in host configuration", () => {
662                const host = setupExternalProject(/*configureHost*/ true);
663                checkWatchedFilesDetailed(host, [libFile.path], 1);
664                checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
665                checkWatchedDirectoriesDetailed(
666                    host,
667                    [`${tscWatch.projectRoot}/src`],
668                    1,
669                    /*recursive*/ true,
670                );
671            });
672
673            it("external project watch options errors", () => {
674                const { main, bar, foo } = setupFiles();
675                const files = [libFile, main, bar, foo];
676                const host = createServerHost(files, { currentDirectory: tscWatch.projectRoot });
677                const service = createProjectService(host);
678                service.openExternalProject(<protocol.ExternalProject>{
679                    projectFileName: `${tscWatch.projectRoot}/project.csproj`,
680                    rootFiles: toExternalFiles([main.path, bar.path, foo.path]),
681                    options: { excludeDirectories: ["**/../*"] }
682                });
683                service.openClientFile(main.path);
684                const project = service.externalProjects[0];
685                assert.deepEqual(project.getAllProjectErrors(), [
686                    {
687                        messageText: `File specification cannot contain a parent directory ('..') that appears after a recursive directory wildcard ('**'): '**/../*'.`,
688                        category: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.category,
689                        code: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.code,
690                        file: undefined,
691                        start: undefined,
692                        length: undefined,
693                        reportsDeprecated: undefined,
694                        reportsUnnecessary: undefined,
695                    }
696                ]);
697            });
698
699            function setupInferredProject(configureHost?: boolean) {
700                const { main, bar, foo } = setupFiles();
701                const files = [libFile, main, bar, foo];
702                const host = createServerHost(files, { currentDirectory: tscWatch.projectRoot });
703                const service = createProjectService(host, { useInferredProjectPerProjectRoot: true });
704                setupConfigureHost(service, configureHost);
705                service.setCompilerOptionsForInferredProjects({ excludeDirectories: ["node_modules"] }, tscWatch.projectRoot);
706                service.openClientFile(main.path, main.content, ScriptKind.TS, tscWatch.projectRoot);
707                return host;
708            }
709
710            it("inferred project watch options", () => {
711                const host = setupInferredProject();
712                checkWatchedFilesDetailed(
713                    host,
714                    [libFile.path, `${tscWatch.projectRoot}/tsconfig.json`, `${tscWatch.projectRoot}/jsconfig.json`, `${tscWatch.projectRoot}/src/tsconfig.json`, `${tscWatch.projectRoot}/src/jsconfig.json`],
715                    1
716                );
717                checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
718                checkWatchedDirectoriesDetailed(
719                    host,
720                    [`${tscWatch.projectRoot}/src`, `${tscWatch.projectRoot}/node_modules`],
721                    1,
722                    /*recursive*/ true,
723                );
724            });
725
726            it("inferred project watch options in host configuration", () => {
727                const host = setupInferredProject(/*configureHost*/ true);
728                checkWatchedFilesDetailed(
729                    host,
730                    [libFile.path, `${tscWatch.projectRoot}/tsconfig.json`, `${tscWatch.projectRoot}/jsconfig.json`, `${tscWatch.projectRoot}/src/tsconfig.json`, `${tscWatch.projectRoot}/src/jsconfig.json`],
731                    1
732                );
733                checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
734                checkWatchedDirectoriesDetailed(
735                    host,
736                    [`${tscWatch.projectRoot}/src`],
737                    1,
738                    /*recursive*/ true,
739                );
740            });
741
742            it("inferred project watch options errors", () => {
743                const { main, bar, foo } = setupFiles();
744                const files = [libFile, main, bar, foo];
745                const host = createServerHost(files, { currentDirectory: tscWatch.projectRoot });
746                const service = createProjectService(host, { useInferredProjectPerProjectRoot: true });
747                service.setCompilerOptionsForInferredProjects({ excludeDirectories: ["**/../*"] }, tscWatch.projectRoot);
748                service.openClientFile(main.path, main.content, ScriptKind.TS, tscWatch.projectRoot);
749                const project = service.inferredProjects[0];
750                assert.deepEqual(project.getAllProjectErrors(), [
751                    {
752                        messageText: `File specification cannot contain a parent directory ('..') that appears after a recursive directory wildcard ('**'): '**/../*'.`,
753                        category: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.category,
754                        code: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.code,
755                        file: undefined,
756                        start: undefined,
757                        length: undefined,
758                        reportsDeprecated: undefined,
759                        reportsUnnecessary: undefined,
760                    }
761                ]);
762            });
763        });
764    });
765
766    describe("unittests:: tsserver:: watchEnvironment:: file names on case insensitive file system", () => {
767        function verifyFileNames(projectRoot: string, projectRootPath: string) {
768            const keyMapper = (str: string) => str.replace(projectRoot, projectRootPath);
769            const file: File = {
770                path: `${projectRoot}/foo.ts`,
771                content: `import { foo } from "bar"`
772            };
773            const host = createServerHost([file, libFile]);
774            const service = createProjectService(host);
775            service.openClientFile(file.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, projectRoot);
776            const expectedWatchFiles = [libFile.path, `${projectRoot}/tsconfig.json`, `${projectRoot}/jsconfig.json`];
777            checkWatchedFilesDetailed(
778                host,
779                expectedWatchFiles.map(keyMapper),
780                1,
781                arrayToMap(
782                    expectedWatchFiles,
783                    keyMapper,
784                    fileName => [{
785                        fileName,
786                        pollingInterval: PollingInterval.Low
787                    }]
788                )
789            );
790            checkWatchedDirectories(host, [], /*recursive*/ false);
791            const expectedWatchedDirectories = [`${projectRoot}/node_modules`, `${projectRoot}/node_modules/@types`];
792            checkWatchedDirectoriesDetailed(
793                host,
794                expectedWatchedDirectories.map(keyMapper),
795                1,
796                /*recursive*/ true,
797                arrayToMap(
798                    expectedWatchedDirectories,
799                    keyMapper,
800                    directoryName => [{
801                        directoryName,
802                        fallbackPollingInterval: PollingInterval.Medium,
803                        fallbackOptions: { watchFile: WatchFileKind.PriorityPollingInterval }
804                    }]
805                )
806            );
807        }
808
809        it("project with ascii file names", () => {
810            verifyFileNames("/User/userName/Projects/I", "/user/username/projects/i");
811        });
812
813        it("project with ascii file names with i", () => {
814            verifyFileNames("/User/userName/Projects/i", "/user/username/projects/i");
815        });
816
817        it("project with unicode file names", () => {
818            verifyFileNames("/User/userName/Projects/İ", "/user/username/projects/İ");
819        });
820    });
821}
822