• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1namespace ts.projectSystem {
2    function getNumberOfWatchesInvokedForRecursiveWatches(recursiveWatchedDirs: string[], file: string) {
3        return countWhere(recursiveWatchedDirs, dir => file.length > dir.length && startsWith(file, dir) && file[dir.length] === directorySeparator);
4    }
5
6    describe("unittests:: tsserver:: CachingFileSystemInformation:: tsserverProjectSystem CachingFileSystemInformation", () => {
7        enum CalledMapsWithSingleArg {
8            fileExists = "fileExists",
9            directoryExists = "directoryExists",
10            getDirectories = "getDirectories",
11            readFile = "readFile"
12        }
13        enum CalledMapsWithFiveArgs {
14            readDirectory = "readDirectory"
15        }
16        type CalledMaps = CalledMapsWithSingleArg | CalledMapsWithFiveArgs;
17        type CalledWithFiveArgs = [readonly string[], readonly string[], readonly string[], number];
18        function createCallsTrackingHost(host: TestServerHost) {
19            const calledMaps: Record<CalledMapsWithSingleArg, MultiMap<string, true>> & Record<CalledMapsWithFiveArgs, MultiMap<string, CalledWithFiveArgs>> = {
20                fileExists: setCallsTrackingWithSingleArgFn(CalledMapsWithSingleArg.fileExists),
21                directoryExists: setCallsTrackingWithSingleArgFn(CalledMapsWithSingleArg.directoryExists),
22                getDirectories: setCallsTrackingWithSingleArgFn(CalledMapsWithSingleArg.getDirectories),
23                readFile: setCallsTrackingWithSingleArgFn(CalledMapsWithSingleArg.readFile),
24                readDirectory: setCallsTrackingWithFiveArgFn(CalledMapsWithFiveArgs.readDirectory)
25            };
26
27            return {
28                verifyNoCall,
29                verifyCalledOnEachEntryNTimes,
30                verifyCalledOnEachEntry,
31                verifyNoHostCalls,
32                verifyNoHostCallsExceptFileExistsOnce,
33                verifyCalledOn,
34                clear
35            };
36
37            function setCallsTrackingWithSingleArgFn(prop: CalledMapsWithSingleArg) {
38                const calledMap = createMultiMap<true>();
39                const cb = (<any>host)[prop].bind(host);
40                (<any>host)[prop] = (f: string) => {
41                    calledMap.add(f, /*value*/ true);
42                    return cb(f);
43                };
44                return calledMap;
45            }
46
47            function setCallsTrackingWithFiveArgFn<U, V, W, X>(prop: CalledMapsWithFiveArgs) {
48                const calledMap = createMultiMap<[U, V, W, X]>();
49                const cb = (<any>host)[prop].bind(host);
50                (<any>host)[prop] = (f: string, arg1?: U, arg2?: V, arg3?: W, arg4?: X) => {
51                    calledMap.add(f, [arg1!, arg2!, arg3!, arg4!]); // TODO: GH#18217
52                    return cb(f, arg1, arg2, arg3, arg4);
53                };
54                return calledMap;
55            }
56
57            function verifyCalledOn(callback: CalledMaps, name: string) {
58                const calledMap = calledMaps[callback];
59                const result = calledMap.get(name);
60                assert.isTrue(result && !!result.length, `${callback} should be called with name: ${name}: ${arrayFrom(calledMap.keys())}`);
61            }
62
63            function verifyNoCall(callback: CalledMaps) {
64                const calledMap = calledMaps[callback];
65                assert.equal(calledMap.size, 0, `${callback} shouldn't be called: ${arrayFrom(calledMap.keys())}`);
66            }
67
68            function verifyCalledOnEachEntry(callback: CalledMaps, expectedKeys: ESMap<string, number>) {
69                TestFSWithWatch.checkMap<true | CalledWithFiveArgs>(callback, calledMaps[callback], expectedKeys);
70            }
71
72            function verifyCalledOnEachEntryNTimes(callback: CalledMaps, expectedKeys: readonly string[], nTimes: number) {
73                TestFSWithWatch.checkMap<true | CalledWithFiveArgs>(callback, calledMaps[callback], expectedKeys, nTimes);
74            }
75
76            function verifyNoHostCalls() {
77                iterateOnCalledMaps(key => verifyNoCall(key));
78            }
79
80            function verifyNoHostCallsExceptFileExistsOnce(expectedKeys: readonly string[]) {
81                verifyCalledOnEachEntryNTimes(CalledMapsWithSingleArg.fileExists, expectedKeys, 1);
82                verifyNoCall(CalledMapsWithSingleArg.directoryExists);
83                verifyNoCall(CalledMapsWithSingleArg.getDirectories);
84                verifyNoCall(CalledMapsWithSingleArg.readFile);
85                verifyNoCall(CalledMapsWithFiveArgs.readDirectory);
86            }
87
88            function clear() {
89                iterateOnCalledMaps(key => calledMaps[key].clear());
90            }
91
92            function iterateOnCalledMaps(cb: (key: CalledMaps) => void) {
93                for (const key in CalledMapsWithSingleArg) {
94                    cb(key as CalledMapsWithSingleArg);
95                }
96                for (const key in CalledMapsWithFiveArgs) {
97                    cb(key as CalledMapsWithFiveArgs);
98                }
99            }
100        }
101
102        it("works using legacy resolution logic", () => {
103            let rootContent = `import {x} from "f1"`;
104            const root: File = {
105                path: "/c/d/f0.ts",
106                content: rootContent
107            };
108
109            const imported: File = {
110                path: "/c/f1.ts",
111                content: `foo()`
112            };
113
114            const host = createServerHost([root, imported]);
115            const projectService = createProjectService(host);
116            projectService.setCompilerOptionsForInferredProjects({ module: ModuleKind.AMD, noLib: true });
117            projectService.openClientFile(root.path);
118            checkNumberOfProjects(projectService, { inferredProjects: 1 });
119            const project = projectService.inferredProjects[0];
120            const rootScriptInfo = project.getRootScriptInfos()[0];
121            assert.equal(rootScriptInfo.fileName, root.path);
122
123            // ensure that imported file was found
124            verifyImportedDiagnostics();
125
126            const callsTrackingHost = createCallsTrackingHost(host);
127
128            // trigger synchronization to make sure that import will be fetched from the cache
129            // ensure file has correct number of errors after edit
130            editContent(`import {x} from "f1";
131                 var x: string = 1;`);
132            verifyImportedDiagnostics();
133            callsTrackingHost.verifyNoHostCalls();
134
135            // trigger synchronization to make sure that the host will try to find 'f2' module on disk
136            editContent(`import {x} from "f2"`);
137            try {
138                // trigger synchronization to make sure that the host will try to find 'f2' module on disk
139                verifyImportedDiagnostics();
140                assert.isTrue(false, `should not find file '${imported.path}'`);
141            }
142            catch (e) {
143                assert.isTrue(e.message.indexOf(`Could not find source file: '${imported.path}'.`) === 0, `Actual: ${e.message}`);
144            }
145            const f2Lookups = getLocationsForModuleLookup("f2");
146            callsTrackingHost.verifyCalledOnEachEntryNTimes(CalledMapsWithSingleArg.fileExists, f2Lookups, 1);
147            const f2DirLookups = getLocationsForDirectoryLookup();
148            callsTrackingHost.verifyCalledOnEachEntry(CalledMapsWithSingleArg.directoryExists, f2DirLookups);
149            callsTrackingHost.verifyNoCall(CalledMapsWithSingleArg.getDirectories);
150            callsTrackingHost.verifyNoCall(CalledMapsWithSingleArg.readFile);
151            callsTrackingHost.verifyNoCall(CalledMapsWithFiveArgs.readDirectory);
152
153            editContent(`import {x} from "f1"`);
154            verifyImportedDiagnostics();
155            const f1Lookups = f2Lookups.map(s => s.replace("f2", "f1"));
156            f1Lookups.length = f1Lookups.indexOf(imported.path) + 1;
157            const f1DirLookups = ["/c/d", "/c", ...mapCombinedPathsInAncestor(getDirectoryPath(root.path), nodeModulesAtTypes, returnTrue)];
158            vertifyF1Lookups();
159
160            // setting compiler options discards module resolution cache
161            callsTrackingHost.clear();
162            projectService.setCompilerOptionsForInferredProjects({ module: ModuleKind.AMD, noLib: true, target: ScriptTarget.ES5 });
163            verifyImportedDiagnostics();
164            vertifyF1Lookups();
165
166            function vertifyF1Lookups() {
167                callsTrackingHost.verifyCalledOnEachEntryNTimes(CalledMapsWithSingleArg.fileExists, f1Lookups, 1);
168                callsTrackingHost.verifyCalledOnEachEntryNTimes(CalledMapsWithSingleArg.directoryExists, f1DirLookups, 1);
169                callsTrackingHost.verifyNoCall(CalledMapsWithSingleArg.getDirectories);
170                callsTrackingHost.verifyNoCall(CalledMapsWithSingleArg.readFile);
171                callsTrackingHost.verifyNoCall(CalledMapsWithFiveArgs.readDirectory);
172            }
173
174            function editContent(newContent: string) {
175                callsTrackingHost.clear();
176                rootScriptInfo.editContent(0, rootContent.length, newContent);
177                rootContent = newContent;
178            }
179
180            function verifyImportedDiagnostics() {
181                const diags = project.getLanguageService().getSemanticDiagnostics(imported.path);
182                assert.equal(diags.length, 1);
183                const diag = diags[0];
184                assert.equal(diag.code, Diagnostics.Cannot_find_name_0.code);
185                assert.equal(flattenDiagnosticMessageText(diag.messageText, "\n"), "Cannot find name 'foo'.");
186            }
187
188            function getLocationsForModuleLookup(module: string) {
189                const locations: string[] = [];
190                forEachAncestorDirectory(getDirectoryPath(root.path), ancestor => {
191                    locations.push(
192                        combinePaths(ancestor, `${module}.ts`),
193                        combinePaths(ancestor, `${module}.tsx`),
194                        combinePaths(ancestor, `${module}.d.ts`),
195                        combinePaths(ancestor, `${module}.ets`)
196                    );
197                });
198                forEachAncestorDirectory(getDirectoryPath(root.path), ancestor => {
199                    locations.push(
200                        combinePaths(ancestor, `${module}.js`),
201                        combinePaths(ancestor, `${module}.jsx`)
202                    );
203                });
204                return locations;
205            }
206
207            function getLocationsForDirectoryLookup() {
208                const result = new Map<string, number>();
209                forEachAncestorDirectory(getDirectoryPath(root.path), ancestor => {
210                    // To resolve modules
211                    result.set(ancestor, 2);
212                    // for type roots
213                    result.set(combinePaths(ancestor, nodeModules), 1);
214                    result.set(combinePaths(ancestor, nodeModulesAtTypes), 1);
215                });
216                return result;
217            }
218        });
219
220        it("loads missing files from disk", () => {
221            const root: File = {
222                path: "/c/foo.ts",
223                content: `import {y} from "bar"`
224            };
225
226            const imported: File = {
227                path: "/c/bar.d.ts",
228                content: `export var y = 1`
229            };
230
231            const host = createServerHost([root]);
232            const projectService = createProjectService(host);
233            projectService.setCompilerOptionsForInferredProjects({ module: ModuleKind.AMD, noLib: true });
234            const callsTrackingHost = createCallsTrackingHost(host);
235            projectService.openClientFile(root.path);
236            checkNumberOfProjects(projectService, { inferredProjects: 1 });
237            const project = projectService.inferredProjects[0];
238            const rootScriptInfo = project.getRootScriptInfos()[0];
239            assert.equal(rootScriptInfo.fileName, root.path);
240
241            let diags = project.getLanguageService().getSemanticDiagnostics(root.path);
242            assert.equal(diags.length, 1);
243            const diag = diags[0];
244            assert.equal(diag.code, Diagnostics.Cannot_find_module_0_Did_you_mean_to_set_the_moduleResolution_option_to_node_or_to_add_aliases_to_the_paths_option.code);
245            assert.equal(flattenDiagnosticMessageText(diag.messageText, "\n"), "Cannot find module 'bar'. Did you mean to set the 'moduleResolution' option to 'node', or to add aliases to the 'paths' option?");
246            callsTrackingHost.verifyCalledOn(CalledMapsWithSingleArg.fileExists, imported.path);
247
248
249            callsTrackingHost.clear();
250            host.writeFile(imported.path, imported.content);
251            host.runQueuedTimeoutCallbacks();
252            diags = project.getLanguageService().getSemanticDiagnostics(root.path);
253            assert.equal(diags.length, 0);
254            callsTrackingHost.verifyCalledOn(CalledMapsWithSingleArg.fileExists, imported.path);
255        });
256
257        it("when calling goto definition of module", () => {
258            const clientFile: File = {
259                path: "/a/b/controllers/vessels/client.ts",
260                content: `
261                    import { Vessel } from '~/models/vessel';
262                    const v = new Vessel();
263                `
264            };
265            const anotherModuleFile: File = {
266                path: "/a/b/utils/db.ts",
267                content: "export class Bookshelf { }"
268            };
269            const moduleFile: File = {
270                path: "/a/b/models/vessel.ts",
271                content: `
272                    import { Bookshelf } from '~/utils/db';
273                    export class Vessel extends Bookshelf {}
274                `
275            };
276            const tsconfigFile: File = {
277                path: "/a/b/tsconfig.json",
278                content: JSON.stringify({
279                    compilerOptions: {
280                        target: "es6",
281                        module: "es6",
282                        baseUrl: "./",  // all paths are relative to the baseUrl
283                        paths: {
284                            "~/*": ["*"]   // resolve any `~/foo/bar` to `<baseUrl>/foo/bar`
285                        }
286                    },
287                    exclude: [
288                        "api",
289                        "build",
290                        "node_modules",
291                        "public",
292                        "seeds",
293                        "sql_updates",
294                        "tests.build"
295                    ]
296                })
297            };
298            const projectFiles = [clientFile, anotherModuleFile, moduleFile, tsconfigFile];
299            const host = createServerHost(projectFiles);
300            const session = createSession(host);
301            const projectService = session.getProjectService();
302            const { configFileName } = projectService.openClientFile(clientFile.path);
303
304            assert.isDefined(configFileName, `should find config`);
305            checkNumberOfConfiguredProjects(projectService, 1);
306
307            const project = projectService.configuredProjects.get(tsconfigFile.path)!;
308            checkProjectActualFiles(project, map(projectFiles, f => f.path));
309
310            const callsTrackingHost = createCallsTrackingHost(host);
311
312            // Get definitions shouldnt make host requests
313            const getDefinitionRequest = makeSessionRequest<protocol.FileLocationRequestArgs>(protocol.CommandTypes.Definition, {
314                file: clientFile.path,
315                position: clientFile.content.indexOf("/vessel") + 1,
316                line: undefined!, // TODO: GH#18217
317                offset: undefined! // TODO: GH#18217
318            });
319            const response = session.executeCommand(getDefinitionRequest).response as server.protocol.FileSpan[];
320            assert.equal(response[0].file, moduleFile.path, "Should go to definition of vessel: response: " + JSON.stringify(response));
321            callsTrackingHost.verifyNoHostCalls();
322
323            // Open the file should call only file exists on module directory and use cached value for parental directory
324            const { configFileName: config2 } = projectService.openClientFile(moduleFile.path);
325            assert.equal(config2, configFileName);
326            callsTrackingHost.verifyNoHostCallsExceptFileExistsOnce(["/a/b/models/tsconfig.json", "/a/b/models/jsconfig.json"]);
327
328            checkNumberOfConfiguredProjects(projectService, 1);
329            assert.strictEqual(projectService.configuredProjects.get(tsconfigFile.path), project);
330        });
331
332        describe("WatchDirectories for config file with", () => {
333            function verifyWatchDirectoriesCaseSensitivity(useCaseSensitiveFileNames: boolean) {
334                const frontendDir = "/Users/someuser/work/applications/frontend";
335                const toCanonical: (s: string) => Path = useCaseSensitiveFileNames ? s => s as Path : s => s.toLowerCase() as Path;
336                const canonicalFrontendDir = toCanonical(frontendDir);
337                const file1: File = {
338                    path: `${frontendDir}/src/app/utils/Analytic.ts`,
339                    content: "export class SomeClass { };"
340                };
341                const file2: File = {
342                    path: `${frontendDir}/src/app/redux/configureStore.ts`,
343                    content: "export class configureStore { }"
344                };
345                const file3: File = {
346                    path: `${frontendDir}/src/app/utils/Cookie.ts`,
347                    content: "export class Cookie { }"
348                };
349                const es2016LibFile: File = {
350                    path: "/a/lib/lib.es2016.full.d.ts",
351                    content: libFile.content
352                };
353                const typeRoots = ["types", "node_modules/@types"];
354                const types = ["node", "jest"];
355                const tsconfigFile: File = {
356                    path: `${frontendDir}/tsconfig.json`,
357                    content: JSON.stringify({
358                        compilerOptions: {
359                            strict: true,
360                            strictNullChecks: true,
361                            target: "es2016",
362                            module: "commonjs",
363                            moduleResolution: "node",
364                            sourceMap: true,
365                            noEmitOnError: true,
366                            experimentalDecorators: true,
367                            emitDecoratorMetadata: true,
368                            types,
369                            noUnusedLocals: true,
370                            outDir: "./compiled",
371                            typeRoots,
372                            baseUrl: ".",
373                            paths: {
374                                "*": [
375                                    "types/*"
376                                ]
377                            }
378                        },
379                        include: [
380                            "src/**/*"
381                        ],
382                        exclude: [
383                            "node_modules",
384                            "compiled"
385                        ]
386                    })
387                };
388                const projectFiles = [file1, file2, es2016LibFile, tsconfigFile];
389                const host = createServerHost(projectFiles, { useCaseSensitiveFileNames });
390                const projectService = createProjectService(host);
391                const canonicalConfigPath = toCanonical(tsconfigFile.path);
392                const { configFileName } = projectService.openClientFile(file1.path);
393                assert.equal(configFileName, tsconfigFile.path as server.NormalizedPath, `should find config`);
394                checkNumberOfConfiguredProjects(projectService, 1);
395                const watchingRecursiveDirectories = [`${canonicalFrontendDir}/src`, `${canonicalFrontendDir}/types`, `${canonicalFrontendDir}/node_modules`].concat(getNodeModuleDirectories(getDirectoryPath(canonicalFrontendDir)));
396
397                const project = projectService.configuredProjects.get(canonicalConfigPath)!;
398                verifyProjectAndWatchedDirectories();
399
400                const callsTrackingHost = createCallsTrackingHost(host);
401
402                // Create file cookie.ts
403                projectFiles.push(file3);
404                host.writeFile(file3.path, file3.content);
405                host.runQueuedTimeoutCallbacks();
406
407                const canonicalFile3Path = useCaseSensitiveFileNames ? file3.path : file3.path.toLocaleLowerCase();
408                const numberOfTimesWatchInvoked = getNumberOfWatchesInvokedForRecursiveWatches(watchingRecursiveDirectories, canonicalFile3Path);
409                callsTrackingHost.verifyCalledOnEachEntryNTimes(CalledMapsWithSingleArg.fileExists, [canonicalFile3Path], numberOfTimesWatchInvoked);
410                callsTrackingHost.verifyCalledOnEachEntryNTimes(CalledMapsWithSingleArg.directoryExists, [canonicalFile3Path], numberOfTimesWatchInvoked);
411                callsTrackingHost.verifyNoCall(CalledMapsWithSingleArg.getDirectories);
412                callsTrackingHost.verifyCalledOnEachEntryNTimes(CalledMapsWithSingleArg.readFile, [file3.path], 1);
413                callsTrackingHost.verifyNoCall(CalledMapsWithFiveArgs.readDirectory);
414
415                checkNumberOfConfiguredProjects(projectService, 1);
416                assert.strictEqual(projectService.configuredProjects.get(canonicalConfigPath), project);
417                verifyProjectAndWatchedDirectories();
418
419                callsTrackingHost.clear();
420
421                const { configFileName: configFile2 } = projectService.openClientFile(file3.path);
422                assert.equal(configFile2, configFileName);
423
424                checkNumberOfConfiguredProjects(projectService, 1);
425                assert.strictEqual(projectService.configuredProjects.get(canonicalConfigPath), project);
426                verifyProjectAndWatchedDirectories();
427                callsTrackingHost.verifyNoHostCalls();
428
429                function getFilePathIfNotOpen(f: File) {
430                    const path = toCanonical(f.path);
431                    const info = projectService.getScriptInfoForPath(toCanonical(f.path));
432                    return info && info.isScriptOpen() ? undefined : path;
433                }
434
435                function verifyProjectAndWatchedDirectories() {
436                    checkProjectActualFiles(project, map(projectFiles, f => f.path));
437                    checkWatchedFiles(host, mapDefined(projectFiles, getFilePathIfNotOpen));
438                    checkWatchedDirectories(host, watchingRecursiveDirectories, /*recursive*/ true);
439                    checkWatchedDirectories(host, [], /*recursive*/ false);
440                }
441            }
442
443            it("case insensitive file system", () => {
444                verifyWatchDirectoriesCaseSensitivity(/*useCaseSensitiveFileNames*/ false);
445            });
446
447            it("case sensitive file system", () => {
448                verifyWatchDirectoriesCaseSensitivity(/*useCaseSensitiveFileNames*/ true);
449            });
450        });
451
452        describe("Subfolder invalidations correctly include parent folder failed lookup locations", () => {
453            function runFailedLookupTest(resolution: "Node" | "Classic") {
454                const projectLocation = "/proj";
455                const file1: File = {
456                    path: `${projectLocation}/foo/boo/app.ts`,
457                    content: `import * as debug from "debug"`
458                };
459                const file2: File = {
460                    path: `${projectLocation}/foo/boo/moo/app.ts`,
461                    content: `import * as debug from "debug"`
462                };
463                const tsconfig: File = {
464                    path: `${projectLocation}/tsconfig.json`,
465                    content: JSON.stringify({
466                        files: ["foo/boo/app.ts", "foo/boo/moo/app.ts"],
467                        moduleResolution: resolution
468                    })
469                };
470
471                const files = [file1, file2, tsconfig, libFile];
472                const host = createServerHost(files);
473                const service = createProjectService(host);
474                service.openClientFile(file1.path);
475
476                const project = service.configuredProjects.get(tsconfig.path)!;
477                checkProjectActualFiles(project, files.map(f => f.path));
478                assert.deepEqual(project.getLanguageService().getSemanticDiagnostics(file1.path).map(diag => diag.messageText), ["Cannot find module 'debug' or its corresponding type declarations."]);
479                assert.deepEqual(project.getLanguageService().getSemanticDiagnostics(file2.path).map(diag => diag.messageText), ["Cannot find module 'debug' or its corresponding type declarations."]);
480
481                const debugTypesFile: File = {
482                    path: `${projectLocation}/node_modules/debug/index.d.ts`,
483                    content: "export {}"
484                };
485                files.push(debugTypesFile);
486                host.writeFile(debugTypesFile.path, debugTypesFile.content);
487                host.runQueuedTimeoutCallbacks(); // Scheduled invalidation of resolutions
488                host.runQueuedTimeoutCallbacks(); // Actual update
489                checkProjectActualFiles(project, files.map(f => f.path));
490                assert.deepEqual(project.getLanguageService().getSemanticDiagnostics(file1.path).map(diag => diag.messageText), []);
491                assert.deepEqual(project.getLanguageService().getSemanticDiagnostics(file2.path).map(diag => diag.messageText), []);
492            }
493
494            it("Includes the parent folder FLLs in node module resolution mode", () => {
495                runFailedLookupTest("Node");
496            });
497            it("Includes the parent folder FLLs in classic module resolution mode", () => {
498                runFailedLookupTest("Classic");
499            });
500        });
501
502        describe("Verify npm install in directory with tsconfig file works when", () => {
503            function verifyNpmInstall(timeoutDuringPartialInstallation: boolean) {
504                const root = "/user/username/rootfolder/otherfolder";
505                const getRootedFileOrFolder = (fileOrFolder: File) => {
506                    fileOrFolder.path = root + fileOrFolder.path;
507                    return fileOrFolder;
508                };
509                const app: File = getRootedFileOrFolder({
510                    path: "/a/b/app.ts",
511                    content: "import _ from 'lodash';"
512                });
513                const tsconfigJson: File = getRootedFileOrFolder({
514                    path: "/a/b/tsconfig.json",
515                    content: '{ "compilerOptions": { } }'
516                });
517                const packageJson: File = getRootedFileOrFolder({
518                    path: "/a/b/package.json",
519                    content: `
520{
521  "name": "test",
522  "version": "1.0.0",
523  "description": "",
524  "main": "index.js",
525  "dependencies": {
526    "lodash",
527    "rxjs"
528  },
529  "devDependencies": {
530    "@types/lodash",
531    "typescript"
532  },
533  "scripts": {
534    "test": "echo \"Error: no test specified\" && exit 1"
535  },
536  "keywords": [],
537  "author": "",
538  "license": "ISC"
539}
540`
541                });
542                const appFolder = getDirectoryPath(app.path);
543                const projectFiles = [app, libFile, tsconfigJson];
544                const typeRootDirectories = getTypeRootsFromLocation(getDirectoryPath(tsconfigJson.path));
545                const otherFiles = [packageJson];
546                const host = createServerHost(projectFiles.concat(otherFiles));
547                const projectService = createProjectService(host);
548                projectService.setHostConfiguration({ preferences: { includePackageJsonAutoImports: "off" } });
549                const { configFileName } = projectService.openClientFile(app.path);
550                assert.equal(configFileName, tsconfigJson.path as server.NormalizedPath, `should find config`); // TODO: GH#18217
551                const recursiveWatchedDirectories: string[] = [`${appFolder}`, `${appFolder}/node_modules`].concat(getNodeModuleDirectories(getDirectoryPath(appFolder)));
552                verifyProject();
553
554                let npmInstallComplete = false;
555
556                // Simulate npm install
557                const filesAndFoldersToAdd: File[] = [
558                    { path: "/a/b/node_modules" },
559                    { path: "/a/b/node_modules/.staging/@types" },
560                    { path: "/a/b/node_modules/.staging/lodash-b0733faa" },
561                    { path: "/a/b/node_modules/.staging/@types/lodash-e56c4fe7" },
562                    { path: "/a/b/node_modules/.staging/symbol-observable-24bcbbff" },
563                    { path: "/a/b/node_modules/.staging/rxjs-22375c61" },
564                    { path: "/a/b/node_modules/.staging/typescript-8493ea5d" },
565                    { path: "/a/b/node_modules/.staging/symbol-observable-24bcbbff/package.json", content: "{\n  \"name\": \"symbol-observable\",\n  \"version\": \"1.0.4\",\n  \"description\": \"Symbol.observable ponyfill\",\n  \"license\": \"MIT\",\n  \"repository\": \"blesh/symbol-observable\",\n  \"author\": {\n    \"name\": \"Ben Lesh\",\n    \"email\": \"ben@benlesh.com\"\n  },\n  \"engines\": {\n    \"node\": \">=0.10.0\"\n  },\n  \"scripts\": {\n    \"test\": \"npm run build && mocha && tsc ./ts-test/test.ts && node ./ts-test/test.js && check-es3-syntax -p lib/ --kill\",\n    \"build\": \"babel es --out-dir lib\",\n    \"prepublish\": \"npm test\"\n  },\n  \"files\": [\n    \"" },
566                    { path: "/a/b/node_modules/.staging/lodash-b0733faa/package.json", content: "{\n  \"name\": \"lodash\",\n  \"version\": \"4.17.4\",\n  \"description\": \"Lodash modular utilities.\",\n  \"keywords\": \"modules, stdlib, util\",\n  \"homepage\": \"https://lodash.com/\",\n  \"repository\": \"lodash/lodash\",\n  \"icon\": \"https://lodash.com/icon.svg\",\n  \"license\": \"MIT\",\n  \"main\": \"lodash.js\",\n  \"author\": \"John-David Dalton <john.david.dalton@gmail.com> (http://allyoucanleet.com/)\",\n  \"contributors\": [\n    \"John-David Dalton <john.david.dalton@gmail.com> (http://allyoucanleet.com/)\",\n    \"Mathias Bynens <mathias@qiwi." },
567                    { path: "/a/b/node_modules/.staging/rxjs-22375c61/package.json", content: "{\n  \"name\": \"rxjs\",\n  \"version\": \"5.4.3\",\n  \"description\": \"Reactive Extensions for modern JavaScript\",\n  \"main\": \"Rx.js\",\n  \"config\": {\n    \"commitizen\": {\n      \"path\": \"cz-conventional-changelog\"\n    }\n  },\n  \"lint-staged\": {\n    \"*.@(js)\": [\n      \"eslint --fix\",\n      \"git add\"\n    ],\n    \"*.@(ts)\": [\n      \"eslint -c .eslintrc --ext .ts . --fix\",\n      \"git add\"\n    ]\n  },\n  \"scripts-info\": {\n    \"info\": \"List available script\",\n    \"build_all\": \"Build all packages (ES6, CJS, UMD) and generate packages\",\n    \"build_cjs\": \"Build CJS package with clean up existing build, copy source into dist\",\n    \"build_es6\": \"Build ES6 package with clean up existing build, copy source into dist\",\n    \"build_closure_core\": \"Minify Global core build using closure compiler\",\n    \"build_global\": \"Build Global package, then minify build\",\n    \"build_perf\": \"Build CJS & Global build, run macro performance test\",\n    \"build_test\": \"Build CJS package & test spec, execute mocha test runner\",\n    \"build_cover\": \"Run lint to current code, build CJS & test spec, execute test coverage\",\n    \"build_docs\": \"Build ES6 & global package, create documentation using it\",\n    \"build_spec\": \"Build test specs\",\n    \"check_circular_dependencies\": \"Check codebase has circular dependencies\",\n    \"clean_spec\": \"Clean up existing test spec build output\",\n    \"clean_dist_cjs\": \"Clean up existing CJS package output\",\n    \"clean_dist_es6\": \"Clean up existing ES6 package output\",\n    \"clean_dist_global\": \"Clean up existing Global package output\",\n    \"commit\": \"Run git commit wizard\",\n    \"compile_dist_cjs\": \"Compile codebase into CJS module\",\n    \"compile_module_es6\": \"Compile codebase into ES6\",\n    \"cover\": \"Execute test coverage\",\n    \"lint_perf\": \"Run lint against performance test suite\",\n    \"lint_spec\": \"Run lint against test spec\",\n    \"lint_src\": \"Run lint against source\",\n    \"lint\": \"Run lint against everything\",\n    \"perf\": \"Run macro performance benchmark\",\n    \"perf_micro\": \"Run micro performance benchmark\",\n    \"test_mocha\": \"Execute mocha test runner against existing test spec build\",\n    \"test_browser\": \"Execute mocha test runner on browser against existing test spec build\",\n    \"test\": \"Clean up existing test spec build, build test spec and execute mocha test runner\",\n    \"tests2png\": \"Generate marble diagram image from test spec\",\n    \"watch\": \"Watch codebase, trigger compile when source code changes\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git@github.com:ReactiveX/RxJS.git\"\n  },\n  \"keywords\": [\n    \"Rx\",\n    \"RxJS\",\n    \"ReactiveX\",\n    \"ReactiveExtensions\",\n    \"Streams\",\n    \"Observables\",\n    \"Observable\",\n    \"Stream\",\n    \"ES6\",\n    \"ES2015\"\n  ],\n  \"author\": \"Ben Lesh <ben@benlesh.com>\",\n  \"contributors\": [\n    {\n      \"name\": \"Ben Lesh\",\n      \"email\": \"ben@benlesh.com\"\n    },\n    {\n      \"name\": \"Paul Taylor\",\n      \"email\": \"paul.e.taylor@me.com\"\n    },\n    {\n      \"name\": \"Jeff Cross\",\n      \"email\": \"crossj@google.com\"\n    },\n    {\n      \"name\": \"Matthew Podwysocki\",\n      \"email\": \"matthewp@microsoft.com\"\n    },\n    {\n      \"name\": \"OJ Kwon\",\n      \"email\": \"kwon.ohjoong@gmail.com\"\n    },\n    {\n      \"name\": \"Andre Staltz\",\n      \"email\": \"andre@staltz.com\"\n    }\n  ],\n  \"license\": \"Apache-2.0\",\n  \"bugs\": {\n    \"url\": \"https://github.com/ReactiveX/RxJS/issues\"\n  },\n  \"homepage\": \"https://github.com/ReactiveX/RxJS\",\n  \"devDependencies\": {\n    \"babel-polyfill\": \"^6.23.0\",\n    \"benchmark\": \"^2.1.0\",\n    \"benchpress\": \"2.0.0-beta.1\",\n    \"chai\": \"^3.5.0\",\n    \"color\": \"^0.11.1\",\n    \"colors\": \"1.1.2\",\n    \"commitizen\": \"^2.8.6\",\n    \"coveralls\": \"^2.11.13\",\n    \"cz-conventional-changelog\": \"^1.2.0\",\n    \"danger\": \"^1.1.0\",\n    \"doctoc\": \"^1.0.0\",\n    \"escape-string-regexp\": \"^1.0.5 \",\n    \"esdoc\": \"^0.4.7\",\n    \"eslint\": \"^3.8.0\",\n    \"fs-extra\": \"^2.1.2\",\n    \"get-folder-size\": \"^1.0.0\",\n    \"glob\": \"^7.0.3\",\n    \"gm\": \"^1.22.0\",\n    \"google-closure-compiler-js\": \"^20170218.0.0\",\n    \"gzip-size\": \"^3.0.0\",\n    \"http-server\": \"^0.9.0\",\n    \"husky\": \"^0.13.3\",\n    \"lint-staged\": \"3.2.5\",\n    \"lodash\": \"^4.15.0\",\n    \"madge\": \"^1.4.3\",\n    \"markdown-doctest\": \"^0.9.1\",\n    \"minimist\": \"^1.2.0\",\n    \"mkdirp\": \"^0.5.1\",\n    \"mocha\": \"^3.0.2\",\n    \"mocha-in-sauce\": \"0.0.1\",\n    \"npm-run-all\": \"^4.0.2\",\n    \"npm-scripts-info\": \"^0.3.4\",\n    \"nyc\": \"^10.2.0\",\n    \"opn-cli\": \"^3.1.0\",\n    \"platform\": \"^1.3.1\",\n    \"promise\": \"^7.1.1\",\n    \"protractor\": \"^3.1.1\",\n    \"rollup\": \"0.36.3\",\n    \"rollup-plugin-inject\": \"^2.0.0\",\n    \"rollup-plugin-node-resolve\": \"^2.0.0\",\n    \"rx\": \"latest\",\n    \"rxjs\": \"latest\",\n    \"shx\": \"^0.2.2\",\n    \"sinon\": \"^2.1.0\",\n    \"sinon-chai\": \"^2.9.0\",\n    \"source-map-support\": \"^0.4.0\",\n    \"tslib\": \"^1.5.0\",\n    \"eslint\": \"^4.4.2\",\n    \"typescript\": \"~2.0.6\",\n    \"typings\": \"^2.0.0\",\n    \"validate-commit-msg\": \"^2.14.0\",\n    \"watch\": \"^1.0.1\",\n    \"webpack\": \"^1.13.1\",\n    \"xmlhttprequest\": \"1.8.0\"\n  },\n  \"engines\": {\n    \"npm\": \">=2.0.0\"\n  },\n  \"typings\": \"Rx.d.ts\",\n  \"dependencies\": {\n    \"symbol-observable\": \"^1.0.1\"\n  }\n}" },
568                    { path: "/a/b/node_modules/.staging/typescript-8493ea5d/package.json", content: "{\n    \"name\": \"typescript\",\n    \"author\": \"Microsoft Corp.\",\n    \"homepage\": \"http://typescriptlang.org/\",\n    \"version\": \"2.4.2\",\n    \"license\": \"Apache-2.0\",\n    \"description\": \"TypeScript is a language for application scale JavaScript development\",\n    \"keywords\": [\n        \"TypeScript\",\n        \"Microsoft\",\n        \"compiler\",\n        \"language\",\n        \"javascript\"\n    ],\n    \"bugs\": {\n        \"url\": \"https://github.com/Microsoft/TypeScript/issues\"\n    },\n    \"repository\": {\n        \"type\": \"git\",\n        \"url\": \"https://github.com/Microsoft/TypeScript.git\"\n    },\n    \"main\": \"./lib/typescript.js\",\n    \"typings\": \"./lib/typescript.d.ts\",\n    \"bin\": {\n        \"tsc\": \"./bin/tsc\",\n        \"tsserver\": \"./bin/tsserver\"\n    },\n    \"engines\": {\n        \"node\": \">=4.2.0\"\n    },\n    \"devDependencies\": {\n        \"@types/browserify\": \"latest\",\n        \"@types/chai\": \"latest\",\n        \"@types/convert-source-map\": \"latest\",\n        \"@types/del\": \"latest\",\n        \"@types/glob\": \"latest\",\n        \"@types/gulp\": \"latest\",\n        \"@types/gulp-concat\": \"latest\",\n        \"@types/gulp-help\": \"latest\",\n        \"@types/gulp-newer\": \"latest\",\n        \"@types/gulp-sourcemaps\": \"latest\",\n        \"@types/merge2\": \"latest\",\n        \"@types/minimatch\": \"latest\",\n        \"@types/minimist\": \"latest\",\n        \"@types/mkdirp\": \"latest\",\n        \"@types/mocha\": \"latest\",\n        \"@types/node\": \"latest\",\n        \"@types/q\": \"latest\",\n        \"@types/run-sequence\": \"latest\",\n        \"@types/through2\": \"latest\",\n        \"browserify\": \"latest\",\n        \"chai\": \"latest\",\n        \"convert-source-map\": \"latest\",\n        \"del\": \"latest\",\n        \"gulp\": \"latest\",\n        \"gulp-clone\": \"latest\",\n        \"gulp-concat\": \"latest\",\n        \"gulp-help\": \"latest\",\n        \"gulp-insert\": \"latest\",\n        \"gulp-newer\": \"latest\",\n        \"gulp-sourcemaps\": \"latest\",\n        \"gulp-typescript\": \"latest\",\n        \"into-stream\": \"latest\",\n        \"istanbul\": \"latest\",\n        \"jake\": \"latest\",\n        \"merge2\": \"latest\",\n        \"minimist\": \"latest\",\n        \"mkdirp\": \"latest\",\n        \"mocha\": \"latest\",\n        \"mocha-fivemat-progress-reporter\": \"latest\",\n        \"q\": \"latest\",\n        \"run-sequence\": \"latest\",\n        \"sorcery\": \"latest\",\n        \"through2\": \"latest\",\n        \"travis-fold\": \"latest\",\n        \"ts-node\": \"latest\",\n        \"eslint\": \"5.16.0\",\n        \"typescript\": \"^2.4\"\n    },\n    \"scripts\": {\n        \"pretest\": \"jake tests\",\n        \"test\": \"jake runtests-parallel\",\n        \"build\": \"npm run build:compiler && npm run build:tests\",\n        \"build:compiler\": \"jake local\",\n        \"build:tests\": \"jake tests\",\n        \"start\": \"node lib/tsc\",\n        \"clean\": \"jake clean\",\n        \"gulp\": \"gulp\",\n        \"jake\": \"jake\",\n        \"lint\": \"jake lint\",\n        \"setup-hooks\": \"node scripts/link-hooks.js\"\n    },\n    \"browser\": {\n        \"buffer\": false,\n        \"fs\": false,\n        \"os\": false,\n        \"path\": false\n    }\n}" },
569                    { path: "/a/b/node_modules/.staging/symbol-observable-24bcbbff/index.js", content: "module.exports = require('./lib/index');\n" },
570                    { path: "/a/b/node_modules/.staging/symbol-observable-24bcbbff/index.d.ts", content: "declare const observableSymbol: symbol;\nexport default observableSymbol;\n" },
571                    { path: "/a/b/node_modules/.staging/symbol-observable-24bcbbff/lib" },
572                    { path: "/a/b/node_modules/.staging/symbol-observable-24bcbbff/lib/index.js", content: "'use strict';\n\nObject.defineProperty(exports, \"__esModule\", {\n  value: true\n});\n\nvar _ponyfill = require('./ponyfill');\n\nvar _ponyfill2 = _interopRequireDefault(_ponyfill);\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }\n\nvar root; /* global window */\n\n\nif (typeof self !== 'undefined') {\n  root = self;\n} else if (typeof window !== 'undefined') {\n  root = window;\n} else if (typeof global !== 'undefined') {\n  root = global;\n} else if (typeof module !== 'undefined') {\n  root = module;\n} else {\n  root = Function('return this')();\n}\n\nvar result = (0, _ponyfill2['default'])(root);\nexports['default'] = result;" },
573                ].map(getRootedFileOrFolder);
574                verifyAfterPartialOrCompleteNpmInstall(2);
575
576                filesAndFoldersToAdd.push(...[
577                    { path: "/a/b/node_modules/.staging/typescript-8493ea5d/lib" },
578                    { path: "/a/b/node_modules/.staging/rxjs-22375c61/add/operator" },
579                    { path: "/a/b/node_modules/.staging/@types/lodash-e56c4fe7/package.json", content: "{\n    \"name\": \"@types/lodash\",\n    \"version\": \"4.14.74\",\n    \"description\": \"TypeScript definitions for Lo-Dash\",\n    \"license\": \"MIT\",\n    \"contributors\": [\n        {\n            \"name\": \"Brian Zengel\",\n            \"url\": \"https://github.com/bczengel\"\n        },\n        {\n            \"name\": \"Ilya Mochalov\",\n            \"url\": \"https://github.com/chrootsu\"\n        },\n        {\n            \"name\": \"Stepan Mikhaylyuk\",\n            \"url\": \"https://github.com/stepancar\"\n        },\n        {\n            \"name\": \"Eric L Anderson\",\n            \"url\": \"https://github.com/ericanderson\"\n        },\n        {\n            \"name\": \"AJ Richardson\",\n            \"url\": \"https://github.com/aj-r\"\n        },\n        {\n            \"name\": \"Junyoung Clare Jang\",\n            \"url\": \"https://github.com/ailrun\"\n        }\n    ],\n    \"main\": \"\",\n    \"repository\": {\n        \"type\": \"git\",\n        \"url\": \"https://www.github.com/DefinitelyTyped/DefinitelyTyped.git\"\n    },\n    \"scripts\": {},\n    \"dependencies\": {},\n    \"typesPublisherContentHash\": \"12af578ffaf8d86d2df37e591857906a86b983fa9258414326544a0fe6af0de8\",\n    \"typeScriptVersion\": \"2.2\"\n}" },
580                    { path: "/a/b/node_modules/.staging/lodash-b0733faa/index.js", content: "module.exports = require('./lodash');" },
581                    { path: "/a/b/node_modules/.staging/typescript-8493ea5d/package.json.3017591594", content: "" }
582                ].map(getRootedFileOrFolder));
583                // Since we added/removed in .staging no timeout
584                verifyAfterPartialOrCompleteNpmInstall(0);
585
586                // Remove file "/a/b/node_modules/.staging/typescript-8493ea5d/package.json.3017591594"
587                host.deleteFile(last(filesAndFoldersToAdd).path);
588                filesAndFoldersToAdd.length--;
589                verifyAfterPartialOrCompleteNpmInstall(0);
590
591                filesAndFoldersToAdd.push(...[
592                    { path: "/a/b/node_modules/.staging/rxjs-22375c61/bundles" },
593                    { path: "/a/b/node_modules/.staging/rxjs-22375c61/operator" },
594                    { path: "/a/b/node_modules/.staging/rxjs-22375c61/src/add/observable/dom" },
595                    { path: "/a/b/node_modules/.staging/@types/lodash-e56c4fe7/index.d.ts", content: "\n// Stub for lodash\nexport = _;\nexport as namespace _;\ndeclare var _: _.LoDashStatic;\ndeclare namespace _ {\n    interface LoDashStatic {\n        someProp: string;\n    }\n    class SomeClass {\n        someMethod(): void;\n    }\n}" }
596                ].map(getRootedFileOrFolder));
597                verifyAfterPartialOrCompleteNpmInstall(0);
598
599                filesAndFoldersToAdd.push(...[
600                    { path: "/a/b/node_modules/.staging/rxjs-22375c61/src/scheduler" },
601                    { path: "/a/b/node_modules/.staging/rxjs-22375c61/src/util" },
602                    { path: "/a/b/node_modules/.staging/rxjs-22375c61/symbol" },
603                    { path: "/a/b/node_modules/.staging/rxjs-22375c61/testing" },
604                    { path: "/a/b/node_modules/.staging/rxjs-22375c61/package.json.2252192041", content: "{\n  \"_args\": [\n    [\n      {\n        \"raw\": \"rxjs@^5.4.2\",\n        \"scope\": null,\n        \"escapedName\": \"rxjs\",\n        \"name\": \"rxjs\",\n        \"rawSpec\": \"^5.4.2\",\n        \"spec\": \">=5.4.2 <6.0.0\",\n        \"type\": \"range\"\n      },\n      \"C:\\\\Users\\\\shkamat\\\\Desktop\\\\app\"\n    ]\n  ],\n  \"_from\": \"rxjs@>=5.4.2 <6.0.0\",\n  \"_id\": \"rxjs@5.4.3\",\n  \"_inCache\": true,\n  \"_location\": \"/rxjs\",\n  \"_nodeVersion\": \"7.7.2\",\n  \"_npmOperationalInternal\": {\n    \"host\": \"s3://npm-registry-packages\",\n    \"tmp\": \"tmp/rxjs-5.4.3.tgz_1502407898166_0.6800217325799167\"\n  },\n  \"_npmUser\": {\n    \"name\": \"blesh\",\n    \"email\": \"ben@benlesh.com\"\n  },\n  \"_npmVersion\": \"5.3.0\",\n  \"_phantomChildren\": {},\n  \"_requested\": {\n    \"raw\": \"rxjs@^5.4.2\",\n    \"scope\": null,\n    \"escapedName\": \"rxjs\",\n    \"name\": \"rxjs\",\n    \"rawSpec\": \"^5.4.2\",\n    \"spec\": \">=5.4.2 <6.0.0\",\n    \"type\": \"range\"\n  },\n  \"_requiredBy\": [\n    \"/\"\n  ],\n  \"_resolved\": \"https://registry.npmjs.org/rxjs/-/rxjs-5.4.3.tgz\",\n  \"_shasum\": \"0758cddee6033d68e0fd53676f0f3596ce3d483f\",\n  \"_shrinkwrap\": null,\n  \"_spec\": \"rxjs@^5.4.2\",\n  \"_where\": \"C:\\\\Users\\\\shkamat\\\\Desktop\\\\app\",\n  \"author\": {\n    \"name\": \"Ben Lesh\",\n    \"email\": \"ben@benlesh.com\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/ReactiveX/RxJS/issues\"\n  },\n  \"config\": {\n    \"commitizen\": {\n      \"path\": \"cz-conventional-changelog\"\n    }\n  },\n  \"contributors\": [\n    {\n      \"name\": \"Ben Lesh\",\n      \"email\": \"ben@benlesh.com\"\n    },\n    {\n      \"name\": \"Paul Taylor\",\n      \"email\": \"paul.e.taylor@me.com\"\n    },\n    {\n      \"name\": \"Jeff Cross\",\n      \"email\": \"crossj@google.com\"\n    },\n    {\n      \"name\": \"Matthew Podwysocki\",\n      \"email\": \"matthewp@microsoft.com\"\n    },\n    {\n      \"name\": \"OJ Kwon\",\n      \"email\": \"kwon.ohjoong@gmail.com\"\n    },\n    {\n      \"name\": \"Andre Staltz\",\n      \"email\": \"andre@staltz.com\"\n    }\n  ],\n  \"dependencies\": {\n    \"symbol-observable\": \"^1.0.1\"\n  },\n  \"description\": \"Reactive Extensions for modern JavaScript\",\n  \"devDependencies\": {\n    \"babel-polyfill\": \"^6.23.0\",\n    \"benchmark\": \"^2.1.0\",\n    \"benchpress\": \"2.0.0-beta.1\",\n    \"chai\": \"^3.5.0\",\n    \"color\": \"^0.11.1\",\n    \"colors\": \"1.1.2\",\n    \"commitizen\": \"^2.8.6\",\n    \"coveralls\": \"^2.11.13\",\n    \"cz-conventional-changelog\": \"^1.2.0\",\n    \"danger\": \"^1.1.0\",\n    \"doctoc\": \"^1.0.0\",\n    \"escape-string-regexp\": \"^1.0.5 \",\n    \"esdoc\": \"^0.4.7\",\n    \"eslint\": \"^3.8.0\",\n    \"fs-extra\": \"^2.1.2\",\n    \"get-folder-size\": \"^1.0.0\",\n    \"glob\": \"^7.0.3\",\n    \"gm\": \"^1.22.0\",\n    \"google-closure-compiler-js\": \"^20170218.0.0\",\n    \"gzip-size\": \"^3.0.0\",\n    \"http-server\": \"^0.9.0\",\n    \"husky\": \"^0.13.3\",\n    \"lint-staged\": \"3.2.5\",\n    \"lodash\": \"^4.15.0\",\n    \"madge\": \"^1.4.3\",\n    \"markdown-doctest\": \"^0.9.1\",\n    \"minimist\": \"^1.2.0\",\n    \"mkdirp\": \"^0.5.1\",\n    \"mocha\": \"^3.0.2\",\n    \"mocha-in-sauce\": \"0.0.1\",\n    \"npm-run-all\": \"^4.0.2\",\n    \"npm-scripts-info\": \"^0.3.4\",\n    \"nyc\": \"^10.2.0\",\n    \"opn-cli\": \"^3.1.0\",\n    \"platform\": \"^1.3.1\",\n    \"promise\": \"^7.1.1\",\n    \"protractor\": \"^3.1.1\",\n    \"rollup\": \"0.36.3\",\n    \"rollup-plugin-inject\": \"^2.0.0\",\n    \"rollup-plugin-node-resolve\": \"^2.0.0\",\n    \"rx\": \"latest\",\n    \"rxjs\": \"latest\",\n    \"shx\": \"^0.2.2\",\n    \"sinon\": \"^2.1.0\",\n    \"sinon-chai\": \"^2.9.0\",\n    \"source-map-support\": \"^0.4.0\",\n    \"tslib\": \"^1.5.0\",\n    \"eslint\": \"^5.16.0\",\n    \"typescript\": \"~2.0.6\",\n    \"typings\": \"^2.0.0\",\n    \"validate-commit-msg\": \"^2.14.0\",\n    \"watch\": \"^1.0.1\",\n    \"webpack\": \"^1.13.1\",\n    \"xmlhttprequest\": \"1.8.0\"\n  },\n  \"directories\": {},\n  \"dist\": {\n    \"integrity\": \"sha512-fSNi+y+P9ss+EZuV0GcIIqPUK07DEaMRUtLJvdcvMyFjc9dizuDjere+A4V7JrLGnm9iCc+nagV/4QdMTkqC4A==\",\n    \"shasum\": \"0758cddee6033d68e0fd53676f0f3596ce3d483f\",\n    \"tarball\": \"https://registry.npmjs.org/rxjs/-/rxjs-5.4.3.tgz\"\n  },\n  \"engines\": {\n    \"npm\": \">=2.0.0\"\n  },\n  \"homepage\": \"https://github.com/ReactiveX/RxJS\",\n  \"keywords\": [\n    \"Rx\",\n    \"RxJS\",\n    \"ReactiveX\",\n    \"ReactiveExtensions\",\n    \"Streams\",\n    \"Observables\",\n    \"Observable\",\n    \"Stream\",\n    \"ES6\",\n    \"ES2015\"\n  ],\n  \"license\": \"Apache-2.0\",\n  \"lint-staged\": {\n    \"*.@(js)\": [\n      \"eslint --fix\",\n      \"git add\"\n    ],\n    \"*.@(ts)\": [\n      \"eslint -c .eslintrc --ext .ts . --fix\",\n      \"git add\"\n    ]\n  },\n  \"main\": \"Rx.js\",\n  \"maintainers\": [\n    {\n      \"name\": \"blesh\",\n      \"email\": \"ben@benlesh.com\"\n    }\n  ],\n  \"name\": \"rxjs\",\n  \"optionalDependencies\": {},\n  \"readme\": \"ERROR: No README data found!\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+ssh://git@github.com/ReactiveX/RxJS.git\"\n  },\n  \"scripts-info\": {\n    \"info\": \"List available script\",\n    \"build_all\": \"Build all packages (ES6, CJS, UMD) and generate packages\",\n    \"build_cjs\": \"Build CJS package with clean up existing build, copy source into dist\",\n    \"build_es6\": \"Build ES6 package with clean up existing build, copy source into dist\",\n    \"build_closure_core\": \"Minify Global core build using closure compiler\",\n    \"build_global\": \"Build Global package, then minify build\",\n    \"build_perf\": \"Build CJS & Global build, run macro performance test\",\n    \"build_test\": \"Build CJS package & test spec, execute mocha test runner\",\n    \"build_cover\": \"Run lint to current code, build CJS & test spec, execute test coverage\",\n    \"build_docs\": \"Build ES6 & global package, create documentation using it\",\n    \"build_spec\": \"Build test specs\",\n    \"check_circular_dependencies\": \"Check codebase has circular dependencies\",\n    \"clean_spec\": \"Clean up existing test spec build output\",\n    \"clean_dist_cjs\": \"Clean up existing CJS package output\",\n    \"clean_dist_es6\": \"Clean up existing ES6 package output\",\n    \"clean_dist_global\": \"Clean up existing Global package output\",\n    \"commit\": \"Run git commit wizard\",\n    \"compile_dist_cjs\": \"Compile codebase into CJS module\",\n    \"compile_module_es6\": \"Compile codebase into ES6\",\n    \"cover\": \"Execute test coverage\",\n    \"lint_perf\": \"Run lint against performance test suite\",\n    \"lint_spec\": \"Run lint against test spec\",\n    \"lint_src\": \"Run lint against source\",\n    \"lint\": \"Run lint against everything\",\n    \"perf\": \"Run macro performance benchmark\",\n    \"perf_micro\": \"Run micro performance benchmark\",\n    \"test_mocha\": \"Execute mocha test runner against existing test spec build\",\n    \"test_browser\": \"Execute mocha test runner on browser against existing test spec build\",\n    \"test\": \"Clean up existing test spec build, build test spec and execute mocha test runner\",\n    \"tests2png\": \"Generate marble diagram image from test spec\",\n    \"watch\": \"Watch codebase, trigger compile when source code changes\"\n  },\n  \"typings\": \"Rx.d.ts\",\n  \"version\": \"5.4.3\"\n}\n" }
605                ].map(getRootedFileOrFolder));
606                verifyAfterPartialOrCompleteNpmInstall(0);
607
608                // remove /a/b/node_modules/.staging/rxjs-22375c61/package.json.2252192041
609                host.deleteFile(last(filesAndFoldersToAdd).path);
610                filesAndFoldersToAdd.length--;
611                // and add few more folders/files
612                filesAndFoldersToAdd.push(...[
613                    { path: "/a/b/node_modules/symbol-observable" },
614                    { path: "/a/b/node_modules/@types" },
615                    { path: "/a/b/node_modules/@types/lodash" },
616                    { path: "/a/b/node_modules/lodash" },
617                    { path: "/a/b/node_modules/rxjs" },
618                    { path: "/a/b/node_modules/typescript" },
619                    { path: "/a/b/node_modules/.bin" }
620                ].map(getRootedFileOrFolder));
621                // From the type root update
622                verifyAfterPartialOrCompleteNpmInstall(2);
623
624                forEach(filesAndFoldersToAdd, f => {
625                    f.path = f.path
626                        .replace("/a/b/node_modules/.staging", "/a/b/node_modules")
627                        .replace(/[\-\.][\d\w][\d\w][\d\w][\d\w][\d\w][\d\w][\d\w][\d\w]/g, "");
628                });
629
630                host.deleteFolder(root + "/a/b/node_modules/.staging", /*recursive*/ true);
631                const lodashIndexPath = root + "/a/b/node_modules/@types/lodash/index.d.ts";
632                projectFiles.push(find(filesAndFoldersToAdd, f => f.path === lodashIndexPath)!);
633                // we would now not have failed lookup in the parent of appFolder since lodash is available
634                recursiveWatchedDirectories.length = 2;
635                // npm installation complete, timeout after reload fs
636                npmInstallComplete = true;
637                verifyAfterPartialOrCompleteNpmInstall(2);
638
639                function verifyAfterPartialOrCompleteNpmInstall(timeoutQueueLengthWhenRunningTimeouts: number) {
640                    filesAndFoldersToAdd.forEach(f => host.ensureFileOrFolder(f));
641                    if (npmInstallComplete || timeoutDuringPartialInstallation) {
642                        if (timeoutQueueLengthWhenRunningTimeouts) {
643                            // Expected project update
644                            host.checkTimeoutQueueLengthAndRun(timeoutQueueLengthWhenRunningTimeouts + 1); // Scheduled invalidation of resolutions
645                            host.runQueuedTimeoutCallbacks(); // Actual update
646                        }
647                        else {
648                            host.checkTimeoutQueueLengthAndRun(timeoutQueueLengthWhenRunningTimeouts);
649                        }
650                    }
651                    else {
652                        host.checkTimeoutQueueLength(3);
653                    }
654                    verifyProject();
655                }
656
657                function verifyProject() {
658                    checkNumberOfConfiguredProjects(projectService, 1);
659
660                    const project = projectService.configuredProjects.get(tsconfigJson.path)!;
661                    const projectFilePaths = map(projectFiles, f => f.path);
662                    checkProjectActualFiles(project, projectFilePaths);
663
664                    const filesWatched = filter(projectFilePaths, p => p !== app.path && p.indexOf("/a/b/node_modules") === -1);
665                    checkWatchedFiles(host, filesWatched);
666                    checkWatchedDirectories(host, typeRootDirectories.concat(recursiveWatchedDirectories), /*recursive*/ true);
667                    checkWatchedDirectories(host, [], /*recursive*/ false);
668                }
669            }
670
671            it("timeouts occur inbetween installation", () => {
672                verifyNpmInstall(/*timeoutDuringPartialInstallation*/ true);
673            });
674
675            it("timeout occurs after installation", () => {
676                verifyNpmInstall(/*timeoutDuringPartialInstallation*/ false);
677            });
678        });
679
680        it("when node_modules dont receive event for the @types file addition", () => {
681            const projectLocation = "/user/username/folder/myproject";
682            const app: File = {
683                path: `${projectLocation}/app.ts`,
684                content: `import * as debug from "debug"`
685            };
686            const tsconfig: File = {
687                path: `${projectLocation}/tsconfig.json`,
688                content: ""
689            };
690
691            const files = [app, tsconfig, libFile];
692            const host = createServerHost(files);
693            const service = createProjectService(host);
694            service.openClientFile(app.path);
695
696            const project = service.configuredProjects.get(tsconfig.path)!;
697            checkProjectActualFiles(project, files.map(f => f.path));
698            assert.deepEqual(project.getLanguageService().getSemanticDiagnostics(app.path).map(diag => diag.messageText), ["Cannot find module 'debug' or its corresponding type declarations."]);
699
700            const debugTypesFile: File = {
701                path: `${projectLocation}/node_modules/@types/debug/index.d.ts`,
702                content: "export {}"
703            };
704            files.push(debugTypesFile);
705            // Do not invoke recursive directory watcher for anything other than node_module/@types
706            const invoker = host.invokeFsWatchesRecursiveCallbacks;
707            host.invokeFsWatchesRecursiveCallbacks = (fullPath, eventName, entryFullPath) => {
708                if (fullPath.endsWith("@types")) {
709                    invoker.call(host, fullPath, eventName, entryFullPath);
710                }
711            };
712            host.writeFile(debugTypesFile.path, debugTypesFile.content);
713            host.runQueuedTimeoutCallbacks();
714            checkProjectActualFiles(project, files.map(f => f.path));
715            assert.deepEqual(project.getLanguageService().getSemanticDiagnostics(app.path).map(diag => diag.messageText), []);
716        });
717    });
718}
719