• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1namespace ts.projectSystem {
2    describe("unittests:: tsserver:: Inferred projects", () => {
3        it("create inferred project", () => {
4            const appFile: File = {
5                path: `${tscWatch.projectRoot}/app.ts`,
6                content: `
7                import {f} from "./module"
8                console.log(f)
9                `
10            };
11
12            const moduleFile: File = {
13                path: `${tscWatch.projectRoot}/module.d.ts`,
14                content: `export let x: number`
15            };
16            const host = createServerHost([appFile, moduleFile, libFile]);
17            const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) });
18            projectService.openClientFile(appFile.path);
19            baselineTsserverLogs("inferredProjects", "create inferred project", projectService);
20        });
21
22        it("should use only one inferred project if 'useOneInferredProject' is set", () => {
23            const file1 = {
24                path: `${tscWatch.projectRoot}/a/b/main.ts`,
25                content: "let x =1;"
26            };
27            const configFile: File = {
28                path: `${tscWatch.projectRoot}/a/b/tsconfig.json`,
29                content: `{
30                    "compilerOptions": {
31                        "target": "es6"
32                    },
33                    "files": [ "main.ts" ]
34                }`
35            };
36            const file2 = {
37                path: `${tscWatch.projectRoot}/a/c/main.ts`,
38                content: "let x =1;"
39            };
40
41            const file3 = {
42                path: `${tscWatch.projectRoot}/a/d/main.ts`,
43                content: "let x =1;"
44            };
45
46            const host = createServerHost([file1, file2, file3, libFile]);
47            const projectService = createProjectService(host, { useSingleInferredProject: true });
48            projectService.openClientFile(file1.path);
49            projectService.openClientFile(file2.path);
50            projectService.openClientFile(file3.path);
51
52            checkNumberOfConfiguredProjects(projectService, 0);
53            checkNumberOfInferredProjects(projectService, 1);
54            checkProjectActualFiles(projectService.inferredProjects[0], [file1.path, file2.path, file3.path, libFile.path]);
55
56
57            host.writeFile(configFile.path, configFile.content);
58            host.checkTimeoutQueueLengthAndRun(2); // load configured project from disk + ensureProjectsForOpenFiles
59            checkNumberOfConfiguredProjects(projectService, 1);
60            checkNumberOfInferredProjects(projectService, 1);
61            checkProjectActualFiles(projectService.inferredProjects[0], [file2.path, file3.path, libFile.path]);
62        });
63
64        it("disable inferred project", () => {
65            const file1 = {
66                path: "/a/b/f1.ts",
67                content: "let x =1;"
68            };
69
70            const host = createServerHost([file1]);
71            const projectService = createProjectService(host, { useSingleInferredProject: true, syntaxOnly: true });
72
73            projectService.openClientFile(file1.path, file1.content);
74
75            checkNumberOfProjects(projectService, { inferredProjects: 1 });
76            const proj = projectService.inferredProjects[0];
77            assert.isDefined(proj);
78
79            assert.isFalse(proj.languageServiceEnabled);
80        });
81
82        it("project settings for inferred projects", () => {
83            const file1 = {
84                path: "/a/b/app.ts",
85                content: `import {x} from "mod"`
86            };
87            const modFile = {
88                path: "/a/mod.ts",
89                content: "export let x: number"
90            };
91            const host = createServerHost([file1, modFile]);
92            const projectService = createProjectService(host);
93
94            projectService.openClientFile(file1.path);
95            projectService.openClientFile(modFile.path);
96
97            checkNumberOfProjects(projectService, { inferredProjects: 2 });
98            const inferredProjects = projectService.inferredProjects.slice();
99            checkProjectActualFiles(inferredProjects[0], [file1.path]);
100            checkProjectActualFiles(inferredProjects[1], [modFile.path]);
101
102            projectService.setCompilerOptionsForInferredProjects({ moduleResolution: ModuleResolutionKind.Classic });
103            host.checkTimeoutQueueLengthAndRun(3);
104            checkNumberOfProjects(projectService, { inferredProjects: 2 });
105            assert.strictEqual(projectService.inferredProjects[0], inferredProjects[0]);
106            assert.strictEqual(projectService.inferredProjects[1], inferredProjects[1]);
107            checkProjectActualFiles(inferredProjects[0], [file1.path, modFile.path]);
108            assert.isTrue(inferredProjects[1].isOrphan());
109        });
110
111        it("should support files without extensions", () => {
112            const f = {
113                path: "/a/compile",
114                content: "let x = 1"
115            };
116            const host = createServerHost([f]);
117            const session = createSession(host);
118            session.executeCommand({
119                seq: 1,
120                type: "request",
121                command: "compilerOptionsForInferredProjects",
122                arguments: {
123                    options: {
124                        allowJs: true
125                    }
126                }
127            } as server.protocol.SetCompilerOptionsForInferredProjectsRequest);
128            session.executeCommand({
129                seq: 2,
130                type: "request",
131                command: "open",
132                arguments: {
133                    file: f.path,
134                    fileContent: f.content,
135                    scriptKindName: "JS"
136                }
137            } as server.protocol.OpenRequest);
138            const projectService = session.getProjectService();
139            checkNumberOfProjects(projectService, { inferredProjects: 1 });
140            checkProjectActualFiles(projectService.inferredProjects[0], [f.path]);
141        });
142
143        it("inferred projects per project root", () => {
144            const file1 = { path: "/a/file1.ts", content: "let x = 1;", projectRootPath: "/a" };
145            const file2 = { path: "/a/file2.ts", content: "let y = 2;", projectRootPath: "/a" };
146            const file3 = { path: "/b/file2.ts", content: "let x = 3;", projectRootPath: "/b" };
147            const file4 = { path: "/c/file3.ts", content: "let z = 4;" };
148            const host = createServerHost([file1, file2, file3, file4]);
149            const session = createSession(host, {
150                useSingleInferredProject: true,
151                useInferredProjectPerProjectRoot: true
152            });
153            session.executeCommand({
154                seq: 1,
155                type: "request",
156                command: CommandNames.CompilerOptionsForInferredProjects,
157                arguments: {
158                    options: {
159                        allowJs: true,
160                        target: ScriptTarget.ESNext
161                    }
162                }
163            } as server.protocol.SetCompilerOptionsForInferredProjectsRequest);
164            session.executeCommand({
165                seq: 2,
166                type: "request",
167                command: CommandNames.CompilerOptionsForInferredProjects,
168                arguments: {
169                    options: {
170                        allowJs: true,
171                        target: ScriptTarget.ES2015
172                    },
173                    projectRootPath: "/b"
174                }
175            } as server.protocol.SetCompilerOptionsForInferredProjectsRequest);
176            session.executeCommand({
177                seq: 3,
178                type: "request",
179                command: CommandNames.Open,
180                arguments: {
181                    file: file1.path,
182                    fileContent: file1.content,
183                    scriptKindName: "JS",
184                    projectRootPath: file1.projectRootPath
185                }
186            } as server.protocol.OpenRequest);
187            session.executeCommand({
188                seq: 4,
189                type: "request",
190                command: CommandNames.Open,
191                arguments: {
192                    file: file2.path,
193                    fileContent: file2.content,
194                    scriptKindName: "JS",
195                    projectRootPath: file2.projectRootPath
196                }
197            } as server.protocol.OpenRequest);
198            session.executeCommand({
199                seq: 5,
200                type: "request",
201                command: CommandNames.Open,
202                arguments: {
203                    file: file3.path,
204                    fileContent: file3.content,
205                    scriptKindName: "JS",
206                    projectRootPath: file3.projectRootPath
207                }
208            } as server.protocol.OpenRequest);
209            session.executeCommand({
210                seq: 6,
211                type: "request",
212                command: CommandNames.Open,
213                arguments: {
214                    file: file4.path,
215                    fileContent: file4.content,
216                    scriptKindName: "JS"
217                }
218            } as server.protocol.OpenRequest);
219
220            const projectService = session.getProjectService();
221            checkNumberOfProjects(projectService, { inferredProjects: 3 });
222            checkProjectActualFiles(projectService.inferredProjects[0], [file4.path]);
223            checkProjectActualFiles(projectService.inferredProjects[1], [file1.path, file2.path]);
224            checkProjectActualFiles(projectService.inferredProjects[2], [file3.path]);
225            assert.equal(projectService.inferredProjects[0].getCompilationSettings().target, ScriptTarget.ESNext);
226            assert.equal(projectService.inferredProjects[1].getCompilationSettings().target, ScriptTarget.ESNext);
227            assert.equal(projectService.inferredProjects[2].getCompilationSettings().target, ScriptTarget.ES2015);
228        });
229
230        function checkInferredProject(inferredProject: server.InferredProject, actualFiles: File[], target: ScriptTarget) {
231            checkProjectActualFiles(inferredProject, actualFiles.map(f => f.path));
232            assert.equal(inferredProject.getCompilationSettings().target, target);
233        }
234
235        function verifyProjectRootWithCaseSensitivity(useCaseSensitiveFileNames: boolean) {
236            const files: [File, File, File, File] = [
237                { path: "/a/file1.ts", content: "let x = 1;" },
238                { path: "/A/file2.ts", content: "let y = 2;" },
239                { path: "/b/file2.ts", content: "let x = 3;" },
240                { path: "/c/file3.ts", content: "let z = 4;" }
241            ];
242            const host = createServerHost(files, { useCaseSensitiveFileNames });
243            const projectService = createProjectService(host, { useSingleInferredProject: true, useInferredProjectPerProjectRoot: true });
244            projectService.setCompilerOptionsForInferredProjects({
245                allowJs: true,
246                target: ScriptTarget.ESNext
247            });
248            projectService.setCompilerOptionsForInferredProjects({
249                allowJs: true,
250                target: ScriptTarget.ES2015
251            }, "/a");
252
253            openClientFiles(["/a", "/a", "/b", undefined]);
254            verifyInferredProjectsState([
255                [[files[3]], ScriptTarget.ESNext],
256                [[files[0], files[1]], ScriptTarget.ES2015],
257                [[files[2]], ScriptTarget.ESNext]
258            ]);
259            closeClientFiles();
260
261            openClientFiles(["/a", "/A", "/b", undefined]);
262            if (useCaseSensitiveFileNames) {
263                verifyInferredProjectsState([
264                    [[files[3]], ScriptTarget.ESNext],
265                    [[files[0]], ScriptTarget.ES2015],
266                    [[files[1]], ScriptTarget.ESNext],
267                    [[files[2]], ScriptTarget.ESNext]
268                ]);
269            }
270            else {
271                verifyInferredProjectsState([
272                    [[files[3]], ScriptTarget.ESNext],
273                    [[files[0], files[1]], ScriptTarget.ES2015],
274                    [[files[2]], ScriptTarget.ESNext]
275                ]);
276            }
277            closeClientFiles();
278
279            projectService.setCompilerOptionsForInferredProjects({
280                allowJs: true,
281                target: ScriptTarget.ES2017
282            }, "/A");
283
284            openClientFiles(["/a", "/a", "/b", undefined]);
285            verifyInferredProjectsState([
286                [[files[3]], ScriptTarget.ESNext],
287                [[files[0], files[1]], useCaseSensitiveFileNames ? ScriptTarget.ES2015 : ScriptTarget.ES2017],
288                [[files[2]], ScriptTarget.ESNext]
289            ]);
290            closeClientFiles();
291
292            openClientFiles(["/a", "/A", "/b", undefined]);
293            if (useCaseSensitiveFileNames) {
294                verifyInferredProjectsState([
295                    [[files[3]], ScriptTarget.ESNext],
296                    [[files[0]], ScriptTarget.ES2015],
297                    [[files[1]], ScriptTarget.ES2017],
298                    [[files[2]], ScriptTarget.ESNext]
299                ]);
300            }
301            else {
302                verifyInferredProjectsState([
303                    [[files[3]], ScriptTarget.ESNext],
304                    [[files[0], files[1]], ScriptTarget.ES2017],
305                    [[files[2]], ScriptTarget.ESNext]
306                ]);
307            }
308            closeClientFiles();
309
310            function openClientFiles(projectRoots: [string | undefined, string | undefined, string | undefined, string | undefined]) {
311                files.forEach((file, index) => {
312                    projectService.openClientFile(file.path, file.content, ScriptKind.JS, projectRoots[index]);
313                });
314            }
315
316            function closeClientFiles() {
317                files.forEach(file => projectService.closeClientFile(file.path));
318            }
319
320            function verifyInferredProjectsState(expected: [File[], ScriptTarget][]) {
321                checkNumberOfProjects(projectService, { inferredProjects: expected.length });
322                projectService.inferredProjects.forEach((p, index) => {
323                    const [actualFiles, target] = expected[index];
324                    checkInferredProject(p, actualFiles, target);
325                });
326            }
327        }
328
329        it("inferred projects per project root with case sensitive system", () => {
330            verifyProjectRootWithCaseSensitivity(/*useCaseSensitiveFileNames*/ true);
331        });
332
333        it("inferred projects per project root with case insensitive system", () => {
334            verifyProjectRootWithCaseSensitivity(/*useCaseSensitiveFileNames*/ false);
335        });
336
337        it("should still retain configured project created while opening the file", () => {
338            const appFile: File = {
339                path: `${tscWatch.projectRoot}/app.ts`,
340                content: `const app = 20;`
341            };
342            const config: File = {
343                path: `${tscWatch.projectRoot}/tsconfig.json`,
344                content: "{}"
345            };
346            const jsFile1: File = {
347                path: `${tscWatch.projectRoot}/jsFile1.js`,
348                content: `const jsFile1 = 10;`
349            };
350            const jsFile2: File = {
351                path: `${tscWatch.projectRoot}/jsFile2.js`,
352                content: `const jsFile2 = 10;`
353            };
354            const host = createServerHost([appFile, libFile, config, jsFile1, jsFile2]);
355            const projectService = createProjectService(host);
356            const originalSet = projectService.configuredProjects.set;
357            const originalDelete = projectService.configuredProjects.delete;
358            const configuredCreated = new Map<string, true>();
359            const configuredRemoved = new Map<string, true>();
360            projectService.configuredProjects.set = (key, value) => {
361                assert.isFalse(configuredCreated.has(key));
362                configuredCreated.set(key, true);
363                return originalSet.call(projectService.configuredProjects, key, value);
364            };
365            projectService.configuredProjects.delete = key => {
366                assert.isFalse(configuredRemoved.has(key));
367                configuredRemoved.set(key, true);
368                return originalDelete.call(projectService.configuredProjects, key);
369            };
370
371            // Do not remove config project when opening jsFile that is not present as part of config project
372            projectService.openClientFile(jsFile1.path);
373            checkNumberOfProjects(projectService, { inferredProjects: 1, configuredProjects: 1 });
374            checkProjectActualFiles(projectService.inferredProjects[0], [jsFile1.path, libFile.path]);
375            const project = projectService.configuredProjects.get(config.path)!;
376            checkProjectActualFiles(project, [appFile.path, config.path, libFile.path]);
377            checkConfiguredProjectCreatedAndNotDeleted();
378
379            // Do not remove config project when opening jsFile that is not present as part of config project
380            projectService.closeClientFile(jsFile1.path);
381            checkNumberOfProjects(projectService, { inferredProjects: 1, configuredProjects: 1 });
382            projectService.openClientFile(jsFile2.path);
383            checkNumberOfProjects(projectService, { inferredProjects: 1, configuredProjects: 1 });
384            checkProjectActualFiles(projectService.inferredProjects[0], [jsFile2.path, libFile.path]);
385            checkProjectActualFiles(project, [appFile.path, config.path, libFile.path]);
386            checkConfiguredProjectNotCreatedAndNotDeleted();
387
388            // Do not remove config project when opening jsFile that is not present as part of config project
389            projectService.openClientFile(jsFile1.path);
390            checkNumberOfProjects(projectService, { inferredProjects: 2, configuredProjects: 1 });
391            checkProjectActualFiles(projectService.inferredProjects[0], [jsFile2.path, libFile.path]);
392            checkProjectActualFiles(projectService.inferredProjects[1], [jsFile1.path, libFile.path]);
393            checkProjectActualFiles(project, [appFile.path, config.path, libFile.path]);
394            checkConfiguredProjectNotCreatedAndNotDeleted();
395
396            // When opening file that doesnt fall back to the config file, we remove the config project
397            projectService.openClientFile(libFile.path);
398            checkNumberOfProjects(projectService, { inferredProjects: 2 });
399            checkProjectActualFiles(projectService.inferredProjects[0], [jsFile2.path, libFile.path]);
400            checkProjectActualFiles(projectService.inferredProjects[1], [jsFile1.path, libFile.path]);
401            checkConfiguredProjectNotCreatedButDeleted();
402
403            function checkConfiguredProjectCreatedAndNotDeleted() {
404                assert.equal(configuredCreated.size, 1);
405                assert.isTrue(configuredCreated.has(config.path));
406                assert.equal(configuredRemoved.size, 0);
407                configuredCreated.clear();
408            }
409
410            function checkConfiguredProjectNotCreatedAndNotDeleted() {
411                assert.equal(configuredCreated.size, 0);
412                assert.equal(configuredRemoved.size, 0);
413            }
414
415            function checkConfiguredProjectNotCreatedButDeleted() {
416                assert.equal(configuredCreated.size, 0);
417                assert.equal(configuredRemoved.size, 1);
418                assert.isTrue(configuredRemoved.has(config.path));
419                configuredRemoved.clear();
420            }
421        });
422
423        it("regression test - should infer typeAcquisition for inferred projects when set undefined", () => {
424            const file1 = { path: "/a/file1.js", content: "" };
425            const host = createServerHost([file1]);
426
427            const projectService = createProjectService(host);
428
429            projectService.openClientFile(file1.path);
430
431            checkNumberOfProjects(projectService, { inferredProjects: 1 });
432            const inferredProject = projectService.inferredProjects[0];
433            checkProjectActualFiles(inferredProject, [file1.path]);
434            inferredProject.setTypeAcquisition(undefined);
435
436            const expected = {
437                enable: true,
438                include: [],
439                exclude: []
440            };
441            assert.deepEqual(inferredProject.getTypeAcquisition(), expected, "typeAcquisition should be inferred for inferred projects");
442        });
443
444        it("Setting compiler options for inferred projects when there are no open files should not schedule any refresh", () => {
445            const host = createServerHost([commonFile1, libFile]);
446            const projectService = createProjectService(host);
447            projectService.setCompilerOptionsForInferredProjects({
448                allowJs: true,
449                target: ScriptTarget.ES2015
450            });
451            host.checkTimeoutQueueLength(0);
452        });
453    });
454}
455