• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1namespace ts.projectSystem {
2    import validatePackageName = JsTyping.validatePackageName;
3    import NameValidationResult = JsTyping.NameValidationResult;
4
5    interface InstallerParams {
6        globalTypingsCacheLocation?: string;
7        throttleLimit?: number;
8        typesRegistry?: ESMap<string, MapLike<string>>;
9    }
10
11    class Installer extends TestTypingsInstaller {
12        constructor(host: server.ServerHost, p?: InstallerParams, log?: TI.Log) {
13            super(
14                (p && p.globalTypingsCacheLocation) || "/a/data",
15                (p && p.throttleLimit) || 5,
16                host,
17                (p && p.typesRegistry),
18                log);
19        }
20
21        installAll(expectedCount: number) {
22            this.checkPendingCommands(expectedCount);
23            this.executePendingCommands();
24        }
25    }
26
27    function executeCommand(self: Installer, host: TestServerHost, installedTypings: string[] | string, typingFiles: File[], cb: TI.RequestCompletedAction): void {
28        self.addPostExecAction(installedTypings, success => {
29            for (const file of typingFiles) {
30                host.ensureFileOrFolder(file);
31            }
32            cb(success);
33        });
34    }
35
36    function trackingLogger(): { log(message: string): void, finish(): string[] } {
37        const logs: string[] = [];
38        return {
39            log(message) {
40                logs.push(message);
41            },
42            finish() {
43                return logs;
44            }
45        };
46    }
47
48    import typingsName = TI.typingsName;
49
50    describe("unittests:: tsserver:: typingsInstaller:: local module", () => {
51        it("should not be picked up", () => {
52            const f1 = {
53                path: "/a/app.js",
54                content: "const c = require('./config');"
55            };
56            const f2 = {
57                path: "/a/config.js",
58                content: "export let x = 1"
59            };
60            const typesCache = "/cache";
61            const typesConfig = {
62                path: typesCache + "/node_modules/@types/config/index.d.ts",
63                content: "export let y: number;"
64            };
65            const config = {
66                path: "/a/jsconfig.json",
67                content: JSON.stringify({
68                    compilerOptions: { moduleResolution: "commonjs" },
69                    typeAcquisition: { enable: true }
70                })
71            };
72            const host = createServerHost([f1, f2, config, typesConfig]);
73            const installer = new (class extends Installer {
74                constructor() {
75                    super(host, { typesRegistry: createTypesRegistry("config"), globalTypingsCacheLocation: typesCache });
76                }
77                installWorker(_requestId: number, _args: string[], _cwd: string, _cb: TI.RequestCompletedAction) {
78                    assert(false, "should not be called");
79                }
80            })();
81            const service = createProjectService(host, { typingsInstaller: installer });
82            service.openClientFile(f1.path);
83            service.checkNumberOfProjects({ configuredProjects: 1 });
84            checkProjectActualFiles(configuredProjectAt(service, 0), [f1.path, f2.path, config.path]);
85            installer.installAll(0);
86        });
87    });
88
89    describe("unittests:: tsserver:: typingsInstaller:: General functionality", () => {
90        it("configured projects (typings installed) 1", () => {
91            const file1 = {
92                path: "/a/b/app.js",
93                content: ""
94            };
95            const tsconfig = {
96                path: "/a/b/tsconfig.json",
97                content: JSON.stringify({
98                    compilerOptions: {
99                        allowJs: true
100                    },
101                    typeAcquisition: {
102                        enable: true
103                    }
104                })
105            };
106            const packageJson = {
107                path: "/a/b/package.json",
108                content: JSON.stringify({
109                    name: "test",
110                    dependencies: {
111                        jquery: "^3.1.0"
112                    }
113                })
114            };
115
116            const jquery = {
117                path: "/a/data/node_modules/@types/jquery/index.d.ts",
118                content: "declare const $: { x: number }"
119            };
120            const host = createServerHost([file1, tsconfig, packageJson]);
121            const installer = new (class extends Installer {
122                constructor() {
123                    super(host, { typesRegistry: createTypesRegistry("jquery") });
124                }
125                installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) {
126                    const installedTypings = ["@types/jquery"];
127                    const typingFiles = [jquery];
128                    executeCommand(this, host, installedTypings, typingFiles, cb);
129                }
130            })();
131
132            const projectService = createProjectService(host, { useSingleInferredProject: true, typingsInstaller: installer });
133            projectService.setHostConfiguration({ preferences: { includePackageJsonAutoImports: "off" } });
134            projectService.openClientFile(file1.path);
135
136            checkNumberOfProjects(projectService, { configuredProjects: 1 });
137            const p = configuredProjectAt(projectService, 0);
138            checkProjectActualFiles(p, [file1.path, tsconfig.path]);
139
140            const expectedWatchedFiles = new Map<string, number>();
141            expectedWatchedFiles.set(tsconfig.path, 1); // tsserver
142            expectedWatchedFiles.set(libFile.path, 1); // tsserver
143            expectedWatchedFiles.set(packageJson.path, 1); // typing installer
144            checkWatchedFilesDetailed(host, expectedWatchedFiles);
145
146            checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
147
148            const expectedWatchedDirectoriesRecursive = new Map<string, number>();
149            expectedWatchedDirectoriesRecursive.set("/a/b", 1); // wild card
150            expectedWatchedDirectoriesRecursive.set("/a/b/node_modules/@types", 1); // type root watch
151            expectedWatchedDirectoriesRecursive.set("/a/b/node_modules", 1); // TypingInstaller
152            expectedWatchedDirectoriesRecursive.set("/a/b/bower_components", 1); // TypingInstaller
153            checkWatchedDirectoriesDetailed(host, expectedWatchedDirectoriesRecursive, /*recursive*/ true);
154
155            installer.installAll(/*expectedCount*/ 1);
156
157            checkNumberOfProjects(projectService, { configuredProjects: 1 });
158            host.checkTimeoutQueueLengthAndRun(2);
159            checkProjectActualFiles(p, [file1.path, jquery.path, tsconfig.path]);
160            // should not watch jquery
161            checkWatchedFilesDetailed(host, expectedWatchedFiles);
162            checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
163            checkWatchedDirectoriesDetailed(host, expectedWatchedDirectoriesRecursive, /*recursive*/ true);
164        });
165
166        it("inferred project (typings installed)", () => {
167            const file1 = {
168                path: "/a/b/app.js",
169                content: ""
170            };
171            const packageJson = {
172                path: "/a/b/package.json",
173                content: JSON.stringify({
174                    name: "test",
175                    dependencies: {
176                        jquery: "^3.1.0"
177                    }
178                })
179            };
180
181            const jquery = {
182                path: "/a/data/node_modules/@types/jquery/index.d.ts",
183                content: "declare const $: { x: number }"
184            };
185            const host = createServerHost([file1, packageJson]);
186            const installer = new (class extends Installer {
187                constructor() {
188                    super(host, { typesRegistry: createTypesRegistry("jquery") });
189                }
190                installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) {
191                    const installedTypings = ["@types/jquery"];
192                    const typingFiles = [jquery];
193                    executeCommand(this, host, installedTypings, typingFiles, cb);
194                }
195            })();
196
197            const projectService = createProjectService(host, { useSingleInferredProject: true, typingsInstaller: installer });
198            projectService.openClientFile(file1.path);
199
200            checkNumberOfProjects(projectService, { inferredProjects: 1 });
201            const p = projectService.inferredProjects[0];
202            checkProjectActualFiles(p, [file1.path]);
203
204            installer.installAll(/*expectedCount*/ 1);
205            host.checkTimeoutQueueLengthAndRun(2);
206            checkNumberOfProjects(projectService, { inferredProjects: 1 });
207            checkProjectActualFiles(p, [file1.path, jquery.path]);
208        });
209
210        it("inferred project - type acquisition with disableFilenameBasedTypeAcquisition:true", () => {
211            // Tests:
212            // Exclude file with disableFilenameBasedTypeAcquisition:true
213            const jqueryJs = {
214                path: "/a/b/jquery.js",
215                content: ""
216            };
217
218            const messages: string[] = [];
219            const host = createServerHost([jqueryJs]);
220            const installer = new (class extends Installer {
221                constructor() {
222                    super(host, { typesRegistry: createTypesRegistry("jquery") }, { isEnabled: () => true, writeLine: msg => messages.push(msg) });
223                }
224                enqueueInstallTypingsRequest(project: server.Project, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray<string>) {
225                    super.enqueueInstallTypingsRequest(project, typeAcquisition, unresolvedImports);
226                }
227                installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction): void {
228                    const installedTypings: string[] = [];
229                    const typingFiles: File[] = [];
230                    executeCommand(this, host, installedTypings, typingFiles, cb);
231                }
232            })();
233
234            const projectService = createProjectService(host, { typingsInstaller: installer });
235            projectService.setCompilerOptionsForInferredProjects({
236                allowJs: true,
237                enable: true,
238                disableFilenameBasedTypeAcquisition: true
239            });
240            projectService.openClientFile(jqueryJs.path);
241
242            checkNumberOfProjects(projectService, { inferredProjects: 1 });
243            const p = projectService.inferredProjects[0];
244            checkProjectActualFiles(p, [jqueryJs.path]);
245
246            installer.installAll(/*expectedCount*/ 0);
247            host.checkTimeoutQueueLength(0);
248            checkNumberOfProjects(projectService, { inferredProjects: 1 });
249            // files should not be removed from project if ATA is skipped
250            checkProjectActualFiles(p, [jqueryJs.path]);
251            assert.isTrue(messages.indexOf("No new typings were requested as a result of typings discovery") > 0, "Should not request filename-based typings");
252        });
253
254        it("external project - no type acquisition, no .d.ts/js files", () => {
255            const file1 = {
256                path: "/a/b/app.ts",
257                content: ""
258            };
259            const host = createServerHost([file1]);
260            const installer = new (class extends Installer {
261                constructor() {
262                    super(host);
263                }
264                enqueueInstallTypingsRequest() {
265                    assert(false, "auto discovery should not be enabled");
266                }
267            })();
268
269            const projectFileName = "/a/app/test.csproj";
270            const projectService = createProjectService(host, { typingsInstaller: installer });
271            projectService.openExternalProject({
272                projectFileName,
273                options: {},
274                rootFiles: [toExternalFile(file1.path)]
275            });
276            installer.checkPendingCommands(/*expectedCount*/ 0);
277            // by default auto discovery will kick in if project contain only .js/.d.ts files
278            // in this case project contain only ts files - no auto discovery
279            projectService.checkNumberOfProjects({ externalProjects: 1 });
280        });
281
282        it("external project - deduplicate from local @types packages", () => {
283            const appJs = {
284                path: "/a/b/app.js",
285                content: ""
286            };
287            const nodeDts = {
288                path: "/node_modules/@types/node/index.d.ts",
289                content: "declare var node;"
290            };
291            const host = createServerHost([appJs, nodeDts]);
292            const installer = new (class extends Installer {
293                constructor() {
294                    super(host, { typesRegistry: createTypesRegistry("node") });
295                }
296                installWorker() {
297                    assert(false, "nothing should get installed");
298                }
299            })();
300
301            const projectFileName = "/a/app/test.csproj";
302            const projectService = createProjectService(host, { typingsInstaller: installer });
303            projectService.openExternalProject({
304                projectFileName,
305                options: {},
306                rootFiles: [toExternalFile(appJs.path)],
307                typeAcquisition: { enable: true, include: ["node"] }
308            });
309            installer.checkPendingCommands(/*expectedCount*/ 0);
310            projectService.checkNumberOfProjects({ externalProjects: 1 });
311        });
312
313        it("external project - no auto in typing acquisition, no .d.ts/js files", () => {
314            const file1 = {
315                path: "/a/b/app.ts",
316                content: ""
317            };
318            const host = createServerHost([file1]);
319            const installer = new (class extends Installer {
320                constructor() {
321                    super(host, { typesRegistry: createTypesRegistry("jquery") });
322                }
323                enqueueInstallTypingsRequest() {
324                    assert(false, "auto discovery should not be enabled");
325                }
326            })();
327
328            const projectFileName = "/a/app/test.csproj";
329            const projectService = createProjectService(host, { typingsInstaller: installer });
330            projectService.openExternalProject({
331                projectFileName,
332                options: {},
333                rootFiles: [toExternalFile(file1.path)],
334                typeAcquisition: { include: ["jquery"] }
335            });
336            installer.checkPendingCommands(/*expectedCount*/ 0);
337            // by default auto discovery will kick in if project contain only .js/.d.ts files
338            // in this case project contain only ts files - no auto discovery even if type acquisition is set
339            projectService.checkNumberOfProjects({ externalProjects: 1 });
340        });
341
342        it("external project - autoDiscovery = true, no .d.ts/js files", () => {
343            const file1 = {
344                path: "/a/b/app.ts",
345                content: ""
346            };
347            const jquery = {
348                path: "/a/data/node_modules/@types/jquery/index.d.ts",
349                content: "declare const $: { x: number }"
350            };
351            const host = createServerHost([file1]);
352            let enqueueIsCalled = false;
353            const installer: Installer = new (class extends Installer {
354                constructor() {
355                    super(host, { typesRegistry: createTypesRegistry("jquery") });
356                }
357                enqueueInstallTypingsRequest(project: server.Project, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray<string>) {
358                    enqueueIsCalled = true;
359                    super.enqueueInstallTypingsRequest(project, typeAcquisition, unresolvedImports);
360                }
361                installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction): void {
362                    const installedTypings = ["@types/node"];
363                    const typingFiles = [jquery];
364                    executeCommand(this, host, installedTypings, typingFiles, cb);
365                }
366            })();
367
368            const projectFileName = "/a/app/test.csproj";
369            const projectService = createProjectService(host, { typingsInstaller: installer });
370            projectService.openExternalProject({
371                projectFileName,
372                options: {},
373                rootFiles: [toExternalFile(file1.path)],
374                typeAcquisition: { enable: true, include: ["jquery"] }
375            });
376
377            assert.isTrue(enqueueIsCalled, "expected enqueueIsCalled to be true");
378            installer.installAll(/*expectedCount*/ 1);
379
380            // auto is set in type acquisition - use it even if project contains only .ts files
381            projectService.checkNumberOfProjects({ externalProjects: 1 });
382        });
383
384        it("external project - no type acquisition, with only js, jsx, d.ts files", () => {
385            // Tests:
386            // 1. react typings are installed for .jsx
387            // 2. loose files names are matched against safe list for typings if
388            //    this is a JS project (only js, jsx, d.ts files are present)
389            const lodashJs = {
390                path: "/a/b/lodash.js",
391                content: ""
392            };
393            const file2Jsx = {
394                path: "/a/b/file2.jsx",
395                content: ""
396            };
397            const file3dts = {
398                path: "/a/b/file3.d.ts",
399                content: ""
400            };
401            const reactDts = {
402                path: "/a/data/node_modules/@types/react/index.d.ts",
403                content: "declare const react: { x: number }"
404            };
405            const lodashDts = {
406                path: "/a/data/node_modules/@types/lodash/index.d.ts",
407                content: "declare const lodash: { x: number }"
408            };
409
410            const host = createServerHost([lodashJs, file2Jsx, file3dts, customTypesMap]);
411            const installer = new (class extends Installer {
412                constructor() {
413                    super(host, { typesRegistry: createTypesRegistry("lodash", "react") });
414                }
415                installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction): void {
416                    const installedTypings = ["@types/lodash", "@types/react"];
417                    const typingFiles = [lodashDts, reactDts];
418                    executeCommand(this, host, installedTypings, typingFiles, cb);
419                }
420            })();
421
422            const projectFileName = "/a/app/test.csproj";
423            const projectService = createProjectService(host, { typingsInstaller: installer });
424            projectService.openExternalProject({
425                projectFileName,
426                options: { allowJS: true, moduleResolution: ModuleResolutionKind.NodeJs },
427                rootFiles: [toExternalFile(lodashJs.path), toExternalFile(file2Jsx.path), toExternalFile(file3dts.path)],
428                typeAcquisition: { }
429            });
430
431            const p = projectService.externalProjects[0];
432            projectService.checkNumberOfProjects({ externalProjects: 1 });
433            checkProjectActualFiles(p, [file2Jsx.path, file3dts.path]);
434
435            installer.installAll(/*expectedCount*/ 1);
436
437            checkNumberOfProjects(projectService, { externalProjects: 1 });
438            host.checkTimeoutQueueLengthAndRun(1);
439            checkNumberOfProjects(projectService, { externalProjects: 1 });
440            checkProjectActualFiles(p, [file2Jsx.path, file3dts.path, lodashDts.path, reactDts.path]);
441        });
442
443        it("external project - type acquisition with enable: false", () => {
444            // Tests:
445            // Exclude
446            const jqueryJs = {
447                path: "/a/b/jquery.js",
448                content: ""
449            };
450
451            const host = createServerHost([jqueryJs]);
452            const installer = new (class extends Installer {
453                constructor() {
454                    super(host, { typesRegistry: createTypesRegistry("jquery") });
455                }
456                enqueueInstallTypingsRequest(project: server.Project, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray<string>) {
457                    super.enqueueInstallTypingsRequest(project, typeAcquisition, unresolvedImports);
458                }
459                installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction): void {
460                    const installedTypings: string[] = [];
461                    const typingFiles: File[] = [];
462                    executeCommand(this, host, installedTypings, typingFiles, cb);
463                }
464            })();
465
466            const projectFileName = "/a/app/test.csproj";
467            const projectService = createProjectService(host, { typingsInstaller: installer });
468            projectService.openExternalProject({
469                projectFileName,
470                options: { allowJS: true, moduleResolution: ModuleResolutionKind.NodeJs },
471                rootFiles: [toExternalFile(jqueryJs.path)],
472                typeAcquisition: { enable: false }
473            });
474
475            const p = projectService.externalProjects[0];
476            projectService.checkNumberOfProjects({ externalProjects: 1 });
477            checkProjectActualFiles(p, [jqueryJs.path]);
478
479            installer.checkPendingCommands(/*expectedCount*/ 0);
480        });
481
482        it("external project - type acquisition with disableFilenameBasedTypeAcquisition:true", () => {
483            // Tests:
484            // Exclude file with disableFilenameBasedTypeAcquisition:true
485            const jqueryJs = {
486                path: "/a/b/jquery.js",
487                content: ""
488            };
489
490            const messages: string[] = [];
491            const host = createServerHost([jqueryJs]);
492            const installer = new (class extends Installer {
493                constructor() {
494                    super(host, { typesRegistry: createTypesRegistry("jquery") }, { isEnabled: () => true, writeLine: msg => messages.push(msg) });
495                }
496                enqueueInstallTypingsRequest(project: server.Project, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray<string>) {
497                    super.enqueueInstallTypingsRequest(project, typeAcquisition, unresolvedImports);
498                }
499                installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction): void {
500                    const installedTypings: string[] = [];
501                    const typingFiles: File[] = [];
502                    executeCommand(this, host, installedTypings, typingFiles, cb);
503                }
504            })();
505
506            const projectFileName = "/a/app/test.csproj";
507            const projectService = createProjectService(host, { typingsInstaller: installer });
508            projectService.openExternalProject({
509                projectFileName,
510                options: { allowJS: true, moduleResolution: ModuleResolutionKind.NodeJs },
511                rootFiles: [toExternalFile(jqueryJs.path)],
512                typeAcquisition: { enable: true, disableFilenameBasedTypeAcquisition: true }
513            });
514
515            const p = projectService.externalProjects[0];
516            projectService.checkNumberOfProjects({ externalProjects: 1 });
517            checkProjectActualFiles(p, [jqueryJs.path]);
518
519            installer.installAll(/*expectedCount*/ 0);
520            projectService.checkNumberOfProjects({ externalProjects: 1 });
521            // files should not be removed from project if ATA is skipped
522            checkProjectActualFiles(p, [jqueryJs.path]);
523            assert.isTrue(messages.indexOf("No new typings were requested as a result of typings discovery") > 0, "Should not request filename-based typings");
524        });
525
526        it("external project - no type acquisition, with js & ts files", () => {
527            // Tests:
528            // 1. No typings are included for JS projects when the project contains ts files
529            const jqueryJs = {
530                path: "/a/b/jquery.js",
531                content: ""
532            };
533            const file2Ts = {
534                path: "/a/b/file2.ts",
535                content: ""
536            };
537
538            const host = createServerHost([jqueryJs, file2Ts]);
539            const installer = new (class extends Installer {
540                constructor() {
541                    super(host, { typesRegistry: createTypesRegistry("jquery") });
542                }
543                enqueueInstallTypingsRequest(project: server.Project, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray<string>) {
544                    super.enqueueInstallTypingsRequest(project, typeAcquisition, unresolvedImports);
545                }
546                installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction): void {
547                    const installedTypings: string[] = [];
548                    const typingFiles: File[] = [];
549                    executeCommand(this, host, installedTypings, typingFiles, cb);
550                }
551            })();
552
553            const projectFileName = "/a/app/test.csproj";
554            const projectService = createProjectService(host, { typingsInstaller: installer });
555            projectService.openExternalProject({
556                projectFileName,
557                options: { allowJS: true, moduleResolution: ModuleResolutionKind.NodeJs },
558                rootFiles: [toExternalFile(jqueryJs.path), toExternalFile(file2Ts.path)],
559                typeAcquisition: {}
560            });
561
562            const p = projectService.externalProjects[0];
563            projectService.checkNumberOfProjects({ externalProjects: 1 });
564
565            checkProjectActualFiles(p, [jqueryJs.path, file2Ts.path]);
566
567            installer.checkPendingCommands(/*expectedCount*/ 0);
568
569            checkNumberOfProjects(projectService, { externalProjects: 1 });
570            checkProjectActualFiles(p, [jqueryJs.path, file2Ts.path]);
571        });
572
573        it("external project - with type acquisition, with only js, d.ts files", () => {
574            // Tests:
575            // 1. Safelist matching, type acquisition includes/excludes and package.json typings are all acquired
576            // 2. Types for safelist matches are not included when they also appear in the type acquisition exclude list
577            // 3. Multiple includes and excludes are respected in type acquisition
578            const lodashJs = {
579                path: "/a/b/lodash.js",
580                content: ""
581            };
582            const commanderJs = {
583                path: "/a/b/commander.js",
584                content: ""
585            };
586            const file3dts = {
587                path: "/a/b/file3.d.ts",
588                content: ""
589            };
590            const packageJson = {
591                path: "/a/b/package.json",
592                content: JSON.stringify({
593                    name: "test",
594                    dependencies: {
595                        express: "^3.1.0"
596                    }
597                })
598            };
599
600            const commander = {
601                path: "/a/data/node_modules/@types/commander/index.d.ts",
602                content: "declare const commander: { x: number }"
603            };
604            const express = {
605                path: "/a/data/node_modules/@types/express/index.d.ts",
606                content: "declare const express: { x: number }"
607            };
608            const jquery = {
609                path: "/a/data/node_modules/@types/jquery/index.d.ts",
610                content: "declare const jquery: { x: number }"
611            };
612            const moment = {
613                path: "/a/data/node_modules/@types/moment/index.d.ts",
614                content: "declare const moment: { x: number }"
615            };
616
617            const host = createServerHost([lodashJs, commanderJs, file3dts, packageJson, customTypesMap]);
618            const installer = new (class extends Installer {
619                constructor() {
620                    super(host, { typesRegistry: createTypesRegistry("jquery", "commander", "moment", "express") });
621                }
622                installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction): void {
623                    const installedTypings = ["@types/commander", "@types/express", "@types/jquery", "@types/moment"];
624                    const typingFiles = [commander, express, jquery, moment];
625                    executeCommand(this, host, installedTypings, typingFiles, cb);
626                }
627            })();
628
629            const projectFileName = "/a/app/test.csproj";
630            const projectService = createProjectService(host, { typingsInstaller: installer });
631            projectService.openExternalProject({
632                projectFileName,
633                options: { allowJS: true, moduleResolution: ModuleResolutionKind.NodeJs },
634                rootFiles: [toExternalFile(lodashJs.path), toExternalFile(commanderJs.path), toExternalFile(file3dts.path)],
635                typeAcquisition: { enable: true, include: ["jquery", "moment"], exclude: ["lodash"] }
636            });
637
638            const p = projectService.externalProjects[0];
639            projectService.checkNumberOfProjects({ externalProjects: 1 });
640            checkProjectActualFiles(p, [file3dts.path]);
641
642            installer.installAll(/*expectedCount*/ 1);
643
644            checkNumberOfProjects(projectService, { externalProjects: 1 });
645            host.checkTimeoutQueueLengthAndRun(1);
646            checkNumberOfProjects(projectService, { externalProjects: 1 });
647            // Commander: Existed as a JS file
648            // JQuery: Specified in 'include'
649            // Moment: Specified in 'include'
650            // Express: Specified in package.json
651            // lodash: Excluded (not present)
652            checkProjectActualFiles(p, [file3dts.path, commander.path, jquery.path, moment.path, express.path]);
653        });
654
655        it("Throttle - delayed typings to install", () => {
656            const lodashJs = {
657                path: "/a/b/lodash.js",
658                content: ""
659            };
660            const commanderJs = {
661                path: "/a/b/commander.js",
662                content: ""
663            };
664            const file3 = {
665                path: "/a/b/file3.d.ts",
666                content: ""
667            };
668            const packageJson = {
669                path: "/a/b/package.json",
670                content: JSON.stringify({
671                    name: "test",
672                    dependencies: {
673                        express: "^3.1.0"
674                    }
675                })
676            };
677
678            const commander = {
679                path: "/a/data/node_modules/@types/commander/index.d.ts",
680                content: "declare const commander: { x: number }"
681            };
682            const express = {
683                path: "/a/data/node_modules/@types/express/index.d.ts",
684                content: "declare const express: { x: number }"
685            };
686            const jquery = {
687                path: "/a/data/node_modules/@types/jquery/index.d.ts",
688                content: "declare const jquery: { x: number }"
689            };
690            const moment = {
691                path: "/a/data/node_modules/@types/moment/index.d.ts",
692                content: "declare const moment: { x: number }"
693            };
694            const lodash = {
695                path: "/a/data/node_modules/@types/lodash/index.d.ts",
696                content: "declare const lodash: { x: number }"
697            };
698
699            const typingFiles = [commander, express, jquery, moment, lodash];
700            const host = createServerHost([lodashJs, commanderJs, file3, packageJson, customTypesMap]);
701            const installer = new (class extends Installer {
702                constructor() {
703                    super(host, { throttleLimit: 3, typesRegistry: createTypesRegistry("commander", "express", "jquery", "moment", "lodash") });
704                }
705                installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction): void {
706                    const installedTypings = ["@types/commander", "@types/express", "@types/jquery", "@types/moment", "@types/lodash"];
707                    executeCommand(this, host, installedTypings, typingFiles, cb);
708                }
709            })();
710
711            const projectFileName = "/a/app/test.csproj";
712            const projectService = createProjectService(host, { typingsInstaller: installer });
713            projectService.openExternalProject({
714                projectFileName,
715                options: { allowJS: true, moduleResolution: ModuleResolutionKind.NodeJs },
716                rootFiles: [toExternalFile(lodashJs.path), toExternalFile(commanderJs.path), toExternalFile(file3.path)],
717                typeAcquisition: { include: ["jquery", "moment"] }
718            });
719
720            const p = projectService.externalProjects[0];
721            projectService.checkNumberOfProjects({ externalProjects: 1 });
722            checkProjectActualFiles(p, [file3.path]);
723            installer.checkPendingCommands(/*expectedCount*/ 1);
724            installer.executePendingCommands();
725            // expected all typings file to exist
726            for (const f of typingFiles) {
727                assert.isTrue(host.fileExists(f.path), `expected file ${f.path} to exist`);
728            }
729            host.checkTimeoutQueueLengthAndRun(1);
730            checkNumberOfProjects(projectService, { externalProjects: 1 });
731            checkProjectActualFiles(p, [file3.path, commander.path, express.path, jquery.path, moment.path, lodash.path]);
732        });
733
734        it("Throttle - delayed run install requests", () => {
735            const lodashJs = {
736                path: "/a/b/lodash.js",
737                content: ""
738            };
739            const commanderJs = {
740                path: "/a/b/commander.js",
741                content: ""
742            };
743            const file3 = {
744                path: "/a/b/file3.d.ts",
745                content: ""
746            };
747
748            const commander = {
749                path: "/a/data/node_modules/@types/commander/index.d.ts",
750                content: "declare const commander: { x: number }",
751                typings: typingsName("commander")
752            };
753            const jquery = {
754                path: "/a/data/node_modules/@types/jquery/index.d.ts",
755                content: "declare const jquery: { x: number }",
756                typings: typingsName("jquery")
757            };
758            const lodash = {
759                path: "/a/data/node_modules/@types/lodash/index.d.ts",
760                content: "declare const lodash: { x: number }",
761                typings: typingsName("lodash")
762            };
763            const cordova = {
764                path: "/a/data/node_modules/@types/cordova/index.d.ts",
765                content: "declare const cordova: { x: number }",
766                typings: typingsName("cordova")
767            };
768            const grunt = {
769                path: "/a/data/node_modules/@types/grunt/index.d.ts",
770                content: "declare const grunt: { x: number }",
771                typings: typingsName("grunt")
772            };
773            const gulp = {
774                path: "/a/data/node_modules/@types/gulp/index.d.ts",
775                content: "declare const gulp: { x: number }",
776                typings: typingsName("gulp")
777            };
778
779            const host = createServerHost([lodashJs, commanderJs, file3, customTypesMap]);
780            const installer = new (class extends Installer {
781                constructor() {
782                    super(host, { throttleLimit: 1, typesRegistry: createTypesRegistry("commander", "jquery", "lodash", "cordova", "gulp", "grunt") });
783                }
784                installWorker(_requestId: number, args: string[], _cwd: string, cb: TI.RequestCompletedAction): void {
785                    let typingFiles: (File & { typings: string })[] = [];
786                    if (args.indexOf(typingsName("commander")) >= 0) {
787                        typingFiles = [commander, jquery, lodash, cordova];
788                    }
789                    else {
790                        typingFiles = [grunt, gulp];
791                    }
792                    executeCommand(this, host, typingFiles.map(f => f.typings), typingFiles, cb);
793                }
794            })();
795
796            // Create project #1 with 4 typings
797            const projectService = createProjectService(host, { typingsInstaller: installer });
798            const projectFileName1 = "/a/app/test1.csproj";
799            projectService.openExternalProject({
800                projectFileName: projectFileName1,
801                options: { allowJS: true, moduleResolution: ModuleResolutionKind.NodeJs },
802                rootFiles: [toExternalFile(lodashJs.path), toExternalFile(commanderJs.path), toExternalFile(file3.path)],
803                typeAcquisition: { include: ["jquery", "cordova"] }
804            });
805
806            installer.checkPendingCommands(/*expectedCount*/ 1);
807            assert.equal(installer.pendingRunRequests.length, 0, "expect no throttled requests");
808
809            // Create project #2 with 2 typings
810            const projectFileName2 = "/a/app/test2.csproj";
811            projectService.openExternalProject({
812                projectFileName: projectFileName2,
813                options: { allowJS: true, moduleResolution: ModuleResolutionKind.NodeJs },
814                rootFiles: [toExternalFile(file3.path)],
815                typeAcquisition: { include: ["grunt", "gulp"] }
816            });
817            assert.equal(installer.pendingRunRequests.length, 1, "expect one throttled request");
818
819            const p1 = projectService.externalProjects[0];
820            const p2 = projectService.externalProjects[1];
821            projectService.checkNumberOfProjects({ externalProjects: 2 });
822            checkProjectActualFiles(p1, [file3.path]);
823            checkProjectActualFiles(p2, [file3.path]);
824
825            installer.executePendingCommands();
826
827            // expected one install request from the second project
828            installer.checkPendingCommands(/*expectedCount*/ 1);
829            assert.equal(installer.pendingRunRequests.length, 0, "expected no throttled requests");
830
831            installer.executePendingCommands();
832            host.checkTimeoutQueueLengthAndRun(2); // for 2 projects
833            checkProjectActualFiles(p1, [file3.path, commander.path, jquery.path, lodash.path, cordova.path]);
834            checkProjectActualFiles(p2, [file3.path, grunt.path, gulp.path]);
835        });
836
837        it("configured projects discover from node_modules", () => {
838            const app = {
839                path: "/app.js",
840                content: ""
841            };
842            const jsconfig = {
843                path: "/jsconfig.json",
844                content: JSON.stringify({})
845            };
846            const jquery = {
847                path: "/node_modules/jquery/index.js",
848                content: ""
849            };
850            const jqueryPackage = {
851                path: "/node_modules/jquery/package.json",
852                content: JSON.stringify({ name: "jquery" })
853            };
854            // Should not search deeply in node_modules.
855            const nestedPackage = {
856                path: "/node_modules/jquery/nested/package.json",
857                content: JSON.stringify({ name: "nested" }),
858            };
859            const jqueryDTS = {
860                path: "/tmp/node_modules/@types/jquery/index.d.ts",
861                content: ""
862            };
863            const host = createServerHost([app, jsconfig, jquery, jqueryPackage, nestedPackage]);
864            const installer = new (class extends Installer {
865                constructor() {
866                    super(host, { globalTypingsCacheLocation: "/tmp", typesRegistry: createTypesRegistry("jquery", "nested") });
867                }
868                installWorker(_requestId: number, args: string[], _cwd: string, cb: TI.RequestCompletedAction) {
869                    assert.deepEqual(args, [`@types/jquery@ts${versionMajorMinor}`]);
870                    const installedTypings = ["@types/jquery"];
871                    const typingFiles = [jqueryDTS];
872                    executeCommand(this, host, installedTypings, typingFiles, cb);
873                }
874            })();
875
876            const projectService = createProjectService(host, { useSingleInferredProject: true, typingsInstaller: installer });
877            projectService.openClientFile(app.path);
878
879            checkNumberOfProjects(projectService, { configuredProjects: 1 });
880            const p = configuredProjectAt(projectService, 0);
881            checkProjectActualFiles(p, [app.path, jsconfig.path]);
882
883            installer.installAll(/*expectedCount*/ 1);
884
885            checkNumberOfProjects(projectService, { configuredProjects: 1 });
886            host.checkTimeoutQueueLengthAndRun(2);
887            checkProjectActualFiles(p, [app.path, jqueryDTS.path, jsconfig.path]);
888        });
889
890        it("configured projects discover from bower_components", () => {
891            const app = {
892                path: "/app.js",
893                content: ""
894            };
895            const jsconfig = {
896                path: "/jsconfig.json",
897                content: JSON.stringify({})
898            };
899            const jquery = {
900                path: "/bower_components/jquery/index.js",
901                content: ""
902            };
903            const jqueryPackage = {
904                path: "/bower_components/jquery/package.json",
905                content: JSON.stringify({ name: "jquery" })
906            };
907            const jqueryDTS = {
908                path: "/tmp/node_modules/@types/jquery/index.d.ts",
909                content: ""
910            };
911            const host = createServerHost([app, jsconfig, jquery, jqueryPackage]);
912            const installer = new (class extends Installer {
913                constructor() {
914                    super(host, { globalTypingsCacheLocation: "/tmp", typesRegistry: createTypesRegistry("jquery") });
915                }
916                installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) {
917                    const installedTypings = ["@types/jquery"];
918                    const typingFiles = [jqueryDTS];
919                    executeCommand(this, host, installedTypings, typingFiles, cb);
920                }
921            })();
922
923            const projectService = createProjectService(host, { useSingleInferredProject: true, typingsInstaller: installer });
924            projectService.openClientFile(app.path);
925
926            checkNumberOfProjects(projectService, { configuredProjects: 1 });
927            const p = configuredProjectAt(projectService, 0);
928            checkProjectActualFiles(p, [app.path, jsconfig.path]);
929
930            const watchedFilesExpected = new Map<string, number>();
931            watchedFilesExpected.set(jsconfig.path, 1); // project files
932            watchedFilesExpected.set(libFile.path, 1); // project files
933            watchedFilesExpected.set(combinePaths(installer.globalTypingsCacheLocation, "package.json"), 1);
934            checkWatchedFilesDetailed(host, watchedFilesExpected);
935
936            checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
937
938            checkWatchedDirectoriesDetailed(host, ["/", "/node_modules", "/bower_components"], 1, /*recursive*/ true);
939
940            installer.installAll(/*expectedCount*/ 1);
941
942            checkNumberOfProjects(projectService, { configuredProjects: 1 });
943            host.checkTimeoutQueueLengthAndRun(2);
944            checkProjectActualFiles(p, [app.path, jqueryDTS.path, jsconfig.path]);
945        });
946
947        it("configured projects discover from bower.json", () => {
948            const app = {
949                path: "/app.js",
950                content: ""
951            };
952            const jsconfig = {
953                path: "/jsconfig.json",
954                content: JSON.stringify({})
955            };
956            const bowerJson = {
957                path: "/bower.json",
958                content: JSON.stringify({
959                    dependencies: {
960                        jquery: "^3.1.0"
961                    }
962                })
963            };
964            const jqueryDTS = {
965                path: "/tmp/node_modules/@types/jquery/index.d.ts",
966                content: ""
967            };
968            const host = createServerHost([app, jsconfig, bowerJson]);
969            const installer = new (class extends Installer {
970                constructor() {
971                    super(host, { globalTypingsCacheLocation: "/tmp", typesRegistry: createTypesRegistry("jquery") });
972                }
973                installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) {
974                    const installedTypings = ["@types/jquery"];
975                    const typingFiles = [jqueryDTS];
976                    executeCommand(this, host, installedTypings, typingFiles, cb);
977                }
978            })();
979
980            const projectService = createProjectService(host, { useSingleInferredProject: true, typingsInstaller: installer });
981            projectService.openClientFile(app.path);
982
983            checkNumberOfProjects(projectService, { configuredProjects: 1 });
984            const p = configuredProjectAt(projectService, 0);
985            checkProjectActualFiles(p, [app.path, jsconfig.path]);
986
987            installer.installAll(/*expectedCount*/ 1);
988
989            checkNumberOfProjects(projectService, { configuredProjects: 1 });
990            host.checkTimeoutQueueLengthAndRun(2);
991            checkProjectActualFiles(p, [app.path, jqueryDTS.path, jsconfig.path]);
992        });
993
994        it("Malformed package.json should be watched", () => {
995            const f = {
996                path: "/a/b/app.js",
997                content: "var x = 1"
998            };
999            const brokenPackageJson = {
1000                path: "/a/b/package.json",
1001                content: `{ "dependencies": { "co } }`
1002            };
1003            const fixedPackageJson = {
1004                path: brokenPackageJson.path,
1005                content: `{ "dependencies": { "commander": "0.0.2" } }`
1006            };
1007            const cachePath = "/a/cache/";
1008            const commander = {
1009                path: cachePath + "node_modules/@types/commander/index.d.ts",
1010                content: "export let x: number"
1011            };
1012            const host = createServerHost([f, brokenPackageJson]);
1013            const installer = new (class extends Installer {
1014                constructor() {
1015                    super(host, { globalTypingsCacheLocation: cachePath, typesRegistry: createTypesRegistry("commander") });
1016                }
1017                installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) {
1018                    const installedTypings = ["@types/commander"];
1019                    const typingFiles = [commander];
1020                    executeCommand(this, host, installedTypings, typingFiles, cb);
1021                }
1022            })();
1023            const service = createProjectService(host, { typingsInstaller: installer });
1024            service.openClientFile(f.path);
1025
1026            installer.checkPendingCommands(/*expectedCount*/ 0);
1027            host.writeFile(fixedPackageJson.path, fixedPackageJson.content);
1028            host.checkTimeoutQueueLength(0);
1029            // expected install request
1030            installer.installAll(/*expectedCount*/ 1);
1031            host.checkTimeoutQueueLengthAndRun(2);
1032            service.checkNumberOfProjects({ inferredProjects: 1 });
1033            checkProjectActualFiles(service.inferredProjects[0], [f.path, commander.path]);
1034        });
1035
1036        it("should install typings for unresolved imports", () => {
1037            const file = {
1038                path: "/a/b/app.js",
1039                content: `
1040                import * as fs from "fs";
1041                import * as commander from "commander";
1042                import * as component from "@ember/component";`
1043            };
1044            const cachePath = "/a/cache";
1045            const node = {
1046                path: cachePath + "/node_modules/@types/node/index.d.ts",
1047                content: "export let x: number"
1048            };
1049            const commander = {
1050                path: cachePath + "/node_modules/@types/commander/index.d.ts",
1051                content: "export let y: string"
1052            };
1053            const emberComponentDirectory = "ember__component";
1054            const emberComponent = {
1055                path: `${cachePath}/node_modules/@types/${emberComponentDirectory}/index.d.ts`,
1056                content: "export let x: number"
1057            };
1058            const host = createServerHost([file]);
1059            const installer = new (class extends Installer {
1060                constructor() {
1061                    super(host, { globalTypingsCacheLocation: cachePath, typesRegistry: createTypesRegistry("node", "commander") });
1062                }
1063                installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) {
1064                    const installedTypings = ["@types/node", "@types/commander", `@types/${emberComponentDirectory}`];
1065                    const typingFiles = [node, commander, emberComponent];
1066                    executeCommand(this, host, installedTypings, typingFiles, cb);
1067                }
1068            })();
1069            const service = createProjectService(host, { typingsInstaller: installer });
1070            service.openClientFile(file.path);
1071
1072            service.checkNumberOfProjects({ inferredProjects: 1 });
1073            checkProjectActualFiles(service.inferredProjects[0], [file.path]);
1074
1075            installer.installAll(/*expectedCount*/1);
1076
1077            assert.isTrue(host.fileExists(node.path), "typings for 'node' should be created");
1078            assert.isTrue(host.fileExists(commander.path), "typings for 'commander' should be created");
1079            assert.isTrue(host.fileExists(emberComponent.path), "typings for 'commander' should be created");
1080
1081            host.checkTimeoutQueueLengthAndRun(2);
1082            checkProjectActualFiles(service.inferredProjects[0], [file.path, node.path, commander.path, emberComponent.path]);
1083        });
1084
1085        it("should redo resolution that resolved to '.js' file after typings are installed", () => {
1086            const file: TestFSWithWatch.File = {
1087                path: `${tscWatch.projects}/a/b/app.js`,
1088                content: `
1089                import * as commander from "commander";`
1090            };
1091            const cachePath = `${tscWatch.projects}/a/cache`;
1092            const commanderJS: TestFSWithWatch.File = {
1093                path: `${tscWatch.projects}/node_modules/commander/index.js`,
1094                content: "module.exports = 0",
1095            };
1096
1097            const typeNames: readonly string[] = ["commander"];
1098            const typePath = (name: string): string => `${cachePath}/node_modules/@types/${name}/index.d.ts`;
1099            const host = createServerHost([file, commanderJS]);
1100            const installer = new (class extends Installer {
1101                constructor() {
1102                    super(host, { globalTypingsCacheLocation: cachePath, typesRegistry: createTypesRegistry(...typeNames) });
1103                }
1104                installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) {
1105                    const installedTypings = typeNames.map(name => `@types/${name}`);
1106                    const typingFiles = typeNames.map((name): TestFSWithWatch.File => ({ path: typePath(name), content: "" }));
1107                    executeCommand(this, host, installedTypings, typingFiles, cb);
1108                }
1109            })();
1110            const service = createProjectService(host, { typingsInstaller: installer });
1111            service.openClientFile(file.path);
1112
1113            checkWatchedFiles(host, [...getConfigFilesToWatch(getDirectoryPath(file.path)), "/a/lib/lib.d.ts"]);
1114            checkWatchedDirectories(host, [], /*recursive*/ false);
1115            // Does not include cachePath because that is handled by typingsInstaller
1116            checkWatchedDirectories(host, [
1117                `${tscWatch.projects}/node_modules`,
1118                `${tscWatch.projects}/a/node_modules`,
1119                `${tscWatch.projects}/a/b/node_modules`,
1120                `${tscWatch.projects}/a/node_modules/@types`,
1121                `${tscWatch.projects}/a/b/node_modules/@types`,
1122                `${tscWatch.projects}/a/b/bower_components`
1123            ], /*recursive*/ true);
1124
1125            service.checkNumberOfProjects({ inferredProjects: 1 });
1126            checkProjectActualFiles(service.inferredProjects[0], [file.path, commanderJS.path]);
1127
1128            installer.installAll(/*expectedCount*/1);
1129            for (const name of typeNames) {
1130                assert.isTrue(host.fileExists(typePath(name)), `typings for '${name}' should be created`);
1131            }
1132            host.checkTimeoutQueueLengthAndRun(2);
1133            checkProjectActualFiles(service.inferredProjects[0], [file.path, ...typeNames.map(typePath)]);
1134        });
1135
1136        it("should pick typing names from non-relative unresolved imports", () => {
1137            const f1 = {
1138                path: "/a/b/app.js",
1139                content: `
1140                import * as a from "foo/a/a";
1141                import * as b from "foo/a/b";
1142                import * as c from "foo/a/c";
1143                import * as d from "@bar/router/";
1144                import * as e from "@bar/common/shared";
1145                import * as e from "@bar/common/apps";
1146                import * as f from "./lib"
1147                `
1148            };
1149
1150            const host = createServerHost([f1]);
1151            const installer = new (class extends Installer {
1152                constructor() {
1153                    super(host, { globalTypingsCacheLocation: "/tmp", typesRegistry: createTypesRegistry("foo") });
1154                }
1155                installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) {
1156                    executeCommand(this, host, ["foo"], [], cb);
1157                }
1158            })();
1159            const projectService = createProjectService(host, { typingsInstaller: installer });
1160            projectService.openClientFile(f1.path);
1161            projectService.checkNumberOfProjects({ inferredProjects: 1 });
1162
1163            const proj = projectService.inferredProjects[0];
1164            proj.updateGraph();
1165
1166            assert.deepEqual(
1167                proj.cachedUnresolvedImportsPerFile.get(<Path>f1.path),
1168                ["foo", "foo", "foo", "@bar/router", "@bar/common", "@bar/common"]
1169            );
1170
1171            installer.installAll(/*expectedCount*/ 1);
1172        });
1173
1174        it("cached unresolved typings are not recomputed if program structure did not change", () => {
1175            const host = createServerHost([]);
1176            const session = createSession(host);
1177            const f = {
1178                path: "/a/app.js",
1179                content: `
1180                import * as fs from "fs";
1181                import * as cmd from "commander
1182                `
1183            };
1184            const openRequest: server.protocol.OpenRequest = {
1185                seq: 1,
1186                type: "request",
1187                command: server.protocol.CommandTypes.Open,
1188                arguments: {
1189                    file: f.path,
1190                    fileContent: f.content
1191                }
1192            };
1193            session.executeCommand(openRequest);
1194            const projectService = session.getProjectService();
1195            checkNumberOfProjects(projectService, { inferredProjects: 1 });
1196            const proj = projectService.inferredProjects[0];
1197            const version1 = proj.lastCachedUnresolvedImportsList;
1198
1199            // make a change that should not affect the structure of the program
1200            const changeRequest: server.protocol.ChangeRequest = {
1201                seq: 2,
1202                type: "request",
1203                command: server.protocol.CommandTypes.Change,
1204                arguments: {
1205                    file: f.path,
1206                    insertString: "\nlet x = 1;",
1207                    line: 2,
1208                    offset: 0,
1209                    endLine: 2,
1210                    endOffset: 0
1211                }
1212            };
1213            session.executeCommand(changeRequest);
1214            host.checkTimeoutQueueLength(0);
1215            proj.updateGraph();
1216            const version2 = proj.lastCachedUnresolvedImportsList;
1217            assert.strictEqual(version1, version2, "set of unresolved imports should change");
1218        });
1219
1220        it("expired cache entry (inferred project, should install typings)", () => {
1221            const file1 = {
1222                path: "/a/b/app.js",
1223                content: ""
1224            };
1225            const packageJson = {
1226                path: "/a/b/package.json",
1227                content: JSON.stringify({
1228                    name: "test",
1229                    dependencies: {
1230                        jquery: "^3.1.0"
1231                    }
1232                })
1233            };
1234            const jquery = {
1235                path: "/a/data/node_modules/@types/jquery/index.d.ts",
1236                content: "declare const $: { x: number }"
1237            };
1238            const cacheConfig = {
1239                path: "/a/data/package.json",
1240                content: JSON.stringify({
1241                    dependencies: {
1242                        "types-registry": "^0.1.317"
1243                    },
1244                    devDependencies: {
1245                        "@types/jquery": "^1.0.0"
1246                    }
1247                })
1248            };
1249            const cacheLockConfig = {
1250                path: "/a/data/package-lock.json",
1251                content: JSON.stringify({
1252                    dependencies: {
1253                        "@types/jquery": {
1254                            version: "1.0.0"
1255                        }
1256                    }
1257                })
1258            };
1259            const host = createServerHost([file1, packageJson, jquery, cacheConfig, cacheLockConfig]);
1260            const installer = new (class extends Installer {
1261                constructor() {
1262                    super(host, { typesRegistry: createTypesRegistry("jquery") });
1263                }
1264                installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) {
1265                    const installedTypings = ["@types/jquery"];
1266                    const typingFiles = [jquery];
1267                    executeCommand(this, host, installedTypings, typingFiles, cb);
1268                }
1269            })();
1270
1271            const projectService = createProjectService(host, { useSingleInferredProject: true, typingsInstaller: installer });
1272            projectService.openClientFile(file1.path);
1273
1274            checkNumberOfProjects(projectService, { inferredProjects: 1 });
1275            const p = projectService.inferredProjects[0];
1276            checkProjectActualFiles(p, [file1.path]);
1277
1278            installer.installAll(/*expectedCount*/ 1);
1279            host.checkTimeoutQueueLengthAndRun(2);
1280            checkNumberOfProjects(projectService, { inferredProjects: 1 });
1281            checkProjectActualFiles(p, [file1.path, jquery.path]);
1282        });
1283
1284        it("non-expired cache entry (inferred project, should not install typings)", () => {
1285            const file1 = {
1286                path: "/a/b/app.js",
1287                content: ""
1288            };
1289            const packageJson = {
1290                path: "/a/b/package.json",
1291                content: JSON.stringify({
1292                    name: "test",
1293                    dependencies: {
1294                        jquery: "^3.1.0"
1295                    }
1296                })
1297            };
1298            const timestamps = {
1299                path: "/a/data/timestamps.json",
1300                content: JSON.stringify({
1301                    entries: {
1302                        "@types/jquery": Date.now()
1303                    }
1304                })
1305            };
1306            const cacheConfig = {
1307                path: "/a/data/package.json",
1308                content: JSON.stringify({
1309                    dependencies: {
1310                        "types-registry": "^0.1.317"
1311                    },
1312                    devDependencies: {
1313                        "@types/jquery": "^1.3.0"
1314                    }
1315                })
1316            };
1317            const cacheLockConfig = {
1318                path: "/a/data/package-lock.json",
1319                content: JSON.stringify({
1320                    dependencies: {
1321                        "@types/jquery": {
1322                            version: "1.3.0"
1323                        }
1324                    }
1325                })
1326            };
1327            const jquery = {
1328                path: "/a/data/node_modules/@types/jquery/index.d.ts",
1329                content: "declare const $: { x: number }"
1330            };
1331            const host = createServerHost([file1, packageJson, timestamps, cacheConfig, cacheLockConfig, jquery]);
1332            const installer = new (class extends Installer {
1333                constructor() {
1334                    super(host, { typesRegistry: createTypesRegistry("jquery") });
1335                }
1336                installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) {
1337                    const installedTypings: string[] = [];
1338                    const typingFiles: File[] = [];
1339                    executeCommand(this, host, installedTypings, typingFiles, cb);
1340                }
1341            })();
1342
1343            const projectService = createProjectService(host, { useSingleInferredProject: true, typingsInstaller: installer });
1344            projectService.openClientFile(file1.path);
1345
1346            checkNumberOfProjects(projectService, { inferredProjects: 1 });
1347            const p = projectService.inferredProjects[0];
1348            checkProjectActualFiles(p, [file1.path]);
1349
1350            installer.installAll(/*expectedCount*/ 0);
1351
1352            checkNumberOfProjects(projectService, { inferredProjects: 1 });
1353            checkProjectActualFiles(p, [file1.path]);
1354        });
1355    });
1356
1357    describe("unittests:: tsserver:: typingsInstaller:: Validate package name:", () => {
1358        it("name cannot be too long", () => {
1359            let packageName = "a";
1360            for (let i = 0; i < 8; i++) {
1361                packageName += packageName;
1362            }
1363            assert.equal(validatePackageName(packageName), NameValidationResult.NameTooLong);
1364        });
1365        it("package name cannot start with dot", () => {
1366            assert.equal(validatePackageName(".foo"), NameValidationResult.NameStartsWithDot);
1367        });
1368        it("package name cannot start with underscore", () => {
1369            assert.equal(validatePackageName("_foo"), NameValidationResult.NameStartsWithUnderscore);
1370        });
1371        it("package non URI safe characters are not supported", () => {
1372            assert.equal(validatePackageName("  scope  "), NameValidationResult.NameContainsNonURISafeCharacters);
1373            assert.equal(validatePackageName("; say ‘Hello from TypeScript!’ #"), NameValidationResult.NameContainsNonURISafeCharacters);
1374            assert.equal(validatePackageName("a/b/c"), NameValidationResult.NameContainsNonURISafeCharacters);
1375        });
1376        it("scoped package name is supported", () => {
1377            assert.equal(validatePackageName("@scope/bar"), NameValidationResult.Ok);
1378        });
1379        it("scoped name in scoped package name cannot start with dot", () => {
1380            assert.deepEqual(validatePackageName("@.scope/bar"), { name: ".scope", isScopeName: true, result: NameValidationResult.NameStartsWithDot });
1381            assert.deepEqual(validatePackageName("@.scope/.bar"), { name: ".scope", isScopeName: true, result: NameValidationResult.NameStartsWithDot });
1382        });
1383        it("scope name in scoped package name cannot start with underscore", () => {
1384            assert.deepEqual(validatePackageName("@_scope/bar"), { name: "_scope", isScopeName: true, result: NameValidationResult.NameStartsWithUnderscore });
1385            assert.deepEqual(validatePackageName("@_scope/_bar"), { name: "_scope", isScopeName: true, result: NameValidationResult.NameStartsWithUnderscore });
1386        });
1387        it("scope name in scoped package name with non URI safe characters are not supported", () => {
1388            assert.deepEqual(validatePackageName("@  scope  /bar"), { name: "  scope  ", isScopeName: true, result: NameValidationResult.NameContainsNonURISafeCharacters });
1389            assert.deepEqual(validatePackageName("@; say ‘Hello from TypeScript!’ #/bar"), { name: "; say ‘Hello from TypeScript!’ #", isScopeName: true, result: NameValidationResult.NameContainsNonURISafeCharacters });
1390            assert.deepEqual(validatePackageName("@  scope  /  bar  "), { name: "  scope  ", isScopeName: true, result: NameValidationResult.NameContainsNonURISafeCharacters });
1391        });
1392        it("package name in scoped package name cannot start with dot", () => {
1393            assert.deepEqual(validatePackageName("@scope/.bar"), { name: ".bar", isScopeName: false, result: NameValidationResult.NameStartsWithDot });
1394        });
1395        it("package name in scoped package name cannot start with underscore", () => {
1396            assert.deepEqual(validatePackageName("@scope/_bar"), { name: "_bar", isScopeName: false, result: NameValidationResult.NameStartsWithUnderscore });
1397        });
1398        it("package name in scoped package name with non URI safe characters are not supported", () => {
1399            assert.deepEqual(validatePackageName("@scope/  bar  "), { name: "  bar  ", isScopeName: false, result: NameValidationResult.NameContainsNonURISafeCharacters });
1400            assert.deepEqual(validatePackageName("@scope/; say ‘Hello from TypeScript!’ #"), { name: "; say ‘Hello from TypeScript!’ #", isScopeName: false, result: NameValidationResult.NameContainsNonURISafeCharacters });
1401        });
1402    });
1403
1404    describe("unittests:: tsserver:: typingsInstaller:: Invalid package names", () => {
1405        it("should not be installed", () => {
1406            const f1 = {
1407                path: "/a/b/app.js",
1408                content: "let x = 1"
1409            };
1410            const packageJson = {
1411                path: "/a/b/package.json",
1412                content: JSON.stringify({
1413                    dependencies: {
1414                        "; say ‘Hello from TypeScript!’ #": "0.0.x"
1415                    }
1416                })
1417            };
1418            const messages: string[] = [];
1419            const host = createServerHost([f1, packageJson]);
1420            const installer = new (class extends Installer {
1421                constructor() {
1422                    super(host, { globalTypingsCacheLocation: "/tmp" }, { isEnabled: () => true, writeLine: msg => messages.push(msg) });
1423                }
1424                installWorker(_requestId: number, _args: string[], _cwd: string, _cb: TI.RequestCompletedAction) {
1425                    assert(false, "runCommand should not be invoked");
1426                }
1427            })();
1428            const projectService = createProjectService(host, { typingsInstaller: installer });
1429            projectService.openClientFile(f1.path);
1430
1431            installer.checkPendingCommands(/*expectedCount*/ 0);
1432            assert.isTrue(messages.indexOf("'; say ‘Hello from TypeScript!’ #':: Package name '; say ‘Hello from TypeScript!’ #' contains non URI safe characters") > 0, "should find package with invalid name");
1433        });
1434    });
1435
1436    describe("unittests:: tsserver:: typingsInstaller:: discover typings", () => {
1437        const emptySafeList = emptyMap;
1438
1439        it("should use mappings from safe list", () => {
1440            const app = {
1441                path: "/a/b/app.js",
1442                content: ""
1443            };
1444            const jquery = {
1445                path: "/a/b/jquery.js",
1446                content: ""
1447            };
1448            const chroma = {
1449                path: "/a/b/chroma.min.js",
1450                content: ""
1451            };
1452
1453            const safeList = new Map(getEntries({ jquery: "jquery", chroma: "chroma-js" }));
1454
1455            const host = createServerHost([app, jquery, chroma]);
1456            const logger = trackingLogger();
1457            const result = JsTyping.discoverTypings(host, logger.log, [app.path, jquery.path, chroma.path], getDirectoryPath(<Path>app.path), safeList, emptyMap, { enable: true }, emptyArray, emptyMap);
1458            const finish = logger.finish();
1459            assert.deepEqual(finish, [
1460                'Inferred typings from file names: ["jquery","chroma-js"]',
1461                "Inferred typings from unresolved imports: []",
1462                'Result: {"cachedTypingPaths":[],"newTypingNames":["jquery","chroma-js"],"filesToWatch":["/a/b/bower_components","/a/b/node_modules"]}',
1463            ], finish.join("\r\n"));
1464            assert.deepEqual(result.newTypingNames, ["jquery", "chroma-js"]);
1465        });
1466
1467        it("should return node for core modules", () => {
1468            const f = {
1469                path: "/a/b/app.js",
1470                content: ""
1471            };
1472            const host = createServerHost([f]);
1473            const cache = new Map<string, JsTyping.CachedTyping>();
1474
1475            for (const name of JsTyping.nodeCoreModuleList) {
1476                const logger = trackingLogger();
1477                const result = JsTyping.discoverTypings(host, logger.log, [f.path], getDirectoryPath(<Path>f.path), emptySafeList, cache, { enable: true }, [name, "somename"], emptyMap);
1478                assert.deepEqual(logger.finish(), [
1479                    'Inferred typings from unresolved imports: ["node","somename"]',
1480                    'Result: {"cachedTypingPaths":[],"newTypingNames":["node","somename"],"filesToWatch":["/a/b/bower_components","/a/b/node_modules"]}',
1481                ]);
1482                assert.deepEqual(result.newTypingNames.sort(), ["node", "somename"]);
1483            }
1484        });
1485
1486        it("should use cached locations", () => {
1487            const f = {
1488                path: "/a/b/app.js",
1489                content: ""
1490            };
1491            const node = {
1492                path: "/a/b/node.d.ts",
1493                content: ""
1494            };
1495            const host = createServerHost([f, node]);
1496            const cache = new Map(getEntries<JsTyping.CachedTyping>({ node: { typingLocation: node.path, version: new Version("1.3.0") } }));
1497            const registry = createTypesRegistry("node");
1498            const logger = trackingLogger();
1499            const result = JsTyping.discoverTypings(host, logger.log, [f.path], getDirectoryPath(<Path>f.path), emptySafeList, cache, { enable: true }, ["fs", "bar"], registry);
1500            assert.deepEqual(logger.finish(), [
1501                'Inferred typings from unresolved imports: ["node","bar"]',
1502                'Result: {"cachedTypingPaths":["/a/b/node.d.ts"],"newTypingNames":["bar"],"filesToWatch":["/a/b/bower_components","/a/b/node_modules"]}',
1503            ]);
1504            assert.deepEqual(result.cachedTypingPaths, [node.path]);
1505            assert.deepEqual(result.newTypingNames, ["bar"]);
1506        });
1507
1508        it("should gracefully handle packages that have been removed from the types-registry", () => {
1509            const f = {
1510                path: "/a/b/app.js",
1511                content: ""
1512            };
1513            const node = {
1514                path: "/a/b/node.d.ts",
1515                content: ""
1516            };
1517            const host = createServerHost([f, node]);
1518            const cache = new Map(getEntries<JsTyping.CachedTyping>({ node: { typingLocation: node.path, version: new Version("1.3.0") } }));
1519            const logger = trackingLogger();
1520            const result = JsTyping.discoverTypings(host, logger.log, [f.path], getDirectoryPath(<Path>f.path), emptySafeList, cache, { enable: true }, ["fs", "bar"], emptyMap);
1521            assert.deepEqual(logger.finish(), [
1522                'Inferred typings from unresolved imports: ["node","bar"]',
1523                'Result: {"cachedTypingPaths":[],"newTypingNames":["node","bar"],"filesToWatch":["/a/b/bower_components","/a/b/node_modules"]}',
1524            ]);
1525            assert.deepEqual(result.cachedTypingPaths, []);
1526            assert.deepEqual(result.newTypingNames, ["node", "bar"]);
1527        });
1528
1529        it("should search only 2 levels deep", () => {
1530            const app = {
1531                path: "/app.js",
1532                content: "",
1533            };
1534            const a = {
1535                path: "/node_modules/a/package.json",
1536                content: JSON.stringify({ name: "a" }),
1537            };
1538            const b = {
1539                path: "/node_modules/a/b/package.json",
1540                content: JSON.stringify({ name: "b" }),
1541            };
1542            const host = createServerHost([app, a, b]);
1543            const cache = new Map<string, JsTyping.CachedTyping>();
1544            const logger = trackingLogger();
1545            const result = JsTyping.discoverTypings(host, logger.log, [app.path], getDirectoryPath(<Path>app.path), emptySafeList, cache, { enable: true }, /*unresolvedImports*/ [], emptyMap);
1546            assert.deepEqual(logger.finish(), [
1547                'Searching for typing names in /node_modules; all files: ["/node_modules/a/package.json"]',
1548                '    Found package names: ["a"]',
1549                "Inferred typings from unresolved imports: []",
1550                'Result: {"cachedTypingPaths":[],"newTypingNames":["a"],"filesToWatch":["/bower_components","/node_modules"]}',
1551            ]);
1552            assert.deepEqual(result, {
1553                cachedTypingPaths: [],
1554                newTypingNames: ["a"], // But not "b"
1555                filesToWatch: ["/bower_components", "/node_modules"],
1556            });
1557        });
1558
1559        it("should install expired typings", () => {
1560            const app = {
1561                path: "/a/app.js",
1562                content: ""
1563            };
1564            const cachePath = "/a/cache/";
1565            const commander = {
1566                path: cachePath + "node_modules/@types/commander/index.d.ts",
1567                content: "export let x: number"
1568            };
1569            const node = {
1570                path: cachePath + "node_modules/@types/node/index.d.ts",
1571                content: "export let y: number"
1572            };
1573            const host = createServerHost([app]);
1574            const cache = new Map(getEntries<JsTyping.CachedTyping>({
1575                node: { typingLocation: node.path, version: new Version("1.3.0") },
1576                commander: { typingLocation: commander.path, version: new Version("1.0.0") }
1577            }));
1578            const registry = createTypesRegistry("node", "commander");
1579            const logger = trackingLogger();
1580            const result = JsTyping.discoverTypings(host, logger.log, [app.path], getDirectoryPath(<Path>app.path), emptySafeList, cache, { enable: true }, ["http", "commander"], registry);
1581            assert.deepEqual(logger.finish(), [
1582                'Inferred typings from unresolved imports: ["node","commander"]',
1583                'Result: {"cachedTypingPaths":["/a/cache/node_modules/@types/node/index.d.ts"],"newTypingNames":["commander"],"filesToWatch":["/a/bower_components","/a/node_modules"]}',
1584            ]);
1585            assert.deepEqual(result.cachedTypingPaths, [node.path]);
1586            assert.deepEqual(result.newTypingNames, ["commander"]);
1587        });
1588
1589        it("should install expired typings with prerelease version of tsserver", () => {
1590            const app = {
1591                path: "/a/app.js",
1592                content: ""
1593            };
1594            const cachePath = "/a/cache/";
1595            const node = {
1596                path: cachePath + "node_modules/@types/node/index.d.ts",
1597                content: "export let y: number"
1598            };
1599            const host = createServerHost([app]);
1600            const cache = new Map(getEntries<JsTyping.CachedTyping>({
1601                node: { typingLocation: node.path, version: new Version("1.0.0") }
1602            }));
1603            const registry = createTypesRegistry("node");
1604            registry.delete(`ts${versionMajorMinor}`);
1605            const logger = trackingLogger();
1606            const result = JsTyping.discoverTypings(host, logger.log, [app.path], getDirectoryPath(<Path>app.path), emptySafeList, cache, { enable: true }, ["http"], registry);
1607            assert.deepEqual(logger.finish(), [
1608                'Inferred typings from unresolved imports: ["node"]',
1609                'Result: {"cachedTypingPaths":[],"newTypingNames":["node"],"filesToWatch":["/a/bower_components","/a/node_modules"]}',
1610            ]);
1611            assert.deepEqual(result.cachedTypingPaths, []);
1612            assert.deepEqual(result.newTypingNames, ["node"]);
1613        });
1614
1615
1616        it("prerelease typings are properly handled", () => {
1617            const app = {
1618                path: "/a/app.js",
1619                content: ""
1620            };
1621            const cachePath = "/a/cache/";
1622            const commander = {
1623                path: cachePath + "node_modules/@types/commander/index.d.ts",
1624                content: "export let x: number"
1625            };
1626            const node = {
1627                path: cachePath + "node_modules/@types/node/index.d.ts",
1628                content: "export let y: number"
1629            };
1630            const host = createServerHost([app]);
1631            const cache = new Map(getEntries<JsTyping.CachedTyping>({
1632                node: { typingLocation: node.path, version: new Version("1.3.0-next.0") },
1633                commander: { typingLocation: commander.path, version: new Version("1.3.0-next.0") }
1634            }));
1635            const registry = createTypesRegistry("node", "commander");
1636            registry.get("node")![`ts${versionMajorMinor}`] = "1.3.0-next.1";
1637            const logger = trackingLogger();
1638            const result = JsTyping.discoverTypings(host, logger.log, [app.path], getDirectoryPath(<Path>app.path), emptySafeList, cache, { enable: true }, ["http", "commander"], registry);
1639            assert.deepEqual(logger.finish(), [
1640                'Inferred typings from unresolved imports: ["node","commander"]',
1641                'Result: {"cachedTypingPaths":[],"newTypingNames":["node","commander"],"filesToWatch":["/a/bower_components","/a/node_modules"]}',
1642            ]);
1643            assert.deepEqual(result.cachedTypingPaths, []);
1644            assert.deepEqual(result.newTypingNames, ["node", "commander"]);
1645        });
1646    });
1647
1648    describe("unittests:: tsserver:: typingsInstaller:: telemetry events", () => {
1649        it("should be received", () => {
1650            const f1 = {
1651                path: "/a/app.js",
1652                content: ""
1653            };
1654            const packageFile = {
1655                path: "/a/package.json",
1656                content: JSON.stringify({ dependencies: { commander: "1.0.0" } })
1657            };
1658            const cachePath = "/a/cache/";
1659            const commander = {
1660                path: cachePath + "node_modules/@types/commander/index.d.ts",
1661                content: "export let x: number"
1662            };
1663            const host = createServerHost([f1, packageFile]);
1664            let seenTelemetryEvent = false;
1665            const installer = new (class extends Installer {
1666                constructor() {
1667                    super(host, { globalTypingsCacheLocation: cachePath, typesRegistry: createTypesRegistry("commander") });
1668                }
1669                installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) {
1670                    const installedTypings = ["@types/commander"];
1671                    const typingFiles = [commander];
1672                    executeCommand(this, host, installedTypings, typingFiles, cb);
1673                }
1674                sendResponse(response: server.SetTypings | server.InvalidateCachedTypings | server.BeginInstallTypes | server.EndInstallTypes) {
1675                    if (response.kind === server.EventBeginInstallTypes) {
1676                        return;
1677                    }
1678                    if (response.kind === server.EventEndInstallTypes) {
1679                        assert.deepEqual(response.packagesToInstall, [typingsName("commander")]);
1680                        seenTelemetryEvent = true;
1681                        return;
1682                    }
1683                    super.sendResponse(response);
1684                }
1685            })();
1686            const projectService = createProjectService(host, { typingsInstaller: installer });
1687            projectService.openClientFile(f1.path);
1688
1689            installer.installAll(/*expectedCount*/ 1);
1690
1691            assert.isTrue(seenTelemetryEvent);
1692            host.checkTimeoutQueueLengthAndRun(2);
1693            checkNumberOfProjects(projectService, { inferredProjects: 1 });
1694            checkProjectActualFiles(projectService.inferredProjects[0], [f1.path, commander.path]);
1695        });
1696    });
1697
1698    describe("unittests:: tsserver:: typingsInstaller:: progress notifications", () => {
1699        it("should be sent for success", () => {
1700            const f1 = {
1701                path: "/a/app.js",
1702                content: ""
1703            };
1704            const packageFile = {
1705                path: "/a/package.json",
1706                content: JSON.stringify({ dependencies: { commander: "1.0.0" } })
1707            };
1708            const packageLockFile = {
1709                path: "/a/cache/package-lock.json",
1710                content: JSON.stringify({
1711                    dependencies: {
1712                        "@types/commander": {
1713                            version: "1.0.0"
1714                        }
1715                    }
1716                })
1717            };
1718            const cachePath = "/a/cache/";
1719            const commander = {
1720                path: cachePath + "node_modules/@types/commander/index.d.ts",
1721                content: "export let x: number"
1722            };
1723            const host = createServerHost([f1, packageFile, packageLockFile]);
1724            let beginEvent!: server.BeginInstallTypes;
1725            let endEvent!: server.EndInstallTypes;
1726            const installer = new (class extends Installer {
1727                constructor() {
1728                    super(host, { globalTypingsCacheLocation: cachePath, typesRegistry: createTypesRegistry("commander") });
1729                }
1730                installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) {
1731                    const installedTypings = ["@types/commander"];
1732                    const typingFiles = [commander];
1733                    executeCommand(this, host, installedTypings, typingFiles, cb);
1734                }
1735                sendResponse(response: server.SetTypings | server.InvalidateCachedTypings | server.BeginInstallTypes | server.EndInstallTypes) {
1736                    if (response.kind === server.EventBeginInstallTypes) {
1737                        beginEvent = response;
1738                        return;
1739                    }
1740                    if (response.kind === server.EventEndInstallTypes) {
1741                        endEvent = response;
1742                        return;
1743                    }
1744                    super.sendResponse(response);
1745                }
1746            })();
1747            const projectService = createProjectService(host, { typingsInstaller: installer });
1748            projectService.openClientFile(f1.path);
1749
1750            installer.installAll(/*expectedCount*/ 1);
1751
1752            assert.isTrue(!!beginEvent);
1753            assert.isTrue(!!endEvent);
1754            assert.isTrue(beginEvent.eventId === endEvent.eventId);
1755            assert.isTrue(endEvent.installSuccess);
1756            host.checkTimeoutQueueLengthAndRun(2);
1757            checkNumberOfProjects(projectService, { inferredProjects: 1 });
1758            checkProjectActualFiles(projectService.inferredProjects[0], [f1.path, commander.path]);
1759        });
1760
1761        it("should be sent for error", () => {
1762            const f1 = {
1763                path: "/a/app.js",
1764                content: ""
1765            };
1766            const packageFile = {
1767                path: "/a/package.json",
1768                content: JSON.stringify({ dependencies: { commander: "1.0.0" } })
1769            };
1770            const cachePath = "/a/cache/";
1771            const host = createServerHost([f1, packageFile]);
1772            let beginEvent: server.BeginInstallTypes | undefined;
1773            let endEvent: server.EndInstallTypes | undefined;
1774            const installer: Installer = new (class extends Installer {
1775                constructor() {
1776                    super(host, { globalTypingsCacheLocation: cachePath, typesRegistry: createTypesRegistry("commander") });
1777                }
1778                installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) {
1779                    executeCommand(this, host, "", [], cb);
1780                }
1781                sendResponse(response: server.SetTypings | server.InvalidateCachedTypings | server.BeginInstallTypes | server.EndInstallTypes) {
1782                    if (response.kind === server.EventBeginInstallTypes) {
1783                        beginEvent = response;
1784                        return;
1785                    }
1786                    if (response.kind === server.EventEndInstallTypes) {
1787                        endEvent = response;
1788                        return;
1789                    }
1790                    super.sendResponse(response);
1791                }
1792            })();
1793            const projectService = createProjectService(host, { typingsInstaller: installer });
1794            projectService.openClientFile(f1.path);
1795
1796            installer.installAll(/*expectedCount*/ 1);
1797
1798            assert.isTrue(!!beginEvent);
1799            assert.isTrue(!!endEvent);
1800            assert.isTrue(beginEvent!.eventId === endEvent!.eventId);
1801            assert.isFalse(endEvent!.installSuccess);
1802            checkNumberOfProjects(projectService, { inferredProjects: 1 });
1803            checkProjectActualFiles(projectService.inferredProjects[0], [f1.path]);
1804        });
1805    });
1806
1807    describe("unittests:: tsserver:: typingsInstaller:: npm installation command", () => {
1808        const npmPath = "npm", tsVersion = "2.9.0-dev.20180410";
1809        const packageNames = ["@types/graphql@ts2.8", "@types/highlight.js@ts2.8", "@types/jest@ts2.8", "@types/mini-css-extract-plugin@ts2.8", "@types/mongoose@ts2.8", "@types/pg@ts2.8", "@types/webpack-bundle-analyzer@ts2.8", "@types/enhanced-resolve@ts2.8", "@types/eslint-plugin-prettier@ts2.8", "@types/friendly-errors-webpack-plugin@ts2.8", "@types/hammerjs@ts2.8", "@types/history@ts2.8", "@types/image-size@ts2.8", "@types/js-cookie@ts2.8", "@types/koa-compress@ts2.8", "@types/less@ts2.8", "@types/material-ui@ts2.8", "@types/mysql@ts2.8", "@types/nodemailer@ts2.8", "@types/prettier@ts2.8", "@types/query-string@ts2.8", "@types/react-places-autocomplete@ts2.8", "@types/react-router@ts2.8", "@types/react-router-config@ts2.8", "@types/react-select@ts2.8", "@types/react-transition-group@ts2.8", "@types/redux-form@ts2.8", "@types/abbrev@ts2.8", "@types/accepts@ts2.8", "@types/acorn@ts2.8", "@types/ansi-regex@ts2.8", "@types/ansi-styles@ts2.8", "@types/anymatch@ts2.8", "@types/apollo-codegen@ts2.8", "@types/are-we-there-yet@ts2.8", "@types/argparse@ts2.8", "@types/arr-union@ts2.8", "@types/array-find-index@ts2.8", "@types/array-uniq@ts2.8", "@types/array-unique@ts2.8", "@types/arrify@ts2.8", "@types/assert-plus@ts2.8", "@types/async@ts2.8", "@types/autoprefixer@ts2.8", "@types/aws4@ts2.8", "@types/babel-code-frame@ts2.8", "@types/babel-generator@ts2.8", "@types/babel-plugin-syntax-jsx@ts2.8", "@types/babel-template@ts2.8", "@types/babel-traverse@ts2.8", "@types/babel-types@ts2.8", "@types/babylon@ts2.8", "@types/base64-js@ts2.8", "@types/basic-auth@ts2.8", "@types/big.js@ts2.8", "@types/bl@ts2.8", "@types/bluebird@ts2.8", "@types/body-parser@ts2.8", "@types/bonjour@ts2.8", "@types/boom@ts2.8", "@types/brace-expansion@ts2.8", "@types/braces@ts2.8", "@types/brorand@ts2.8", "@types/browser-resolve@ts2.8", "@types/bson@ts2.8", "@types/buffer-equal@ts2.8", "@types/builtin-modules@ts2.8", "@types/bytes@ts2.8", "@types/callsites@ts2.8", "@types/camelcase@ts2.8", "@types/camelcase-keys@ts2.8", "@types/caseless@ts2.8", "@types/change-emitter@ts2.8", "@types/check-types@ts2.8", "@types/cheerio@ts2.8", "@types/chokidar@ts2.8", "@types/chownr@ts2.8", "@types/circular-json@ts2.8", "@types/classnames@ts2.8", "@types/clean-css@ts2.8", "@types/clone@ts2.8", "@types/co-body@ts2.8", "@types/color@ts2.8", "@types/color-convert@ts2.8", "@types/color-name@ts2.8", "@types/color-string@ts2.8", "@types/colors@ts2.8", "@types/combined-stream@ts2.8", "@types/common-tags@ts2.8", "@types/component-emitter@ts2.8", "@types/compressible@ts2.8", "@types/compression@ts2.8", "@types/concat-stream@ts2.8", "@types/connect-history-api-fallback@ts2.8", "@types/content-disposition@ts2.8", "@types/content-type@ts2.8", "@types/convert-source-map@ts2.8", "@types/cookie@ts2.8", "@types/cookie-signature@ts2.8", "@types/cookies@ts2.8", "@types/core-js@ts2.8", "@types/cosmiconfig@ts2.8", "@types/create-react-class@ts2.8", "@types/cross-spawn@ts2.8", "@types/cryptiles@ts2.8", "@types/css-modules-require-hook@ts2.8", "@types/dargs@ts2.8", "@types/dateformat@ts2.8", "@types/debug@ts2.8", "@types/decamelize@ts2.8", "@types/decompress@ts2.8", "@types/decompress-response@ts2.8", "@types/deep-equal@ts2.8", "@types/deep-extend@ts2.8", "@types/deepmerge@ts2.8", "@types/defined@ts2.8", "@types/del@ts2.8", "@types/depd@ts2.8", "@types/destroy@ts2.8", "@types/detect-indent@ts2.8", "@types/detect-newline@ts2.8", "@types/diff@ts2.8", "@types/doctrine@ts2.8", "@types/download@ts2.8", "@types/draft-js@ts2.8", "@types/duplexer2@ts2.8", "@types/duplexer3@ts2.8", "@types/duplexify@ts2.8", "@types/ejs@ts2.8", "@types/end-of-stream@ts2.8", "@types/entities@ts2.8", "@types/escape-html@ts2.8", "@types/escape-string-regexp@ts2.8", "@types/escodegen@ts2.8", "@types/eslint-scope@ts2.8", "@types/eslint-visitor-keys@ts2.8", "@types/esprima@ts2.8", "@types/estraverse@ts2.8", "@types/etag@ts2.8", "@types/events@ts2.8", "@types/execa@ts2.8", "@types/exenv@ts2.8", "@types/exit@ts2.8", "@types/exit-hook@ts2.8", "@types/expect@ts2.8", "@types/express@ts2.8", "@types/express-graphql@ts2.8", "@types/extend@ts2.8", "@types/extract-zip@ts2.8", "@types/fancy-log@ts2.8", "@types/fast-diff@ts2.8", "@types/fast-levenshtein@ts2.8", "@types/figures@ts2.8", "@types/file-type@ts2.8", "@types/filenamify@ts2.8", "@types/filesize@ts2.8", "@types/finalhandler@ts2.8", "@types/find-root@ts2.8", "@types/find-up@ts2.8", "@types/findup-sync@ts2.8", "@types/forever-agent@ts2.8", "@types/form-data@ts2.8", "@types/forwarded@ts2.8", "@types/fresh@ts2.8", "@types/from2@ts2.8", "@types/fs-extra@ts2.8", "@types/get-caller-file@ts2.8", "@types/get-stdin@ts2.8", "@types/get-stream@ts2.8", "@types/get-value@ts2.8", "@types/glob-base@ts2.8", "@types/glob-parent@ts2.8", "@types/glob-stream@ts2.8", "@types/globby@ts2.8", "@types/globule@ts2.8", "@types/got@ts2.8", "@types/graceful-fs@ts2.8", "@types/gulp-rename@ts2.8", "@types/gulp-sourcemaps@ts2.8", "@types/gulp-util@ts2.8", "@types/gzip-size@ts2.8", "@types/handlebars@ts2.8", "@types/has-ansi@ts2.8", "@types/hasha@ts2.8", "@types/he@ts2.8", "@types/hoek@ts2.8", "@types/html-entities@ts2.8", "@types/html-minifier@ts2.8", "@types/htmlparser2@ts2.8", "@types/http-assert@ts2.8", "@types/http-errors@ts2.8", "@types/http-proxy@ts2.8", "@types/http-proxy-middleware@ts2.8", "@types/indent-string@ts2.8", "@types/inflected@ts2.8", "@types/inherits@ts2.8", "@types/ini@ts2.8", "@types/inline-style-prefixer@ts2.8", "@types/inquirer@ts2.8", "@types/internal-ip@ts2.8", "@types/into-stream@ts2.8", "@types/invariant@ts2.8", "@types/ip@ts2.8", "@types/ip-regex@ts2.8", "@types/is-absolute-url@ts2.8", "@types/is-binary-path@ts2.8", "@types/is-finite@ts2.8", "@types/is-glob@ts2.8", "@types/is-my-json-valid@ts2.8", "@types/is-number@ts2.8", "@types/is-object@ts2.8", "@types/is-path-cwd@ts2.8", "@types/is-path-in-cwd@ts2.8", "@types/is-promise@ts2.8", "@types/is-scoped@ts2.8", "@types/is-stream@ts2.8", "@types/is-svg@ts2.8", "@types/is-url@ts2.8", "@types/is-windows@ts2.8", "@types/istanbul-lib-coverage@ts2.8", "@types/istanbul-lib-hook@ts2.8", "@types/istanbul-lib-instrument@ts2.8", "@types/istanbul-lib-report@ts2.8", "@types/istanbul-lib-source-maps@ts2.8", "@types/istanbul-reports@ts2.8", "@types/jest-diff@ts2.8", "@types/jest-docblock@ts2.8", "@types/jest-get-type@ts2.8", "@types/jest-matcher-utils@ts2.8", "@types/jest-validate@ts2.8", "@types/jpeg-js@ts2.8", "@types/js-base64@ts2.8", "@types/js-string-escape@ts2.8", "@types/js-yaml@ts2.8", "@types/jsbn@ts2.8", "@types/jsdom@ts2.8", "@types/jsesc@ts2.8", "@types/json-parse-better-errors@ts2.8", "@types/json-schema@ts2.8", "@types/json-stable-stringify@ts2.8", "@types/json-stringify-safe@ts2.8", "@types/json5@ts2.8", "@types/jsonfile@ts2.8", "@types/jsontoxml@ts2.8", "@types/jss@ts2.8", "@types/keygrip@ts2.8", "@types/keymirror@ts2.8", "@types/keyv@ts2.8", "@types/klaw@ts2.8", "@types/koa-send@ts2.8", "@types/leven@ts2.8", "@types/listr@ts2.8", "@types/load-json-file@ts2.8", "@types/loader-runner@ts2.8", "@types/loader-utils@ts2.8", "@types/locate-path@ts2.8", "@types/lodash-es@ts2.8", "@types/lodash.assign@ts2.8", "@types/lodash.camelcase@ts2.8", "@types/lodash.clonedeep@ts2.8", "@types/lodash.debounce@ts2.8", "@types/lodash.escape@ts2.8", "@types/lodash.flowright@ts2.8", "@types/lodash.get@ts2.8", "@types/lodash.isarguments@ts2.8", "@types/lodash.isarray@ts2.8", "@types/lodash.isequal@ts2.8", "@types/lodash.isobject@ts2.8", "@types/lodash.isstring@ts2.8", "@types/lodash.keys@ts2.8", "@types/lodash.memoize@ts2.8", "@types/lodash.merge@ts2.8", "@types/lodash.mergewith@ts2.8", "@types/lodash.pick@ts2.8", "@types/lodash.sortby@ts2.8", "@types/lodash.tail@ts2.8", "@types/lodash.template@ts2.8", "@types/lodash.throttle@ts2.8", "@types/lodash.unescape@ts2.8", "@types/lodash.uniq@ts2.8", "@types/log-symbols@ts2.8", "@types/log-update@ts2.8", "@types/loglevel@ts2.8", "@types/loud-rejection@ts2.8", "@types/lru-cache@ts2.8", "@types/make-dir@ts2.8", "@types/map-obj@ts2.8", "@types/media-typer@ts2.8", "@types/mem@ts2.8", "@types/mem-fs@ts2.8", "@types/memory-fs@ts2.8", "@types/meow@ts2.8", "@types/merge-descriptors@ts2.8", "@types/merge-stream@ts2.8", "@types/methods@ts2.8", "@types/micromatch@ts2.8", "@types/mime@ts2.8", "@types/mime-db@ts2.8", "@types/mime-types@ts2.8", "@types/minimatch@ts2.8", "@types/minimist@ts2.8", "@types/minipass@ts2.8", "@types/mkdirp@ts2.8", "@types/mongodb@ts2.8", "@types/morgan@ts2.8", "@types/move-concurrently@ts2.8", "@types/ms@ts2.8", "@types/msgpack-lite@ts2.8", "@types/multimatch@ts2.8", "@types/mz@ts2.8", "@types/negotiator@ts2.8", "@types/node-dir@ts2.8", "@types/node-fetch@ts2.8", "@types/node-forge@ts2.8", "@types/node-int64@ts2.8", "@types/node-ipc@ts2.8", "@types/node-notifier@ts2.8", "@types/nomnom@ts2.8", "@types/nopt@ts2.8", "@types/normalize-package-data@ts2.8", "@types/normalize-url@ts2.8", "@types/number-is-nan@ts2.8", "@types/object-assign@ts2.8", "@types/on-finished@ts2.8", "@types/on-headers@ts2.8", "@types/once@ts2.8", "@types/onetime@ts2.8", "@types/opener@ts2.8", "@types/opn@ts2.8", "@types/optimist@ts2.8", "@types/ora@ts2.8", "@types/os-homedir@ts2.8", "@types/os-locale@ts2.8", "@types/os-tmpdir@ts2.8", "@types/p-cancelable@ts2.8", "@types/p-each-series@ts2.8", "@types/p-event@ts2.8", "@types/p-lazy@ts2.8", "@types/p-limit@ts2.8", "@types/p-locate@ts2.8", "@types/p-map@ts2.8", "@types/p-map-series@ts2.8", "@types/p-reduce@ts2.8", "@types/p-timeout@ts2.8", "@types/p-try@ts2.8", "@types/pako@ts2.8", "@types/parse-glob@ts2.8", "@types/parse-json@ts2.8", "@types/parseurl@ts2.8", "@types/path-exists@ts2.8", "@types/path-is-absolute@ts2.8", "@types/path-parse@ts2.8", "@types/pg-pool@ts2.8", "@types/pg-types@ts2.8", "@types/pify@ts2.8", "@types/pixelmatch@ts2.8", "@types/pkg-dir@ts2.8", "@types/pluralize@ts2.8", "@types/pngjs@ts2.8", "@types/prelude-ls@ts2.8", "@types/pretty-bytes@ts2.8", "@types/pretty-format@ts2.8", "@types/progress@ts2.8", "@types/promise-retry@ts2.8", "@types/proxy-addr@ts2.8", "@types/pump@ts2.8", "@types/q@ts2.8", "@types/qs@ts2.8", "@types/range-parser@ts2.8", "@types/rc@ts2.8", "@types/rc-select@ts2.8", "@types/rc-slider@ts2.8", "@types/rc-tooltip@ts2.8", "@types/rc-tree@ts2.8", "@types/react-event-listener@ts2.8", "@types/react-side-effect@ts2.8", "@types/react-slick@ts2.8", "@types/read-chunk@ts2.8", "@types/read-pkg@ts2.8", "@types/read-pkg-up@ts2.8", "@types/recompose@ts2.8", "@types/recursive-readdir@ts2.8", "@types/relateurl@ts2.8", "@types/replace-ext@ts2.8", "@types/request@ts2.8", "@types/request-promise-native@ts2.8", "@types/require-directory@ts2.8", "@types/require-from-string@ts2.8", "@types/require-relative@ts2.8", "@types/resolve@ts2.8", "@types/resolve-from@ts2.8", "@types/retry@ts2.8", "@types/rx@ts2.8", "@types/rx-lite@ts2.8", "@types/rx-lite-aggregates@ts2.8", "@types/safe-regex@ts2.8", "@types/sane@ts2.8", "@types/sass-graph@ts2.8", "@types/sax@ts2.8", "@types/scriptjs@ts2.8", "@types/semver@ts2.8", "@types/send@ts2.8", "@types/serialize-javascript@ts2.8", "@types/serve-index@ts2.8", "@types/serve-static@ts2.8", "@types/set-value@ts2.8", "@types/shallowequal@ts2.8", "@types/shelljs@ts2.8", "@types/sockjs@ts2.8", "@types/sockjs-client@ts2.8", "@types/source-list-map@ts2.8", "@types/source-map-support@ts2.8", "@types/spdx-correct@ts2.8", "@types/spdy@ts2.8", "@types/split@ts2.8", "@types/sprintf@ts2.8", "@types/sprintf-js@ts2.8", "@types/sqlstring@ts2.8", "@types/sshpk@ts2.8", "@types/stack-utils@ts2.8", "@types/stat-mode@ts2.8", "@types/statuses@ts2.8", "@types/strict-uri-encode@ts2.8", "@types/string-template@ts2.8", "@types/strip-ansi@ts2.8", "@types/strip-bom@ts2.8", "@types/strip-json-comments@ts2.8", "@types/supports-color@ts2.8", "@types/svg2png@ts2.8", "@types/svgo@ts2.8", "@types/table@ts2.8", "@types/tapable@ts2.8", "@types/tar@ts2.8", "@types/temp@ts2.8", "@types/tempfile@ts2.8", "@types/through@ts2.8", "@types/through2@ts2.8", "@types/tinycolor2@ts2.8", "@types/tmp@ts2.8", "@types/to-absolute-glob@ts2.8", "@types/tough-cookie@ts2.8", "@types/trim@ts2.8", "@types/tryer@ts2.8", "@types/type-check@ts2.8", "@types/type-is@ts2.8", "@types/ua-parser-js@ts2.8", "@types/uglify-js@ts2.8", "@types/uglifyjs-webpack-plugin@ts2.8", "@types/underscore@ts2.8", "@types/uniq@ts2.8", "@types/uniqid@ts2.8", "@types/untildify@ts2.8", "@types/urijs@ts2.8", "@types/url-join@ts2.8", "@types/url-parse@ts2.8", "@types/url-regex@ts2.8", "@types/user-home@ts2.8", "@types/util-deprecate@ts2.8", "@types/util.promisify@ts2.8", "@types/utils-merge@ts2.8", "@types/uuid@ts2.8", "@types/vali-date@ts2.8", "@types/vary@ts2.8", "@types/verror@ts2.8", "@types/vinyl@ts2.8", "@types/vinyl-fs@ts2.8", "@types/warning@ts2.8", "@types/watch@ts2.8", "@types/watchpack@ts2.8", "@types/webpack-dev-middleware@ts2.8", "@types/webpack-sources@ts2.8", "@types/which@ts2.8", "@types/window-size@ts2.8", "@types/wrap-ansi@ts2.8", "@types/write-file-atomic@ts2.8", "@types/ws@ts2.8", "@types/xml2js@ts2.8", "@types/xmlbuilder@ts2.8", "@types/xtend@ts2.8", "@types/yallist@ts2.8", "@types/yargs@ts2.8", "@types/yauzl@ts2.8", "@types/yeoman-generator@ts2.8", "@types/zen-observable@ts2.8", "@types/react-content-loader@ts2.8"];
1810        const expectedCommands = [
1811            TI.getNpmCommandForInstallation(npmPath, tsVersion, packageNames, packageNames.length).command,
1812            TI.getNpmCommandForInstallation(npmPath, tsVersion, packageNames, packageNames.length - Math.ceil(packageNames.length / 2)).command
1813        ];
1814        it("works when the command is too long to install all packages at once", () => {
1815            const commands: string[] = [];
1816            const hasError = TI.installNpmPackages(npmPath, tsVersion, packageNames, command => {
1817                commands.push(command);
1818                return false;
1819            });
1820            assert.isFalse(hasError);
1821            assert.deepEqual(commands, expectedCommands, "commands");
1822        });
1823
1824        it("installs remaining packages when one of the partial command fails", () => {
1825            const commands: string[] = [];
1826            const hasError = TI.installNpmPackages(npmPath, tsVersion, packageNames, command => {
1827                commands.push(command);
1828                return commands.length === 1;
1829            });
1830            assert.isTrue(hasError);
1831            assert.deepEqual(commands, expectedCommands, "commands");
1832        });
1833    });
1834
1835    describe("unittests:: tsserver:: typingsInstaller:: recomputing resolutions of unresolved imports", () => {
1836        const globalTypingsCacheLocation = "/tmp";
1837        const appPath = "/a/b/app.js" as Path;
1838        const foooPath = "/a/b/node_modules/fooo/index.d.ts";
1839        function verifyResolvedModuleOfFooo(project: server.Project) {
1840            server.updateProjectIfDirty(project);
1841            const foooResolution = project.getLanguageService().getProgram()!.getSourceFileByPath(appPath)!.resolvedModules!.get("fooo")!;
1842            assert.equal(foooResolution.resolvedFileName, foooPath);
1843            return foooResolution;
1844        }
1845
1846        function verifyUnresolvedImportResolutions(appContents: string, typingNames: string[], typingFiles: File[]) {
1847            const app: File = {
1848                path: appPath,
1849                content: `${appContents}import * as x from "fooo";`
1850            };
1851            const fooo: File = {
1852                path: foooPath,
1853                content: `export var x: string;`
1854            };
1855
1856            const host = createServerHost([app, fooo]);
1857            const installer = new (class extends Installer {
1858                constructor() {
1859                    super(host, { globalTypingsCacheLocation, typesRegistry: createTypesRegistry("foo") });
1860                }
1861                installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) {
1862                    executeCommand(this, host, typingNames, typingFiles, cb);
1863                }
1864            })();
1865            const projectService = createProjectService(host, { typingsInstaller: installer });
1866            projectService.openClientFile(app.path);
1867            projectService.checkNumberOfProjects({ inferredProjects: 1 });
1868
1869            const proj = projectService.inferredProjects[0];
1870            checkProjectActualFiles(proj, [app.path, fooo.path]);
1871            const foooResolution1 = verifyResolvedModuleOfFooo(proj);
1872
1873            installer.installAll(/*expectedCount*/ 1);
1874            host.checkTimeoutQueueLengthAndRun(2);
1875            checkProjectActualFiles(proj, typingFiles.map(f => f.path).concat(app.path, fooo.path));
1876            const foooResolution2 = verifyResolvedModuleOfFooo(proj);
1877            assert.strictEqual(foooResolution1, foooResolution2);
1878            projectService.applyChangesInOpenFiles(/*openFiles*/ undefined, arrayIterator([{
1879                fileName: app.path,
1880                changes: arrayIterator([{
1881                    span: { start: 0, length: 0 },
1882                    newText: `import * as bar from "bar";`
1883                }])
1884            }]));
1885            host.runQueuedTimeoutCallbacks(); // Update the graph
1886            // Update the typing
1887            host.checkTimeoutQueueLength(0);
1888            assert.isFalse(proj.resolutionCache.isFileWithInvalidatedNonRelativeUnresolvedImports(app.path as Path));
1889        }
1890
1891        it("correctly invalidate the resolutions with typing names", () => {
1892            verifyUnresolvedImportResolutions('import * as a from "foo";', ["foo"], [{
1893                path: `${globalTypingsCacheLocation}/node_modules/foo/index.d.ts`,
1894                content: "export function a(): void;"
1895            }]);
1896        });
1897
1898        it("correctly invalidate the resolutions with typing names that are trimmed", () => {
1899            const fooIndex: File = {
1900                path: `${globalTypingsCacheLocation}/node_modules/foo/index.d.ts`,
1901                content: "export function aa(): void;"
1902            };
1903            const fooAA: File = {
1904                path: `${globalTypingsCacheLocation}/node_modules/foo/a/a.d.ts`,
1905                content: "export function a (): void;"
1906            };
1907            const fooAB: File = {
1908                path: `${globalTypingsCacheLocation}/node_modules/foo/a/b.d.ts`,
1909                content: "export function b (): void;"
1910            };
1911            const fooAC: File = {
1912                path: `${globalTypingsCacheLocation}/node_modules/foo/a/c.d.ts`,
1913                content: "export function c (): void;"
1914            };
1915            verifyUnresolvedImportResolutions(`
1916                    import * as a from "foo/a/a";
1917                    import * as b from "foo/a/b";
1918                    import * as c from "foo/a/c";
1919            `, ["foo"], [fooIndex, fooAA, fooAB, fooAC]);
1920        });
1921
1922        it("should handle node core modules", () => {
1923            const file: TestFSWithWatch.File = {
1924                path: "/a/b/app.js",
1925                content: `// @ts-check
1926
1927const net = require("net");
1928const stream = require("stream");`
1929            };
1930            const nodeTyping: TestFSWithWatch.File = {
1931                path: `${globalTypingsCacheLocation}/node_modules/node/index.d.ts`,
1932                content: `
1933declare module "net" {
1934    export type n = number;
1935}
1936declare module "stream" {
1937    export type s = string;
1938}`,
1939            };
1940
1941            const host = createServerHost([file, libFile]);
1942            const installer = new (class extends Installer {
1943                constructor() {
1944                    super(host, { globalTypingsCacheLocation, typesRegistry: createTypesRegistry("node") });
1945                }
1946                installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) {
1947                    executeCommand(this, host, ["node"], [nodeTyping], cb);
1948                }
1949            })();
1950            const projectService = createProjectService(host, { typingsInstaller: installer });
1951            projectService.openClientFile(file.path);
1952            projectService.checkNumberOfProjects({ inferredProjects: 1 });
1953
1954            const proj = projectService.inferredProjects[0];
1955            checkProjectActualFiles(proj, [file.path, libFile.path]);
1956            installer.installAll(/*expectedCount*/ 1);
1957            host.checkTimeoutQueueLengthAndRun(2);
1958            checkProjectActualFiles(proj, [file.path, libFile.path, nodeTyping.path]);
1959            projectService.applyChangesInOpenFiles(
1960                /*openFiles*/ undefined,
1961                arrayIterator([{
1962                    fileName: file.path,
1963                    changes: arrayIterator([{
1964                        span: {
1965                            start: file.content.indexOf(`"stream"`) + 2,
1966                            length: 0
1967                        },
1968                        newText: " "
1969                    }])
1970                }]),
1971                /*closedFiles*/ undefined
1972            );
1973            // Below timeout Updates the typings to empty array because of "s tream" as unsresolved import
1974            // and schedules the update graph because of this.
1975            host.checkTimeoutQueueLengthAndRun(2);
1976            checkProjectActualFiles(proj, [file.path, libFile.path, nodeTyping.path]);
1977
1978            // Here, since typings dont change, there is no timeout scheduled
1979            host.checkTimeoutQueueLength(0);
1980            checkProjectActualFiles(proj, [file.path, libFile.path, nodeTyping.path]);
1981            projectService.applyChangesInOpenFiles(/*openFiles*/ undefined, arrayIterator([{
1982                fileName: file.path,
1983                changes: arrayIterator([{
1984                    span: { start: file.content.indexOf("const"), length: 0 },
1985                    newText: `const bar = require("bar");`
1986                }])
1987            }]));
1988            proj.updateGraph(); // Update the graph
1989            checkProjectActualFiles(proj, [file.path, libFile.path, nodeTyping.path]);
1990            // Update the typing
1991            host.checkTimeoutQueueLength(0);
1992            assert.isFalse(proj.resolutionCache.isFileWithInvalidatedNonRelativeUnresolvedImports(file.path as Path));
1993        });
1994    });
1995
1996    describe("unittests:: tsserver:: typingsInstaller:: tsserver:: with inferred Project", () => {
1997        it("when projectRootPath is provided", () => {
1998            const projects = "/users/username/projects";
1999            const projectRootPath = `${projects}/san2`;
2000            const file: File = {
2001                path: `${projectRootPath}/x.js`,
2002                content: "const aaaaaaav = 1;"
2003            };
2004
2005            const currentDirectory = `${projects}/anotherProject`;
2006            const packageJsonInCurrentDirectory: File = {
2007                path: `${currentDirectory}/package.json`,
2008                content: JSON.stringify({
2009                    devDependencies: {
2010                        pkgcurrentdirectory: ""
2011                    },
2012                })
2013            };
2014            const packageJsonOfPkgcurrentdirectory: File = {
2015                path: `${currentDirectory}/node_modules/pkgcurrentdirectory/package.json`,
2016                content: JSON.stringify({
2017                    name: "pkgcurrentdirectory",
2018                    main: "index.js",
2019                    typings: "index.d.ts"
2020                })
2021            };
2022            const indexOfPkgcurrentdirectory: File = {
2023                path: `${currentDirectory}/node_modules/pkgcurrentdirectory/index.d.ts`,
2024                content: "export function foo() { }"
2025            };
2026
2027            const typingsCache = `/users/username/Library/Caches/typescript/2.7`;
2028            const typingsCachePackageJson: File = {
2029                path: `${typingsCache}/package.json`,
2030                content: JSON.stringify({
2031                    devDependencies: {
2032                    },
2033                })
2034            };
2035            const typingsCachePackageLockJson: File = {
2036                path: `${typingsCache}/package-lock.json`,
2037                content: JSON.stringify({
2038                    dependencies: {
2039                    },
2040                })
2041            };
2042
2043            const files = [file, packageJsonInCurrentDirectory, packageJsonOfPkgcurrentdirectory, indexOfPkgcurrentdirectory, typingsCachePackageJson, typingsCachePackageLockJson];
2044            const host = createServerHost(files, { currentDirectory });
2045
2046            const typesRegistry = createTypesRegistry("pkgcurrentdirectory");
2047            const typingsInstaller = new TestTypingsInstaller(typingsCache, /*throttleLimit*/ 5, host, typesRegistry);
2048
2049            const projectService = createProjectService(host, { typingsInstaller });
2050
2051            projectService.setCompilerOptionsForInferredProjects({
2052                module: ModuleKind.CommonJS,
2053                target: ScriptTarget.ES2016,
2054                jsx: JsxEmit.Preserve,
2055                experimentalDecorators: true,
2056                allowJs: true,
2057                allowSyntheticDefaultImports: true,
2058                allowNonTsExtensions: true
2059            });
2060
2061            projectService.openClientFile(file.path, file.content, ScriptKind.JS, projectRootPath);
2062
2063            const project = projectService.inferredProjects[0];
2064            assert.isDefined(project);
2065
2066            // Ensure that we use result from types cache when getting ls
2067            assert.isDefined(project.getLanguageService());
2068
2069            // Verify that the pkgcurrentdirectory from the current directory isnt picked up
2070            checkProjectActualFiles(project, [file.path]);
2071        });
2072    });
2073}
2074