• 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(scenario: string, tscWatchDirectory: Tsc_WatchDirectory) {
5            it(scenario, () => {
6                const projectFolder = "/a/username/project";
7                const projectSrcFolder = `${projectFolder}/src`;
8                const configFile: File = {
9                    path: `${projectFolder}/tsconfig.json`,
10                    content: JSON.stringify({
11                        watchOptions: {
12                            synchronousWatchDirectory: true
13                        }
14                    })
15                };
16                const index: File = {
17                    path: `${projectSrcFolder}/index.ts`,
18                    content: `import {} from "./"`
19                };
20                const file1: File = {
21                    path: `${projectSrcFolder}/file1.ts`,
22                    content: ""
23                };
24
25                const files = [index, file1, configFile, libFile];
26                const environmentVariables = new Map<string, string>();
27                environmentVariables.set("TSC_WATCHDIRECTORY", tscWatchDirectory);
28                const host = createServerHost(files, { environmentVariables });
29                const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) });
30                openFilesForSession([index], session);
31                session.executeCommandSeq<protocol.CompletionsRequest>({
32                    command: protocol.CommandTypes.CompletionInfo,
33                    arguments: protocolFileLocationFromSubstring(index, '"', { index: 1 })
34                });
35
36                // Add file2
37                const file2: File = {
38                    path: `${projectSrcFolder}/file2.ts`,
39                    content: ""
40                };
41                host.writeFile(file2.path, file2.content);
42                host.runQueuedTimeoutCallbacks();
43                session.executeCommandSeq<protocol.CompletionsRequest>({
44                    command: protocol.CommandTypes.CompletionInfo,
45                    arguments: protocolFileLocationFromSubstring(index, '"', { index: 1 })
46                });
47                baselineTsserverLogs("watchEnvironment", scenario, session);
48            });
49        }
50
51        verifyCompletionListWithNewFileInSubFolder(
52            "uses watchFile when file is added to subfolder",
53            Tsc_WatchDirectory.WatchFile
54        );
55        verifyCompletionListWithNewFileInSubFolder(
56            "uses non recursive watchDirectory when file is added to subfolder",
57            Tsc_WatchDirectory.NonRecursiveWatchDirectory
58        );
59        verifyCompletionListWithNewFileInSubFolder(
60            "uses dynamic polling when file is added to subfolder",
61            Tsc_WatchDirectory.DynamicPolling
62        );
63    });
64
65    describe("unittests:: tsserver:: watchEnvironment:: tsserverProjectSystem Watched recursive directories with windows style file system", () => {
66        function verifyWatchedDirectories(scenario: string, rootedPath: string, useProjectAtRoot: boolean) {
67            it(scenario, () => {
68                const root = useProjectAtRoot ? rootedPath : `${rootedPath}myfolder/allproject/`;
69                const configFile: File = {
70                    path: root + "project/tsconfig.json",
71                    content: "{}"
72                };
73                const file1: File = {
74                    path: root + "project/file1.ts",
75                    content: "let x = 10;"
76                };
77                const file2: File = {
78                    path: root + "project/file2.ts",
79                    content: "let y = 10;"
80                };
81                const files = [configFile, file1, file2, libFile];
82                const host = createServerHost(files, { windowsStyleRoot: "c:/" });
83                const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) });
84                openFilesForSession([file1], session);
85                baselineTsserverLogs("watchEnvironment", scenario, session);
86            });
87        }
88
89        verifyWatchedDirectories("files at windows style root", "c:/", /*useProjectAtRoot*/ true);
90        verifyWatchedDirectories("files not at windows style root", "c:/", /*useProjectAtRoot*/ false);
91        verifyWatchedDirectories("files at root", "c:/", /*useProjectAtRoot*/ true);
92        verifyWatchedDirectories("files not at root", "c:/", /*useProjectAtRoot*/ false);
93    });
94
95    it(`unittests:: tsserver:: watchEnvironment:: tsserverProjectSystem recursive watch directory implementation does not watch files/directories in node_modules starting with "."`, () => {
96        const projectFolder = "/a/username/project";
97        const projectSrcFolder = `${projectFolder}/src`;
98        const configFile: File = {
99            path: `${projectFolder}/tsconfig.json`,
100            content: "{}"
101        };
102        const index: File = {
103            path: `${projectSrcFolder}/index.ts`,
104            content: `import {} from "file"`
105        };
106        const file1: File = {
107            path: `${projectSrcFolder}/file1.ts`,
108            content: ""
109        };
110        const nodeModulesExistingUnusedFile: File = {
111            path: `${projectFolder}/node_modules/someFile.d.ts`,
112            content: ""
113        };
114        const environmentVariables = new Map<string, string>();
115        environmentVariables.set("TSC_WATCHDIRECTORY", Tsc_WatchDirectory.NonRecursiveWatchDirectory);
116        const host = createServerHost([index, file1, configFile, libFile, nodeModulesExistingUnusedFile], { environmentVariables });
117        const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) });
118        openFilesForSession([index], session);
119
120        const nodeModulesIgnoredFileFromIgnoreDirectory: File = {
121            path: `${projectFolder}/node_modules/.cache/someFile.d.ts`,
122            content: ""
123        };
124
125        const nodeModulesIgnoredFile: File = {
126            path: `${projectFolder}/node_modules/.cacheFile.ts`,
127            content: ""
128        };
129
130        const gitIgnoredFileFromIgnoreDirectory: File = {
131            path: `${projectFolder}/.git/someFile.d.ts`,
132            content: ""
133        };
134
135        const gitIgnoredFile: File = {
136            path: `${projectFolder}/.gitCache.d.ts`,
137            content: ""
138        };
139        const emacsIgnoredFileFromIgnoreDirectory: File = {
140            path: `${projectFolder}/src/.#field.ts`,
141            content: ""
142        };
143
144        [
145            nodeModulesIgnoredFileFromIgnoreDirectory,
146            nodeModulesIgnoredFile,
147            gitIgnoredFileFromIgnoreDirectory,
148            gitIgnoredFile,
149            emacsIgnoredFileFromIgnoreDirectory
150        ].forEach(ignoredEntity => {
151            host.ensureFileOrFolder(ignoredEntity);
152            host.checkTimeoutQueueLength(0);
153        });
154
155        baselineTsserverLogs("watchEnvironment", `recursive directory does not watch files starting with dot in node_modules`, session);
156    });
157
158    it("unittests:: tsserver:: watchEnvironment:: tsserverProjectSystem watching files with network style paths", () => {
159        const logger = createLoggerWithInMemoryLogs(/*host*/ undefined!); // Special handling to ensure same logger is used
160        verifyFilePathStyle("c:/myprojects/project/x.js", logger);
161        verifyFilePathStyle("//vda1cs4850/myprojects/project/x.js", logger);
162        verifyFilePathStyle("//vda1cs4850/c$/myprojects/project/x.js", logger);
163        verifyFilePathStyle("c:/users/username/myprojects/project/x.js", logger);
164        verifyFilePathStyle("//vda1cs4850/c$/users/username/myprojects/project/x.js", logger);
165        baselineTsserverLogs("watchEnvironment", `watching files with network style paths`, { logger });
166
167        function verifyFilePathStyle(path: string, logger: Logger) {
168            const windowsStyleRoot = path.substring(0, getRootLength(path));
169            const file: File = { path, content: "const x = 10" };
170            const host = createServerHost(
171                [libFile, file],
172                { windowsStyleRoot }
173            );
174            logger.host = host;
175            logger.info(`For files of style ${path}`);
176            const session = createSession(host, { logger });
177            openFilesForSession([file], session);
178        }
179    });
180
181    describe("unittests:: tsserver:: watchEnvironment:: handles watch compiler options", () => {
182        it("with watchFile option as host configuration", () => {
183            const configFile: File = {
184                path: "/a/b/tsconfig.json",
185                content: "{}"
186            };
187            const files = [libFile, commonFile2, configFile];
188            const host = createServerHost(files.concat(commonFile1));
189            const logger = createLoggerWithInMemoryLogs(host);
190            const session = createSession(host, { logger });
191            session.executeCommandSeq<protocol.ConfigureRequest>({
192                command: protocol.CommandTypes.Configure,
193                arguments: {
194                    watchOptions: {
195                        watchFile: protocol.WatchFileKind.UseFsEvents
196                    }
197                }
198            });
199            openFilesForSession([{ file: commonFile1, projectRootPath: "/a/b" }], session);
200            baselineTsserverLogs("watchEnvironment", `with watchFile option as host configuration`, session);
201        });
202
203        it("with watchDirectory option as host configuration", () => {
204            const configFile: File = {
205                path: "/a/b/tsconfig.json",
206                content: "{}"
207            };
208            const files = [libFile, commonFile2, configFile];
209            const host = createServerHost(files.concat(commonFile1), { runWithoutRecursiveWatches: true });
210            const logger = createLoggerWithInMemoryLogs(host);
211            const session = createSession(host, { logger });
212            session.executeCommandSeq<protocol.ConfigureRequest>({
213                command: protocol.CommandTypes.Configure,
214                arguments: {
215                    watchOptions: {
216                        watchDirectory: protocol.WatchDirectoryKind.UseFsEvents
217                    }
218                }
219            });
220            openFilesForSession([{ file: commonFile1, projectRootPath: "/a/b" }], session);
221            baselineTsserverLogs("watchEnvironment", `with watchDirectory option as host configuration`, session);
222        });
223
224        it("with fallbackPolling option as host configuration", () => {
225            const configFile: File = {
226                path: "/a/b/tsconfig.json",
227                content: "{}"
228            };
229            const files = [libFile, commonFile2, configFile];
230            const host = createServerHost(files.concat(commonFile1), { runWithoutRecursiveWatches: true, runWithFallbackPolling: true });
231            const logger = createLoggerWithInMemoryLogs(host);
232            const session = createSession(host, { logger });
233            session.executeCommandSeq<protocol.ConfigureRequest>({
234                command: protocol.CommandTypes.Configure,
235                arguments: {
236                    watchOptions: {
237                        fallbackPolling: protocol.PollingWatchKind.PriorityInterval
238                    }
239                }
240            });
241            openFilesForSession([{ file: commonFile1, projectRootPath: "/a/b" }], session);
242            baselineTsserverLogs("watchEnvironment", `with fallbackPolling option as host configuration`, session);
243        });
244
245        it("with watchFile option in configFile", () => {
246            const configFile: File = {
247                path: "/a/b/tsconfig.json",
248                content: JSON.stringify({
249                    watchOptions: {
250                        watchFile: "UseFsEvents"
251                    }
252                })
253            };
254            const files = [libFile, commonFile2, configFile];
255            const host = createServerHost(files.concat(commonFile1));
256            const logger = createLoggerWithInMemoryLogs(host);
257            const session = createSession(host, { logger });
258            openFilesForSession([{ file: commonFile1, projectRootPath: "/a/b" }], session);
259            baselineTsserverLogs("watchEnvironment", `with watchFile option in configFile`, session);
260        });
261
262        it("with watchDirectory option in configFile", () => {
263            const configFile: File = {
264                path: "/a/b/tsconfig.json",
265                content: JSON.stringify({
266                    watchOptions: {
267                        watchDirectory: "UseFsEvents"
268                    }
269                })
270            };
271            const files = [libFile, commonFile2, configFile];
272            const host = createServerHost(files.concat(commonFile1), { runWithoutRecursiveWatches: true });
273            const logger = createLoggerWithInMemoryLogs(host);
274            const session = createSession(host, { logger });
275            openFilesForSession([{ file: commonFile1, projectRootPath: "/a/b" }], session);
276            baselineTsserverLogs("watchEnvironment", `with watchDirectory option in configFile`, session);
277        });
278
279        it("with fallbackPolling option in configFile", () => {
280            const configFile: File = {
281                path: "/a/b/tsconfig.json",
282                content: JSON.stringify({
283                    watchOptions: {
284                        fallbackPolling: "PriorityInterval"
285                    }
286                })
287            };
288            const files = [libFile, commonFile2, configFile];
289            const host = createServerHost(files.concat(commonFile1), { runWithoutRecursiveWatches: true, runWithFallbackPolling: true });
290            const logger = createLoggerWithInMemoryLogs(host);
291            const session = createSession(host, { logger });
292            session.executeCommandSeq<protocol.ConfigureRequest>({
293                command: protocol.CommandTypes.Configure,
294                arguments: {
295                    watchOptions: {
296                        fallbackPolling: protocol.PollingWatchKind.PriorityInterval
297                    }
298                }
299            });
300            openFilesForSession([{ file: commonFile1, projectRootPath: "/a/b" }], session);
301            baselineTsserverLogs("watchEnvironment", `with fallbackPolling option in configFile`, session);
302        });
303
304        describe("excludeDirectories", () => {
305            function setupFiles() {
306                const main: File = {
307                    path: `${tscWatch.projectRoot}/src/main.ts`,
308                    content: `import { foo } from "bar"; foo();`
309                };
310                const bar: File = {
311                    path: `${tscWatch.projectRoot}/node_modules/bar/index.d.ts`,
312                    content: `export { foo } from "./foo";`
313                };
314                const foo: File = {
315                    path: `${tscWatch.projectRoot}/node_modules/bar/foo.d.ts`,
316                    content: `export function foo(): string;`
317                };
318                return { main, bar, foo };
319            }
320
321            function setupConfigureHost(session: TestSession, configureHost: boolean | undefined) {
322                if (configureHost) {
323                    session.executeCommandSeq<protocol.ConfigureRequest>({
324                        command: protocol.CommandTypes.Configure,
325                        arguments: {
326                            watchOptions: { excludeDirectories: ["node_modules"] }
327                        }
328                    });
329                }
330            }
331            function setup(configureHost?: boolean) {
332                const configFile: File = {
333                    path: `${tscWatch.projectRoot}/tsconfig.json`,
334                    content: JSON.stringify({ include: ["src"], watchOptions: { excludeDirectories: ["node_modules"] } })
335                };
336                const { main, bar, foo } = setupFiles();
337                const files = [libFile, main, bar, foo, configFile];
338                const host = createServerHost(files, { currentDirectory: tscWatch.projectRoot });
339                const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) });
340                setupConfigureHost(session, configureHost);
341                openFilesForSession([main], session);
342                return session;
343            }
344
345            it("with excludeDirectories option in configFile", () => {
346                const session = setup();
347                baselineTsserverLogs("watchEnvironment", `with excludeDirectories option in configFile`, session);
348            });
349
350            it("with excludeDirectories option in configuration", () => {
351                const session = setup(/*configureHost*/ true);
352                baselineTsserverLogs("watchEnvironment", `with excludeDirectories option in configuration`, session);
353            });
354
355            function setupExternalProject(configureHost?: boolean) {
356                const { main, bar, foo } = setupFiles();
357                const files = [libFile, main, bar, foo];
358                const host = createServerHost(files, { currentDirectory: tscWatch.projectRoot });
359                const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) });
360                setupConfigureHost(session, configureHost);
361                session.executeCommandSeq<protocol.OpenExternalProjectRequest>({
362                    command: protocol.CommandTypes.OpenExternalProject,
363                    arguments: {
364                        projectFileName: `${tscWatch.projectRoot}/project.csproj`,
365                        rootFiles: toExternalFiles([main.path, bar.path, foo.path]),
366                        options: { excludeDirectories: ["node_modules"] }
367                    }
368                });
369                openFilesForSession([main], session);
370                return session;
371            }
372
373            it("external project watch options", () => {
374                const session = setupExternalProject();
375                baselineTsserverLogs("watchEnvironment", `external project watch options`, session);
376            });
377
378            it("external project watch options in host configuration", () => {
379                const session = setupExternalProject(/*configureHost*/ true);
380                baselineTsserverLogs("watchEnvironment", `external project watch options in host configuration`, session);
381            });
382
383            it("external project watch options errors", () => {
384                const { main, bar, foo } = setupFiles();
385                const files = [libFile, main, bar, foo];
386                const host = createServerHost(files, { currentDirectory: tscWatch.projectRoot });
387                const service = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) });
388                service.openExternalProject({
389                    projectFileName: `${tscWatch.projectRoot}/project.csproj`,
390                    rootFiles: toExternalFiles([main.path, bar.path, foo.path]),
391                    options: { excludeDirectories: ["**/../*"] }
392                } as protocol.ExternalProject);
393                service.openClientFile(main.path);
394                const project = service.externalProjects[0];
395                service.logger.info(JSON.stringify(project.getAllProjectErrors(), undefined, 2));
396                baselineTsserverLogs("watchEnvironment", `external project watch options errors`, service);
397            });
398
399            function setupInferredProject(configureHost?: boolean) {
400                const { main, bar, foo } = setupFiles();
401                const files = [libFile, main, bar, foo];
402                const host = createServerHost(files, { currentDirectory: tscWatch.projectRoot });
403                const session = createSession(host, { useInferredProjectPerProjectRoot: true, logger: createLoggerWithInMemoryLogs(host) });
404                setupConfigureHost(session, configureHost);
405                session.executeCommandSeq<protocol.SetCompilerOptionsForInferredProjectsRequest>({
406                    command: protocol.CommandTypes.CompilerOptionsForInferredProjects,
407                    arguments: {
408                        options: { excludeDirectories: ["node_modules"] },
409                        projectRootPath: tscWatch.projectRoot
410                    }
411                });
412                openFilesForSession([{ file: main, projectRootPath: tscWatch.projectRoot }], session);
413                return session;
414            }
415
416            it("inferred project watch options", () => {
417                const session = setupInferredProject();
418                baselineTsserverLogs("watchEnvironment", `inferred project watch options`, session);
419            });
420
421            it("inferred project watch options in host configuration", () => {
422                const session = setupInferredProject(/*configureHost*/ true);
423                baselineTsserverLogs("watchEnvironment", `inferred project watch options in host configuration`, session);
424            });
425
426            it("inferred project watch options errors", () => {
427                const { main, bar, foo } = setupFiles();
428                const files = [libFile, main, bar, foo];
429                const host = createServerHost(files, { currentDirectory: tscWatch.projectRoot });
430                const service = createProjectService(host, { useInferredProjectPerProjectRoot: true, logger: createLoggerWithInMemoryLogs(host) });
431                service.setCompilerOptionsForInferredProjects({ excludeDirectories: ["**/../*"] }, tscWatch.projectRoot);
432                service.openClientFile(main.path, main.content, ScriptKind.TS, tscWatch.projectRoot);
433                const project = service.inferredProjects[0];
434                service.logger.info(JSON.stringify(project.getAllProjectErrors(), undefined, 2));
435                baselineTsserverLogs("watchEnvironment", `inferred project watch options errors`, service);
436            });
437        });
438    });
439
440    describe("unittests:: tsserver:: watchEnvironment:: file names on case insensitive file system", () => {
441        function verifyFileNames(scenario: string, projectRootPath: string) {
442            it(scenario, () => {
443                const file: File = {
444                    path: `${projectRootPath}/foo.ts`,
445                    content: `import { foo } from "bar"`
446                };
447                const host = createServerHost([file, libFile]);
448                const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) });
449                openFilesForSession([{ file, projectRootPath }], session);
450                baselineTsserverLogs("watchEnvironment", scenario, session);
451            });
452        }
453
454        verifyFileNames("project with ascii file names", "/User/userName/Projects/I");
455        verifyFileNames("project with ascii file names with i", "/User/userName/Projects/i");
456        verifyFileNames("project with unicode file names", "/User/userName/Projects/İ");
457    });
458
459    describe("unittests:: tsserver:: watchEnvironment:: watchFile is single watcher per file", () => {
460        it("when watchFile is single watcher per file", () => {
461            const config: File = {
462                path: `${tscWatch.projectRoot}/tsconfig.json`,
463                content: JSON.stringify({
464                    compilerOptions: {
465                        composite: true,
466                        resolveJsonModule: true,
467                    },
468                })
469            };
470            const index: File = {
471                path: `${tscWatch.projectRoot}/index.ts`,
472                content: `import * as tsconfig from "./tsconfig.json";`
473            };
474            const host = createServerHost([config, index, libFile]);
475            const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) });
476            openFilesForSession([index], session);
477            baselineTsserverLogs("watchEnvironment", "when watchFile is single watcher per file", session);
478        });
479    });
480}
481