• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1namespace ts.projectSystem {
2    function createHostModuleResolutionTrace(host: TestServerHost & ModuleResolutionHost) {
3        const resolutionTrace: string[] = [];
4        host.trace = resolutionTrace.push.bind(resolutionTrace);
5        return resolutionTrace;
6    }
7
8    describe("unittests:: tsserver:: resolutionCache:: tsserverProjectSystem extra resolution pass in server host", () => {
9        it("can load typings that are proper modules", () => {
10            const file1 = {
11                path: "/a/b/app.js",
12                content: `var x = require("lib")`
13            };
14            const lib = {
15                path: "/a/cache/node_modules/@types/lib/index.d.ts",
16                content: "export let x = 1"
17            };
18            const host: TestServerHost & ModuleResolutionHost = createServerHost([file1, lib]);
19            const resolutionTrace = createHostModuleResolutionTrace(host);
20            const projectService = createProjectService(host, { typingsInstaller: new TestTypingsInstaller("/a/cache", /*throttleLimit*/5, host) });
21
22            projectService.setCompilerOptionsForInferredProjects({ traceResolution: true, allowJs: true });
23            projectService.openClientFile(file1.path);
24            projectService.checkNumberOfProjects({ inferredProjects: 1 });
25            const proj = projectService.inferredProjects[0];
26
27            assert.deepEqual(resolutionTrace, [
28                "======== Resolving module 'lib' from '/a/b/app.js'. ========",
29                "Module resolution kind is not specified, using 'NodeJs'.",
30                "Loading module 'lib' from 'node_modules' folder, target file type 'TypeScript'.",
31                "Directory '/a/b/node_modules' does not exist, skipping all lookups in it.",
32                "Directory '/a/node_modules' does not exist, skipping all lookups in it.",
33                "Directory '/node_modules' does not exist, skipping all lookups in it.",
34                "Loading module 'lib' from 'node_modules' folder, target file type 'JavaScript'.",
35                "Directory '/a/b/node_modules' does not exist, skipping all lookups in it.",
36                "Directory '/a/node_modules' does not exist, skipping all lookups in it.",
37                "Directory '/node_modules' does not exist, skipping all lookups in it.",
38                "======== Module name 'lib' was not resolved. ========",
39                `Auto discovery for typings is enabled in project '${proj.getProjectName()}'. Running extra resolution pass for module 'lib' using cache location '/a/cache'.`,
40                "File '/a/cache/node_modules/lib.d.ts' does not exist.",
41                "File '/a/cache/node_modules/@types/lib/package.json' does not exist.",
42                "File '/a/cache/node_modules/@types/lib.d.ts' does not exist.",
43                "File '/a/cache/node_modules/@types/lib/index.d.ts' exist - use it as a name resolution result.",
44            ]);
45            checkProjectActualFiles(proj, [file1.path, lib.path]);
46        });
47    });
48
49    describe("unittests:: tsserver:: resolutionCache:: tsserverProjectSystem watching @types", () => {
50        it("works correctly when typings are added or removed", () => {
51            const f1 = {
52                path: "/a/b/app.ts",
53                content: "let x = 1;"
54            };
55            const t1 = {
56                path: "/a/b/node_modules/@types/lib1/index.d.ts",
57                content: "export let a: number"
58            };
59            const t2 = {
60                path: "/a/b/node_modules/@types/lib2/index.d.ts",
61                content: "export let b: number"
62            };
63            const tsconfig = {
64                path: "/a/b/tsconfig.json",
65                content: JSON.stringify({
66                    compilerOptions: {},
67                    exclude: ["node_modules"]
68                })
69            };
70            const host = createServerHost([f1, t1, tsconfig]);
71            const projectService = createProjectService(host);
72
73            projectService.openClientFile(f1.path);
74            projectService.checkNumberOfProjects({ configuredProjects: 1 });
75            checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, t1.path, tsconfig.path]);
76
77            // delete t1
78            host.deleteFile(t1.path);
79            // run throttled operation
80            host.runQueuedTimeoutCallbacks();
81
82            projectService.checkNumberOfProjects({ configuredProjects: 1 });
83            checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, tsconfig.path]);
84
85            // create t2
86            host.writeFile(t2.path, t2.content);
87            // run throttled operation
88            host.runQueuedTimeoutCallbacks();
89
90            projectService.checkNumberOfProjects({ configuredProjects: 1 });
91            checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, t2.path, tsconfig.path]);
92        });
93    });
94
95    describe("unittests:: tsserver:: resolutionCache:: tsserverProjectSystem add the missing module file for inferred project", () => {
96        it("should remove the `module not found` error", () => {
97            const moduleFile = {
98                path: "/a/b/moduleFile.ts",
99                content: "export function bar() { };"
100            };
101            const file1 = {
102                path: "/a/b/file1.ts",
103                content: "import * as T from './moduleFile'; T.bar();"
104            };
105            const host = createServerHost([file1]);
106            const session = createSession(host);
107            openFilesForSession([file1], session);
108            const getErrRequest = makeSessionRequest<server.protocol.SemanticDiagnosticsSyncRequestArgs>(
109                server.CommandNames.SemanticDiagnosticsSync,
110                { file: file1.path }
111            );
112            let diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[];
113            verifyDiagnostics(diags, [
114                { diagnosticMessage: Diagnostics.Cannot_find_module_0_or_its_corresponding_type_declarations, errorTextArguments: ["./moduleFile"] }
115            ]);
116
117            host.writeFile(moduleFile.path, moduleFile.content);
118            host.runQueuedTimeoutCallbacks();
119
120            // Make a change to trigger the program rebuild
121            const changeRequest = makeSessionRequest<server.protocol.ChangeRequestArgs>(
122                server.CommandNames.Change,
123                { file: file1.path, line: 1, offset: 44, endLine: 1, endOffset: 44, insertString: "\n" }
124            );
125            session.executeCommand(changeRequest);
126
127            // Recheck
128            diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[];
129            verifyNoDiagnostics(diags);
130        });
131
132        it("npm install @types works", () => {
133            const folderPath = "/a/b/projects/temp";
134            const file1: File = {
135                path: `${folderPath}/a.ts`,
136                content: 'import f = require("pad"); f;'
137            };
138            const host = createServerHost([file1, libFile]);
139            const session = createSession(host, { canUseEvents: true });
140            const service = session.getProjectService();
141            session.executeCommandSeq<protocol.OpenRequest>({
142                command: server.CommandNames.Open,
143                arguments: {
144                    file: file1.path,
145                    fileContent: file1.content,
146                    scriptKindName: "TS",
147                    projectRootPath: folderPath
148                }
149            });
150            checkNumberOfProjects(service, { inferredProjects: 1 });
151
152            const startOffset = file1.content.indexOf('"') + 1;
153            verifyGetErrRequest({
154                session,
155                host,
156                expected: [{
157                    file: file1,
158                    syntax: [],
159                    semantic: [
160                        createDiagnostic({ line: 1, offset: startOffset }, { line: 1, offset: startOffset + '"pad"'.length }, Diagnostics.Cannot_find_module_0_or_its_corresponding_type_declarations, ["pad"])
161                    ],
162                    suggestion: []
163                }]
164            });
165
166            const padIndex: File = {
167                path: `${folderPath}/node_modules/@types/pad/index.d.ts`,
168                content: "export = pad;declare function pad(length: number, text: string, char ?: string): string;"
169            };
170            host.ensureFileOrFolder(padIndex, /*ignoreWatchInvokedWithTriggerAsFileCreate*/ true);
171            host.runQueuedTimeoutCallbacks(); // Scheduled invalidation of resolutions
172            host.runQueuedTimeoutCallbacks(); // Actual update
173            checkProjectUpdatedInBackgroundEvent(session, [file1.path]);
174            session.clearMessages();
175
176            host.runQueuedTimeoutCallbacks();
177            checkErrorMessage(session, "syntaxDiag", { file: file1.path, diagnostics: [] });
178            session.clearMessages();
179
180            host.runQueuedImmediateCallbacks();
181            checkErrorMessage(session, "semanticDiag", { file: file1.path, diagnostics: [] });
182        });
183
184        it("suggestion diagnostics", () => {
185            const file: File = {
186                path: "/a.js",
187                content: "function f(p) {}",
188            };
189
190            const host = createServerHost([file]);
191            const session = createSession(host, { canUseEvents: true });
192            const service = session.getProjectService();
193
194            session.executeCommandSeq<protocol.OpenRequest>({
195                command: server.CommandNames.Open,
196                arguments: { file: file.path, fileContent: file.content },
197            });
198
199            checkNumberOfProjects(service, { inferredProjects: 1 });
200            session.clearMessages();
201            host.checkTimeoutQueueLength(0);
202            verifyGetErrRequest({
203                session,
204                host,
205                expected: [{
206                    file,
207                    syntax: [],
208                    semantic: [],
209                    suggestion: [
210                        createDiagnostic({ line: 1, offset: 12 }, { line: 1, offset: 13 }, Diagnostics._0_is_declared_but_its_value_is_never_read, ["p"], "suggestion", /*reportsUnnecessary*/ true),
211                    ]
212                }]
213            });
214        });
215
216        it("disable suggestion diagnostics", () => {
217            const file: File = {
218                path: "/a.js",
219                content: 'require("b")',
220            };
221
222            const host = createServerHost([file]);
223            const session = createSession(host, { canUseEvents: true });
224            const service = session.getProjectService();
225
226            session.executeCommandSeq<protocol.OpenRequest>({
227                command: server.CommandNames.Open,
228                arguments: { file: file.path, fileContent: file.content },
229            });
230
231            session.executeCommandSeq<protocol.ConfigureRequest>({
232                command: server.CommandNames.Configure,
233                arguments: {
234                    preferences: { disableSuggestions: true }
235                },
236            });
237
238            checkNumberOfProjects(service, { inferredProjects: 1 });
239            session.clearMessages();
240            host.checkTimeoutQueueLength(0);
241            verifyGetErrRequest({
242                session,
243                host,
244                expected: [{
245                    file,
246                    syntax: [],
247                    semantic: []
248                }]
249            });
250        });
251
252        it("suppressed diagnostic events", () => {
253            const file: File = {
254                path: "/a.ts",
255                content: "1 = 2;",
256            };
257
258            const host = createServerHost([file]);
259            const session = createSession(host, { canUseEvents: true, suppressDiagnosticEvents: true });
260            const service = session.getProjectService();
261
262            session.executeCommandSeq<protocol.OpenRequest>({
263                command: server.CommandNames.Open,
264                arguments: { file: file.path, fileContent: file.content },
265            });
266
267            checkNumberOfProjects(service, { inferredProjects: 1 });
268
269            host.checkTimeoutQueueLength(0);
270            checkNoDiagnosticEvents(session);
271
272            session.clearMessages();
273
274            let expectedSequenceId = session.getNextSeq();
275
276            session.executeCommandSeq<protocol.GeterrRequest>({
277                command: server.CommandNames.Geterr,
278                arguments: {
279                    delay: 0,
280                    files: [file.path],
281                }
282            });
283
284            host.checkTimeoutQueueLength(0);
285            checkNoDiagnosticEvents(session);
286
287            checkCompleteEvent(session, 1, expectedSequenceId);
288
289            session.clearMessages();
290
291            expectedSequenceId = session.getNextSeq();
292
293            session.executeCommandSeq<protocol.GeterrForProjectRequest>({
294                command: server.CommandNames.Geterr,
295                arguments: {
296                    delay: 0,
297                    file: file.path,
298                }
299            });
300
301            host.checkTimeoutQueueLength(0);
302            checkNoDiagnosticEvents(session);
303
304            checkCompleteEvent(session, 1, expectedSequenceId);
305
306            session.clearMessages();
307        });
308    });
309
310    describe("unittests:: tsserver:: resolutionCache:: tsserverProjectSystem rename a module file and rename back", () => {
311        it("should restore the states for inferred projects", () => {
312            const moduleFile = {
313                path: "/a/b/moduleFile.ts",
314                content: "export function bar() { };"
315            };
316            const file1 = {
317                path: "/a/b/file1.ts",
318                content: "import * as T from './moduleFile'; T.bar();"
319            };
320            const host = createServerHost([moduleFile, file1]);
321            const session = createSession(host);
322
323            openFilesForSession([file1], session);
324            const getErrRequest = makeSessionRequest<server.protocol.SemanticDiagnosticsSyncRequestArgs>(
325                server.CommandNames.SemanticDiagnosticsSync,
326                { file: file1.path }
327            );
328            let diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[];
329            verifyNoDiagnostics(diags);
330
331            const moduleFileNewPath = "/a/b/moduleFile1.ts";
332            host.renameFile(moduleFile.path, moduleFileNewPath);
333            host.runQueuedTimeoutCallbacks();
334            diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[];
335            verifyDiagnostics(diags, [
336                { diagnosticMessage: Diagnostics.Cannot_find_module_0_or_its_corresponding_type_declarations, errorTextArguments: ["./moduleFile"] }
337            ]);
338            assert.equal(diags.length, 1);
339
340            host.renameFile(moduleFileNewPath, moduleFile.path);
341            host.runQueuedTimeoutCallbacks();
342
343            // Make a change to trigger the program rebuild
344            const changeRequest = makeSessionRequest<server.protocol.ChangeRequestArgs>(
345                server.CommandNames.Change,
346                { file: file1.path, line: 1, offset: 44, endLine: 1, endOffset: 44, insertString: "\n" }
347            );
348            session.executeCommand(changeRequest);
349            host.runQueuedTimeoutCallbacks();
350
351            diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[];
352            verifyNoDiagnostics(diags);
353        });
354
355        it("should restore the states for configured projects", () => {
356            const moduleFile = {
357                path: "/a/b/moduleFile.ts",
358                content: "export function bar() { };"
359            };
360            const file1 = {
361                path: "/a/b/file1.ts",
362                content: "import * as T from './moduleFile'; T.bar();"
363            };
364            const configFile = {
365                path: "/a/b/tsconfig.json",
366                content: `{}`
367            };
368            const host = createServerHost([moduleFile, file1, configFile]);
369            const session = createSession(host);
370
371            openFilesForSession([file1], session);
372            const getErrRequest = makeSessionRequest<server.protocol.SemanticDiagnosticsSyncRequestArgs>(
373                server.CommandNames.SemanticDiagnosticsSync,
374                { file: file1.path }
375            );
376            let diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[];
377            verifyNoDiagnostics(diags);
378
379            const moduleFileNewPath = "/a/b/moduleFile1.ts";
380            host.renameFile(moduleFile.path, moduleFileNewPath);
381            host.runQueuedTimeoutCallbacks();
382            diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[];
383            verifyDiagnostics(diags, [
384                { diagnosticMessage: Diagnostics.Cannot_find_module_0_or_its_corresponding_type_declarations, errorTextArguments: ["./moduleFile"] }
385            ]);
386
387            host.renameFile(moduleFileNewPath, moduleFile.path);
388            host.runQueuedTimeoutCallbacks();
389            diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[];
390            verifyNoDiagnostics(diags);
391        });
392
393        it("should property handle missing config files", () => {
394            const f1 = {
395                path: "/a/b/app.ts",
396                content: "let x = 1"
397            };
398            const config = {
399                path: "/a/b/tsconfig.json",
400                content: "{}"
401            };
402            const projectName = "project1";
403            const host = createServerHost([f1]);
404            const projectService = createProjectService(host);
405            projectService.openExternalProject({ rootFiles: toExternalFiles([f1.path, config.path]), options: {}, projectFileName: projectName });
406
407            // should have one external project since config file is missing
408            projectService.checkNumberOfProjects({ externalProjects: 1 });
409
410            host.writeFile(config.path, config.content);
411            projectService.openExternalProject({ rootFiles: toExternalFiles([f1.path, config.path]), options: {}, projectFileName: projectName });
412            projectService.checkNumberOfProjects({ configuredProjects: 1 });
413        });
414
415        it("types should load from config file path if config exists", () => {
416            const f1 = {
417                path: "/a/b/app.ts",
418                content: "let x = 1"
419            };
420            const config = {
421                path: "/a/b/tsconfig.json",
422                content: JSON.stringify({ compilerOptions: { types: ["node"], typeRoots: [] } })
423            };
424            const node = {
425                path: "/a/b/node_modules/@types/node/index.d.ts",
426                content: "declare var process: any"
427            };
428            const cwd = {
429                path: "/a/c"
430            };
431            const host = createServerHost([f1, config, node, cwd], { currentDirectory: cwd.path });
432            const projectService = createProjectService(host);
433            projectService.openClientFile(f1.path);
434            projectService.checkNumberOfProjects({ configuredProjects: 1 });
435            checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, node.path, config.path]);
436        });
437    });
438
439    describe("unittests:: tsserver:: resolutionCache:: tsserverProjectSystem module resolution caching", () => {
440        const configFile: File = {
441            path: `${tscWatch.projectRoot}/tsconfig.json`,
442            content: JSON.stringify({ compilerOptions: { traceResolution: true } })
443        };
444
445        function getModules(module1Path: string, module2Path: string) {
446            const module1: File = {
447                path: module1Path,
448                content: `export function module1() {}`
449            };
450            const module2: File = {
451                path: module2Path,
452                content: `export function module2() {}`
453            };
454            return { module1, module2 };
455        }
456
457        function verifyTrace(resolutionTrace: string[], expected: string[]) {
458            assert.deepEqual(resolutionTrace, expected);
459            resolutionTrace.length = 0;
460        }
461
462        function getExpectedFileDoesNotExistResolutionTrace(host: TestServerHost, expectedTrace: string[], foundModule: boolean, module: File, directory: string, file: string, ignoreIfParentMissing?: boolean) {
463            if (!foundModule) {
464                const path = combinePaths(directory, file);
465                if (!ignoreIfParentMissing || host.directoryExists(getDirectoryPath(path))) {
466                    if (module.path === path) {
467                        foundModule = true;
468                    }
469                    else {
470                        expectedTrace.push(`File '${path}' does not exist.`);
471                    }
472                }
473            }
474            return foundModule;
475        }
476
477        function getExpectedMissedLocationResolutionTrace(host: TestServerHost, expectedTrace: string[], dirPath: string, module: File, moduleName: string, useNodeModules: boolean, cacheLocation?: string) {
478            let foundModule = false;
479            forEachAncestorDirectory(dirPath, dirPath => {
480                if (dirPath === cacheLocation) {
481                    return foundModule;
482                }
483
484                const directory = useNodeModules ? combinePaths(dirPath, nodeModules) : dirPath;
485                if (useNodeModules && !foundModule && !host.directoryExists(directory)) {
486                    expectedTrace.push(`Directory '${directory}' does not exist, skipping all lookups in it.`);
487                    return undefined;
488                }
489                foundModule = getExpectedFileDoesNotExistResolutionTrace(host, expectedTrace, foundModule, module, directory, `${moduleName}/package.json`, /*ignoreIfParentMissing*/ true);
490                foundModule = getExpectedFileDoesNotExistResolutionTrace(host, expectedTrace, foundModule, module, directory, `${moduleName}.ts`);
491                foundModule = getExpectedFileDoesNotExistResolutionTrace(host, expectedTrace, foundModule, module, directory, `${moduleName}.tsx`);
492                foundModule = getExpectedFileDoesNotExistResolutionTrace(host, expectedTrace, foundModule, module, directory, `${moduleName}.d.ts`);
493                foundModule = getExpectedFileDoesNotExistResolutionTrace(host, expectedTrace, foundModule, module, directory, `${moduleName}.ets`);
494                foundModule = getExpectedFileDoesNotExistResolutionTrace(host, expectedTrace, foundModule, module, directory, `${moduleName}/index.ts`, /*ignoreIfParentMissing*/ true);
495                if (useNodeModules && !foundModule) {
496                    expectedTrace.push(`Directory '${directory}/@types' does not exist, skipping all lookups in it.`);
497                }
498                return foundModule ? true : undefined;
499            });
500        }
501
502        function getExpectedResolutionTraceHeader(expectedTrace: string[], file: File, moduleName: string) {
503            expectedTrace.push(
504                `======== Resolving module '${moduleName}' from '${file.path}'. ========`,
505                `Module resolution kind is not specified, using 'NodeJs'.`
506            );
507        }
508
509        function getExpectedResolutionTraceFooter(expectedTrace: string[], module: File, moduleName: string, addRealPathTrace: boolean, ignoreModuleFileFound?: boolean) {
510            if (!ignoreModuleFileFound) {
511                expectedTrace.push(`File '${module.path}' exist - use it as a name resolution result.`);
512            }
513            if (addRealPathTrace) {
514                expectedTrace.push(`Resolving real path for '${module.path}', result '${module.path}'.`);
515            }
516            expectedTrace.push(`======== Module name '${moduleName}' was successfully resolved to '${module.path}'. ========`);
517        }
518
519        function getExpectedRelativeModuleResolutionTrace(host: TestServerHost, file: File, module: File, moduleName: string, expectedTrace: string[] = []) {
520            getExpectedResolutionTraceHeader(expectedTrace, file, moduleName);
521            expectedTrace.push(`Loading module as file / folder, candidate module location '${removeFileExtension(module.path)}', target file type 'TypeScript'.`);
522            getExpectedMissedLocationResolutionTrace(host, expectedTrace, getDirectoryPath(normalizePath(combinePaths(getDirectoryPath(file.path), moduleName))), module, moduleName.substring(moduleName.lastIndexOf("/") + 1), /*useNodeModules*/ false);
523            getExpectedResolutionTraceFooter(expectedTrace, module, moduleName, /*addRealPathTrace*/ false);
524            return expectedTrace;
525        }
526
527        function getExpectedNonRelativeModuleResolutionTrace(host: TestServerHost, file: File, module: File, moduleName: string, expectedTrace: string[] = []) {
528            getExpectedResolutionTraceHeader(expectedTrace, file, moduleName);
529            expectedTrace.push(`Loading module '${moduleName}' from 'node_modules' folder, target file type 'TypeScript'.`);
530            getExpectedMissedLocationResolutionTrace(host, expectedTrace, getDirectoryPath(file.path), module, moduleName, /*useNodeModules*/ true);
531            getExpectedResolutionTraceFooter(expectedTrace, module, moduleName, /*addRealPathTrace*/ true);
532            return expectedTrace;
533        }
534
535        function getExpectedNonRelativeModuleResolutionFromCacheTrace(host: TestServerHost, file: File, module: File, moduleName: string, cacheLocation: string, expectedTrace: string[] = []) {
536            getExpectedResolutionTraceHeader(expectedTrace, file, moduleName);
537            expectedTrace.push(`Loading module '${moduleName}' from 'node_modules' folder, target file type 'TypeScript'.`);
538            getExpectedMissedLocationResolutionTrace(host, expectedTrace, getDirectoryPath(file.path), module, moduleName, /*useNodeModules*/ true, cacheLocation);
539            expectedTrace.push(`Resolution for module '${moduleName}' was found in cache from location '${cacheLocation}'.`);
540            getExpectedResolutionTraceFooter(expectedTrace, module, moduleName, /*addRealPathTrace*/ false, /*ignoreModuleFileFound*/ true);
541            return expectedTrace;
542        }
543
544        function getExpectedReusingResolutionFromOldProgram(file: File, moduleName: string) {
545            return `Reusing resolution of module '${moduleName}' to file '${file.path}' from old program.`;
546        }
547
548        function verifyWatchesWithConfigFile(host: TestServerHost, files: File[], openFile: File, extraExpectedDirectories?: readonly string[]) {
549            const expectedRecursiveDirectories = new Set([tscWatch.projectRoot, `${tscWatch.projectRoot}/${nodeModulesAtTypes}`, ...(extraExpectedDirectories || emptyArray)]);
550            checkWatchedFiles(host, mapDefined(files, f => {
551                if (f === openFile) {
552                    return undefined;
553                }
554                const indexOfNodeModules = f.path.indexOf("/node_modules/");
555                if (indexOfNodeModules === -1) {
556                    return f.path;
557                }
558                expectedRecursiveDirectories.add(f.path.substr(0, indexOfNodeModules + "/node_modules".length));
559                return undefined;
560            }));
561            checkWatchedDirectories(host, [], /*recursive*/ false);
562            checkWatchedDirectories(host, arrayFrom(expectedRecursiveDirectories.values()), /*recursive*/ true);
563        }
564
565        describe("from files in same folder", () => {
566            function getFiles(fileContent: string) {
567                const file1: File = {
568                    path: `${tscWatch.projectRoot}/src/file1.ts`,
569                    content: fileContent
570                };
571                const file2: File = {
572                    path: `${tscWatch.projectRoot}/src/file2.ts`,
573                    content: fileContent
574                };
575                return { file1, file2 };
576            }
577
578            it("relative module name", () => {
579                const module1Name = "./module1";
580                const module2Name = "../module2";
581                const fileContent = `import { module1 } from "${module1Name}";import { module2 } from "${module2Name}";`;
582                const { file1, file2 } = getFiles(fileContent);
583                const { module1, module2 } = getModules(`${tscWatch.projectRoot}/src/module1.ts`, `${tscWatch.projectRoot}/module2.ts`);
584                const files = [module1, module2, file1, file2, configFile, libFile];
585                const host = createServerHost(files);
586                const resolutionTrace = createHostModuleResolutionTrace(host);
587                const service = createProjectService(host);
588                service.openClientFile(file1.path);
589                const expectedTrace = getExpectedRelativeModuleResolutionTrace(host, file1, module1, module1Name);
590                getExpectedRelativeModuleResolutionTrace(host, file1, module2, module2Name, expectedTrace);
591                verifyTrace(resolutionTrace, expectedTrace);
592                verifyWatchesWithConfigFile(host, files, file1);
593
594                host.writeFile(file1.path, file1.content + fileContent);
595                host.writeFile(file2.path, file2.content + fileContent);
596                host.runQueuedTimeoutCallbacks();
597                verifyTrace(resolutionTrace, [
598                    getExpectedReusingResolutionFromOldProgram(file1, module1Name),
599                    getExpectedReusingResolutionFromOldProgram(file1, module2Name)
600                ]);
601                verifyWatchesWithConfigFile(host, files, file1);
602            });
603
604            it("non relative module name", () => {
605                const expectedNonRelativeDirectories = [`${tscWatch.projectRoot}/node_modules`, `${tscWatch.projectRoot}/src`];
606                const module1Name = "module1";
607                const module2Name = "module2";
608                const fileContent = `import { module1 } from "${module1Name}";import { module2 } from "${module2Name}";`;
609                const { file1, file2 } = getFiles(fileContent);
610                const { module1, module2 } = getModules(`${tscWatch.projectRoot}/src/node_modules/module1/index.ts`, `${tscWatch.projectRoot}/node_modules/module2/index.ts`);
611                const files = [module1, module2, file1, file2, configFile, libFile];
612                const host = createServerHost(files);
613                const resolutionTrace = createHostModuleResolutionTrace(host);
614                const service = createProjectService(host);
615                service.openClientFile(file1.path);
616                const expectedTrace = getExpectedNonRelativeModuleResolutionTrace(host, file1, module1, module1Name);
617                getExpectedNonRelativeModuleResolutionTrace(host, file1, module2, module2Name, expectedTrace);
618                verifyTrace(resolutionTrace, expectedTrace);
619                verifyWatchesWithConfigFile(host, files, file1, expectedNonRelativeDirectories);
620
621                host.writeFile(file1.path, file1.content + fileContent);
622                host.writeFile(file2.path, file2.content + fileContent);
623                host.runQueuedTimeoutCallbacks();
624                verifyTrace(resolutionTrace, [
625                    getExpectedReusingResolutionFromOldProgram(file1, module1Name),
626                    getExpectedReusingResolutionFromOldProgram(file1, module2Name)
627                ]);
628                verifyWatchesWithConfigFile(host, files, file1, expectedNonRelativeDirectories);
629            });
630        });
631
632        describe("from files in different folders", () => {
633            function getFiles(fileContent1: string, fileContent2 = fileContent1, fileContent3 = fileContent1, fileContent4 = fileContent1) {
634                const file1: File = {
635                    path: `${tscWatch.projectRoot}/product/src/file1.ts`,
636                    content: fileContent1
637                };
638                const file2: File = {
639                    path: `${tscWatch.projectRoot}/product/src/feature/file2.ts`,
640                    content: fileContent2
641                };
642                const file3: File = {
643                    path: `${tscWatch.projectRoot}/product/test/src/file3.ts`,
644                    content: fileContent3
645                };
646                const file4: File = {
647                    path: `${tscWatch.projectRoot}/product/test/file4.ts`,
648                    content: fileContent4
649                };
650                return { file1, file2, file3, file4 };
651            }
652
653            it("relative module name", () => {
654                const module1Name = "./module1";
655                const module2Name = "../module2";
656                const module3Name = "../module1";
657                const module4Name = "../../module2";
658                const module5Name = "../../src/module1";
659                const module6Name = "../src/module1";
660                const fileContent1 = `import { module1 } from "${module1Name}";import { module2 } from "${module2Name}";`;
661                const fileContent2 = `import { module1 } from "${module3Name}";import { module2 } from "${module4Name}";`;
662                const fileContent3 = `import { module1 } from "${module5Name}";import { module2 } from "${module4Name}";`;
663                const fileContent4 = `import { module1 } from "${module6Name}";import { module2 } from "${module2Name}";`;
664                const { file1, file2, file3, file4 } = getFiles(fileContent1, fileContent2, fileContent3, fileContent4);
665                const { module1, module2 } = getModules(`${tscWatch.projectRoot}/product/src/module1.ts`, `${tscWatch.projectRoot}/product/module2.ts`);
666                const files = [module1, module2, file1, file2, file3, file4, configFile, libFile];
667                const host = createServerHost(files);
668                const resolutionTrace = createHostModuleResolutionTrace(host);
669                const service = createProjectService(host);
670                service.openClientFile(file1.path);
671                const expectedTrace = getExpectedRelativeModuleResolutionTrace(host, file1, module1, module1Name);
672                getExpectedRelativeModuleResolutionTrace(host, file1, module2, module2Name, expectedTrace);
673                getExpectedRelativeModuleResolutionTrace(host, file2, module1, module3Name, expectedTrace);
674                getExpectedRelativeModuleResolutionTrace(host, file2, module2, module4Name, expectedTrace);
675                getExpectedRelativeModuleResolutionTrace(host, file4, module1, module6Name, expectedTrace);
676                getExpectedRelativeModuleResolutionTrace(host, file4, module2, module2Name, expectedTrace);
677                getExpectedRelativeModuleResolutionTrace(host, file3, module1, module5Name, expectedTrace);
678                getExpectedRelativeModuleResolutionTrace(host, file3, module2, module4Name, expectedTrace);
679                verifyTrace(resolutionTrace, expectedTrace);
680                verifyWatchesWithConfigFile(host, files, file1);
681
682                host.writeFile(file1.path, file1.content + fileContent1);
683                host.writeFile(file2.path, file2.content + fileContent2);
684                host.writeFile(file3.path, file3.content + fileContent3);
685                host.writeFile(file4.path, file4.content + fileContent4);
686                host.runQueuedTimeoutCallbacks();
687
688                verifyTrace(resolutionTrace, [
689                    getExpectedReusingResolutionFromOldProgram(file1, module1Name),
690                    getExpectedReusingResolutionFromOldProgram(file1, module2Name)
691                ]);
692                verifyWatchesWithConfigFile(host, files, file1);
693            });
694
695            it("non relative module name", () => {
696                const expectedNonRelativeDirectories = [`${tscWatch.projectRoot}/node_modules`, `${tscWatch.projectRoot}/product`];
697                const module1Name = "module1";
698                const module2Name = "module2";
699                const fileContent = `import { module1 } from "${module1Name}";import { module2 } from "${module2Name}";`;
700                const { file1, file2, file3, file4 } = getFiles(fileContent);
701                const { module1, module2 } = getModules(`${tscWatch.projectRoot}/product/node_modules/module1/index.ts`, `${tscWatch.projectRoot}/node_modules/module2/index.ts`);
702                const files = [module1, module2, file1, file2, file3, file4, configFile, libFile];
703                const host = createServerHost(files);
704                const resolutionTrace = createHostModuleResolutionTrace(host);
705                const service = createProjectService(host);
706                service.openClientFile(file1.path);
707                const expectedTrace = getExpectedNonRelativeModuleResolutionTrace(host, file1, module1, module1Name);
708                getExpectedNonRelativeModuleResolutionTrace(host, file1, module2, module2Name, expectedTrace);
709                getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file2, module1, module1Name, getDirectoryPath(file1.path), expectedTrace);
710                getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file2, module2, module2Name, getDirectoryPath(file1.path), expectedTrace);
711                getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file4, module1, module1Name, `${tscWatch.projectRoot}/product`, expectedTrace);
712                getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file4, module2, module2Name, `${tscWatch.projectRoot}/product`, expectedTrace);
713                getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file3, module1, module1Name, getDirectoryPath(file4.path), expectedTrace);
714                getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file3, module2, module2Name, getDirectoryPath(file4.path), expectedTrace);
715                verifyTrace(resolutionTrace, expectedTrace);
716                verifyWatchesWithConfigFile(host, files, file1, expectedNonRelativeDirectories);
717
718                host.writeFile(file1.path, file1.content + fileContent);
719                host.writeFile(file2.path, file2.content + fileContent);
720                host.writeFile(file3.path, file3.content + fileContent);
721                host.writeFile(file4.path, file4.content + fileContent);
722                host.runQueuedTimeoutCallbacks();
723
724                verifyTrace(resolutionTrace, [
725                    getExpectedReusingResolutionFromOldProgram(file1, module1Name),
726                    getExpectedReusingResolutionFromOldProgram(file1, module2Name)
727                ]);
728                verifyWatchesWithConfigFile(host, files, file1, expectedNonRelativeDirectories);
729            });
730
731            it("non relative module name from inferred project", () => {
732                const module1Name = "module1";
733                const module2Name = "module2";
734                const file2Name = "./feature/file2";
735                const file3Name = "../test/src/file3";
736                const file4Name = "../test/file4";
737                const importModuleContent = `import { module1 } from "${module1Name}";import { module2 } from "${module2Name}";`;
738                const { file1, file2, file3, file4 } = getFiles(`import "${file2Name}"; import "${file4Name}"; import "${file3Name}"; ${importModuleContent}`, importModuleContent, importModuleContent, importModuleContent);
739                const { module1, module2 } = getModules(`${tscWatch.projectRoot}/product/node_modules/module1/index.ts`, `${tscWatch.projectRoot}/node_modules/module2/index.ts`);
740                const files = [module1, module2, file1, file2, file3, file4, libFile];
741                const host = createServerHost(files);
742                const resolutionTrace = createHostModuleResolutionTrace(host);
743                const service = createProjectService(host);
744                service.setCompilerOptionsForInferredProjects({ traceResolution: true });
745                service.openClientFile(file1.path);
746                const expectedTrace = getExpectedRelativeModuleResolutionTrace(host, file1, file2, file2Name);
747                getExpectedRelativeModuleResolutionTrace(host, file1, file4, file4Name, expectedTrace);
748                getExpectedRelativeModuleResolutionTrace(host, file1, file3, file3Name, expectedTrace);
749                getExpectedNonRelativeModuleResolutionTrace(host, file1, module1, module1Name, expectedTrace);
750                getExpectedNonRelativeModuleResolutionTrace(host, file1, module2, module2Name, expectedTrace);
751                getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file2, module1, module1Name, getDirectoryPath(file1.path), expectedTrace);
752                getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file2, module2, module2Name, getDirectoryPath(file1.path), expectedTrace);
753                getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file4, module1, module1Name, `${tscWatch.projectRoot}/product`, expectedTrace);
754                getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file4, module2, module2Name, `${tscWatch.projectRoot}/product`, expectedTrace);
755                getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file3, module1, module1Name, getDirectoryPath(file4.path), expectedTrace);
756                getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file3, module2, module2Name, getDirectoryPath(file4.path), expectedTrace);
757                verifyTrace(resolutionTrace, expectedTrace);
758
759                const currentDirectory = getDirectoryPath(file1.path);
760                const watchedFiles = mapDefined(files, f => f === file1 || f.path.indexOf("/node_modules/") !== -1 ? undefined : f.path)
761                    .concat(getConfigFilesToWatch(`${tscWatch.projectRoot}/product/src`));
762                const watchedRecursiveDirectories = getTypeRootsFromLocation(currentDirectory).concat([
763                    `${currentDirectory}/node_modules`, `${currentDirectory}/feature`, `${tscWatch.projectRoot}/product/${nodeModules}`,
764                    `${tscWatch.projectRoot}/${nodeModules}`, `${tscWatch.projectRoot}/product/test/${nodeModules}`,
765                    `${tscWatch.projectRoot}/product/test/src/${nodeModules}`
766                ]);
767                checkWatches();
768
769                host.writeFile(file1.path, file1.content + importModuleContent);
770                host.writeFile(file2.path, file2.content + importModuleContent);
771                host.writeFile(file3.path, file3.content + importModuleContent);
772                host.writeFile(file4.path, file4.content + importModuleContent);
773                host.runQueuedTimeoutCallbacks();
774
775                verifyTrace(resolutionTrace, [
776                    getExpectedReusingResolutionFromOldProgram(file1, file2Name),
777                    getExpectedReusingResolutionFromOldProgram(file1, file4Name),
778                    getExpectedReusingResolutionFromOldProgram(file1, file3Name),
779                    getExpectedReusingResolutionFromOldProgram(file1, module1Name),
780                    getExpectedReusingResolutionFromOldProgram(file1, module2Name)
781                ]);
782                checkWatches();
783
784                function checkWatches() {
785                    checkWatchedFiles(host, watchedFiles);
786                    checkWatchedDirectories(host, [], /*recursive*/ false);
787                    checkWatchedDirectories(host, watchedRecursiveDirectories, /*recursive*/ true);
788                }
789            });
790        });
791
792        describe("when watching directories for failed lookup locations in amd resolution", () => {
793            const nodeFile: File = {
794                path: `${tscWatch.projectRoot}/src/typings/node.d.ts`,
795                content: `
796declare module "fs" {
797    export interface something {
798    }
799}`
800            };
801            const electronFile: File = {
802                path: `${tscWatch.projectRoot}/src/typings/electron.d.ts`,
803                content: `
804declare module 'original-fs' {
805    import * as fs from 'fs';
806    export = fs;
807}`
808            };
809            const srcFile: File = {
810                path: `${tscWatch.projectRoot}/src/somefolder/srcfile.ts`,
811                content: `
812import { x } from "somefolder/module1";
813import { x } from "somefolder/module2";
814const y = x;`
815            };
816            const moduleFile: File = {
817                path: `${tscWatch.projectRoot}/src/somefolder/module1.ts`,
818                content: `
819export const x = 10;`
820            };
821            const configFile: File = {
822                path: `${tscWatch.projectRoot}/src/tsconfig.json`,
823                content: JSON.stringify({
824                    compilerOptions: {
825                        module: "amd",
826                        moduleResolution: "classic",
827                        target: "es5",
828                        outDir: "../out",
829                        baseUrl: "./",
830                        typeRoots: ["typings"]
831                    }
832                })
833            };
834
835            function verifyModuleResolution(useNodeFile: boolean) {
836                const files = [...(useNodeFile ? [nodeFile] : []), electronFile, srcFile, moduleFile, configFile, libFile];
837                const host = createServerHost(files);
838                const service = createProjectService(host);
839                service.openClientFile(srcFile.path, srcFile.content, ScriptKind.TS, tscWatch.projectRoot);
840                checkProjectActualFiles(service.configuredProjects.get(configFile.path)!, files.map(f => f.path));
841                checkWatchedFilesDetailed(host, mapDefined(files, f => f === srcFile ? undefined : f.path), 1);
842                if (useNodeFile) {
843                    checkWatchedDirectories(host, emptyArray,  /*recursive*/ false); // since fs resolves to ambient module, shouldnt watch failed lookup
844                }
845                else {
846                    checkWatchedDirectoriesDetailed(host, [`${tscWatch.projectRoot}`, `${tscWatch.projectRoot}/src`], 1,  /*recursive*/ false); // failed lookup for fs
847                }
848                const expectedWatchedDirectories = new Map<string, number>();
849                expectedWatchedDirectories.set(`${tscWatch.projectRoot}/src`, 1); // Wild card
850                expectedWatchedDirectories.set(`${tscWatch.projectRoot}/src/somefolder`, 1); // failedLookup for somefolder/module2
851                expectedWatchedDirectories.set(`${tscWatch.projectRoot}/src/node_modules`, 1); // failed lookup for somefolder/module2
852                expectedWatchedDirectories.set(`${tscWatch.projectRoot}/somefolder`, 1); // failed lookup for somefolder/module2
853                expectedWatchedDirectories.set(`${tscWatch.projectRoot}/node_modules`, 1); // failed lookup for with node_modules/@types/fs
854                expectedWatchedDirectories.set(`${tscWatch.projectRoot}/src/typings`, useNodeFile ? 1 : 2); // typeroot directory + failed lookup if not using node file
855                checkWatchedDirectoriesDetailed(host, expectedWatchedDirectories, /*recursive*/ true);
856            }
857
858            it("when resolves to ambient module", () => {
859                verifyModuleResolution(/*useNodeFile*/ true);
860            });
861
862            it("when resolution fails", () => {
863                verifyModuleResolution(/*useNodeFile*/ false);
864            });
865        });
866
867        describe("ignores files/folder changes in node_modules that start with '.'", () => {
868            const npmCacheFile: File = {
869                path: `${tscWatch.projectRoot}/node_modules/.cache/babel-loader/89c02171edab901b9926470ba6d5677e.ts`,
870                content: JSON.stringify({ something: 10 })
871            };
872            const file1: File = {
873                path: `${tscWatch.projectRoot}/test.ts`,
874                content: `import { x } from "somemodule";`
875            };
876            const file2: File = {
877                path: `${tscWatch.projectRoot}/node_modules/somemodule/index.d.ts`,
878                content: `export const x = 10;`
879            };
880            it("when watching node_modules in inferred project for failed lookup/closed script infos", () => {
881                const files = [libFile, file1, file2];
882                const host = createServerHost(files);
883                const service = createProjectService(host);
884                service.openClientFile(file1.path);
885                checkNumberOfProjects(service, { inferredProjects: 1 });
886                const project = service.inferredProjects[0];
887                checkProjectActualFiles(project, files.map(f => f.path));
888                host.checkTimeoutQueueLength(0);
889
890                host.ensureFileOrFolder(npmCacheFile);
891                host.checkTimeoutQueueLength(0);
892            });
893            it("when watching node_modules as part of wild card directories in config project", () => {
894                const config: File = {
895                    path: `${tscWatch.projectRoot}/tsconfig.json`,
896                    content: "{}"
897                };
898                const files = [libFile, file1, file2, config];
899                const host = createServerHost(files);
900                const service = createProjectService(host);
901                service.openClientFile(file1.path);
902                checkNumberOfProjects(service, { configuredProjects: 1 });
903                const project = Debug.checkDefined(service.configuredProjects.get(config.path));
904                checkProjectActualFiles(project, files.map(f => f.path));
905                host.checkTimeoutQueueLength(0);
906
907                host.ensureFileOrFolder(npmCacheFile);
908                host.checkTimeoutQueueLength(0);
909            });
910        });
911
912        describe("avoid unnecessary invalidation", () => {
913            it("unnecessary lookup invalidation on save", () => {
914                const expectedNonRelativeDirectories = [`${tscWatch.projectRoot}/node_modules`, `${tscWatch.projectRoot}/src`];
915                const module1Name = "module1";
916                const module2Name = "module2";
917                const fileContent = `import { module1 } from "${module1Name}";import { module2 } from "${module2Name}";`;
918                const file1: File = {
919                    path: `${tscWatch.projectRoot}/src/file1.ts`,
920                    content: fileContent
921                };
922                const { module1, module2 } = getModules(`${tscWatch.projectRoot}/src/node_modules/module1/index.ts`, `${tscWatch.projectRoot}/node_modules/module2/index.ts`);
923                const files = [module1, module2, file1, configFile, libFile];
924                const host = createServerHost(files);
925                const resolutionTrace = createHostModuleResolutionTrace(host);
926                const service = createProjectService(host);
927                service.openClientFile(file1.path);
928                const expectedTrace = getExpectedNonRelativeModuleResolutionTrace(host, file1, module1, module1Name);
929                getExpectedNonRelativeModuleResolutionTrace(host, file1, module2, module2Name, expectedTrace);
930                verifyTrace(resolutionTrace, expectedTrace);
931                verifyWatchesWithConfigFile(host, files, file1, expectedNonRelativeDirectories);
932
933                // invoke callback to simulate saving
934                host.modifyFile(file1.path, file1.content, { invokeFileDeleteCreateAsPartInsteadOfChange: true });
935                host.checkTimeoutQueueLengthAndRun(0);
936            });
937        });
938    });
939}
940