• 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, {
133                useSingleInferredProject: true,
134                typingsInstaller: installer,
135                logger: createLoggerWithInMemoryLogs(host),
136            });
137            projectService.setHostConfiguration({ preferences: { includePackageJsonAutoImports: "off" } });
138            projectService.openClientFile(file1.path);
139
140            installer.installAll(/*expectedCount*/ 1);
141            host.checkTimeoutQueueLengthAndRun(2);
142            baselineTsserverLogs("typingsInstaller", "configured projects", projectService);
143        });
144
145        it("inferred project (typings installed)", () => {
146            const file1 = {
147                path: "/a/b/app.js",
148                content: ""
149            };
150            const packageJson = {
151                path: "/a/b/package.json",
152                content: JSON.stringify({
153                    name: "test",
154                    dependencies: {
155                        jquery: "^3.1.0"
156                    }
157                })
158            };
159
160            const jquery = {
161                path: "/a/data/node_modules/@types/jquery/index.d.ts",
162                content: "declare const $: { x: number }"
163            };
164            const host = createServerHost([file1, packageJson]);
165            const installer = new (class extends Installer {
166                constructor() {
167                    super(host, { typesRegistry: createTypesRegistry("jquery") });
168                }
169                installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) {
170                    const installedTypings = ["@types/jquery"];
171                    const typingFiles = [jquery];
172                    executeCommand(this, host, installedTypings, typingFiles, cb);
173                }
174            })();
175
176            const projectService = createProjectService(host, { useSingleInferredProject: true, typingsInstaller: installer });
177            projectService.openClientFile(file1.path);
178
179            checkNumberOfProjects(projectService, { inferredProjects: 1 });
180            const p = projectService.inferredProjects[0];
181            checkProjectActualFiles(p, [file1.path]);
182
183            installer.installAll(/*expectedCount*/ 1);
184            host.checkTimeoutQueueLengthAndRun(2);
185            checkNumberOfProjects(projectService, { inferredProjects: 1 });
186            checkProjectActualFiles(p, [file1.path, jquery.path]);
187        });
188
189        it("inferred project - type acquisition with disableFilenameBasedTypeAcquisition:true", () => {
190            // Tests:
191            // Exclude file with disableFilenameBasedTypeAcquisition:true
192            const jqueryJs = {
193                path: "/a/b/jquery.js",
194                content: ""
195            };
196
197            const messages: string[] = [];
198            const host = createServerHost([jqueryJs]);
199            const installer = new (class extends Installer {
200                constructor() {
201                    super(host, { typesRegistry: createTypesRegistry("jquery") }, { isEnabled: () => true, writeLine: msg => messages.push(msg) });
202                }
203                enqueueInstallTypingsRequest(project: server.Project, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray<string>) {
204                    super.enqueueInstallTypingsRequest(project, typeAcquisition, unresolvedImports);
205                }
206                installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction): void {
207                    const installedTypings: string[] = [];
208                    const typingFiles: File[] = [];
209                    executeCommand(this, host, installedTypings, typingFiles, cb);
210                }
211            })();
212
213            const projectService = createProjectService(host, { typingsInstaller: installer });
214            projectService.setCompilerOptionsForInferredProjects({
215                allowJs: true,
216                enable: true,
217                disableFilenameBasedTypeAcquisition: true
218            });
219            projectService.openClientFile(jqueryJs.path);
220
221            checkNumberOfProjects(projectService, { inferredProjects: 1 });
222            const p = projectService.inferredProjects[0];
223            checkProjectActualFiles(p, [jqueryJs.path]);
224
225            installer.installAll(/*expectedCount*/ 0);
226            host.checkTimeoutQueueLength(0);
227            checkNumberOfProjects(projectService, { inferredProjects: 1 });
228            // files should not be removed from project if ATA is skipped
229            checkProjectActualFiles(p, [jqueryJs.path]);
230            assert.isTrue(messages.indexOf("No new typings were requested as a result of typings discovery") > 0, "Should not request filename-based typings");
231        });
232
233        it("external project - no type acquisition, no .d.ts/js files", () => {
234            const file1 = {
235                path: "/a/b/app.ts",
236                content: ""
237            };
238            const host = createServerHost([file1]);
239            const installer = new (class extends Installer {
240                constructor() {
241                    super(host);
242                }
243                enqueueInstallTypingsRequest() {
244                    assert(false, "auto discovery should not be enabled");
245                }
246            })();
247
248            const projectFileName = "/a/app/test.csproj";
249            const projectService = createProjectService(host, { typingsInstaller: installer });
250            projectService.openExternalProject({
251                projectFileName,
252                options: {},
253                rootFiles: [toExternalFile(file1.path)]
254            });
255            installer.checkPendingCommands(/*expectedCount*/ 0);
256            // by default auto discovery will kick in if project contain only .js/.d.ts files
257            // in this case project contain only ts files - no auto discovery
258            projectService.checkNumberOfProjects({ externalProjects: 1 });
259        });
260
261        it("external project - deduplicate from local @types packages", () => {
262            const appJs = {
263                path: "/a/b/app.js",
264                content: ""
265            };
266            const nodeDts = {
267                path: "/node_modules/@types/node/index.d.ts",
268                content: "declare var node;"
269            };
270            const host = createServerHost([appJs, nodeDts]);
271            const installer = new (class extends Installer {
272                constructor() {
273                    super(host, { typesRegistry: createTypesRegistry("node") });
274                }
275                installWorker() {
276                    assert(false, "nothing should get installed");
277                }
278            })();
279
280            const projectFileName = "/a/app/test.csproj";
281            const projectService = createProjectService(host, { typingsInstaller: installer });
282            projectService.openExternalProject({
283                projectFileName,
284                options: {},
285                rootFiles: [toExternalFile(appJs.path)],
286                typeAcquisition: { enable: true, include: ["node"] }
287            });
288            installer.checkPendingCommands(/*expectedCount*/ 0);
289            projectService.checkNumberOfProjects({ externalProjects: 1 });
290        });
291
292        it("external project - no auto in typing acquisition, no .d.ts/js files", () => {
293            const file1 = {
294                path: "/a/b/app.ts",
295                content: ""
296            };
297            const host = createServerHost([file1]);
298            const installer = new (class extends Installer {
299                constructor() {
300                    super(host, { typesRegistry: createTypesRegistry("jquery") });
301                }
302                enqueueInstallTypingsRequest() {
303                    assert(false, "auto discovery should not be enabled");
304                }
305            })();
306
307            const projectFileName = "/a/app/test.csproj";
308            const projectService = createProjectService(host, { typingsInstaller: installer });
309            projectService.openExternalProject({
310                projectFileName,
311                options: {},
312                rootFiles: [toExternalFile(file1.path)],
313                typeAcquisition: { include: ["jquery"] }
314            });
315            installer.checkPendingCommands(/*expectedCount*/ 0);
316            // by default auto discovery will kick in if project contain only .js/.d.ts files
317            // in this case project contain only ts files - no auto discovery even if type acquisition is set
318            projectService.checkNumberOfProjects({ externalProjects: 1 });
319        });
320
321        it("external project - autoDiscovery = true, no .d.ts/js files", () => {
322            const file1 = {
323                path: "/a/b/app.ts",
324                content: ""
325            };
326            const jquery = {
327                path: "/a/data/node_modules/@types/jquery/index.d.ts",
328                content: "declare const $: { x: number }"
329            };
330            const host = createServerHost([file1]);
331            let enqueueIsCalled = false;
332            const installer: Installer = new (class extends Installer {
333                constructor() {
334                    super(host, { typesRegistry: createTypesRegistry("jquery") });
335                }
336                enqueueInstallTypingsRequest(project: server.Project, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray<string>) {
337                    enqueueIsCalled = true;
338                    super.enqueueInstallTypingsRequest(project, typeAcquisition, unresolvedImports);
339                }
340                installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction): void {
341                    const installedTypings = ["@types/node"];
342                    const typingFiles = [jquery];
343                    executeCommand(this, host, installedTypings, typingFiles, cb);
344                }
345            })();
346
347            const projectFileName = "/a/app/test.csproj";
348            const projectService = createProjectService(host, { typingsInstaller: installer });
349            projectService.openExternalProject({
350                projectFileName,
351                options: {},
352                rootFiles: [toExternalFile(file1.path)],
353                typeAcquisition: { enable: true, include: ["jquery"] }
354            });
355
356            assert.isTrue(enqueueIsCalled, "expected enqueueIsCalled to be true");
357            installer.installAll(/*expectedCount*/ 1);
358
359            // auto is set in type acquisition - use it even if project contains only .ts files
360            projectService.checkNumberOfProjects({ externalProjects: 1 });
361        });
362
363        it("external project - no type acquisition, with only js, jsx, d.ts files", () => {
364            // Tests:
365            // 1. react typings are installed for .jsx
366            // 2. loose files names are matched against safe list for typings if
367            //    this is a JS project (only js, jsx, d.ts files are present)
368            const lodashJs = {
369                path: "/a/b/lodash.js",
370                content: ""
371            };
372            const file2Jsx = {
373                path: "/a/b/file2.jsx",
374                content: ""
375            };
376            const file3dts = {
377                path: "/a/b/file3.d.ts",
378                content: ""
379            };
380            const reactDts = {
381                path: "/a/data/node_modules/@types/react/index.d.ts",
382                content: "declare const react: { x: number }"
383            };
384            const lodashDts = {
385                path: "/a/data/node_modules/@types/lodash/index.d.ts",
386                content: "declare const lodash: { x: number }"
387            };
388
389            const host = createServerHost([lodashJs, file2Jsx, file3dts, customTypesMap]);
390            const installer = new (class extends Installer {
391                constructor() {
392                    super(host, { typesRegistry: createTypesRegistry("lodash", "react") });
393                }
394                installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction): void {
395                    const installedTypings = ["@types/lodash", "@types/react"];
396                    const typingFiles = [lodashDts, reactDts];
397                    executeCommand(this, host, installedTypings, typingFiles, cb);
398                }
399            })();
400
401            const projectFileName = "/a/app/test.csproj";
402            const projectService = createProjectService(host, { typingsInstaller: installer });
403            projectService.openExternalProject({
404                projectFileName,
405                options: { allowJS: true, moduleResolution: ModuleResolutionKind.NodeJs },
406                rootFiles: [toExternalFile(lodashJs.path), toExternalFile(file2Jsx.path), toExternalFile(file3dts.path)],
407                typeAcquisition: { }
408            });
409
410            const p = projectService.externalProjects[0];
411            projectService.checkNumberOfProjects({ externalProjects: 1 });
412            checkProjectActualFiles(p, [file2Jsx.path, file3dts.path]);
413
414            installer.installAll(/*expectedCount*/ 1);
415
416            checkNumberOfProjects(projectService, { externalProjects: 1 });
417            host.checkTimeoutQueueLengthAndRun(1);
418            checkNumberOfProjects(projectService, { externalProjects: 1 });
419            checkProjectActualFiles(p, [file2Jsx.path, file3dts.path, lodashDts.path, reactDts.path]);
420        });
421
422        it("external project - type acquisition with enable: false", () => {
423            // Tests:
424            // Exclude
425            const jqueryJs = {
426                path: "/a/b/jquery.js",
427                content: ""
428            };
429
430            const host = createServerHost([jqueryJs]);
431            const installer = new (class extends Installer {
432                constructor() {
433                    super(host, { typesRegistry: createTypesRegistry("jquery") });
434                }
435                enqueueInstallTypingsRequest(project: server.Project, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray<string>) {
436                    super.enqueueInstallTypingsRequest(project, typeAcquisition, unresolvedImports);
437                }
438                installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction): void {
439                    const installedTypings: string[] = [];
440                    const typingFiles: File[] = [];
441                    executeCommand(this, host, installedTypings, typingFiles, cb);
442                }
443            })();
444
445            const projectFileName = "/a/app/test.csproj";
446            const projectService = createProjectService(host, { typingsInstaller: installer });
447            projectService.openExternalProject({
448                projectFileName,
449                options: { allowJS: true, moduleResolution: ModuleResolutionKind.NodeJs },
450                rootFiles: [toExternalFile(jqueryJs.path)],
451                typeAcquisition: { enable: false }
452            });
453
454            const p = projectService.externalProjects[0];
455            projectService.checkNumberOfProjects({ externalProjects: 1 });
456            checkProjectActualFiles(p, [jqueryJs.path]);
457
458            installer.checkPendingCommands(/*expectedCount*/ 0);
459        });
460
461        it("external project - type acquisition with disableFilenameBasedTypeAcquisition:true", () => {
462            // Tests:
463            // Exclude file with disableFilenameBasedTypeAcquisition:true
464            const jqueryJs = {
465                path: "/a/b/jquery.js",
466                content: ""
467            };
468
469            const messages: string[] = [];
470            const host = createServerHost([jqueryJs]);
471            const installer = new (class extends Installer {
472                constructor() {
473                    super(host, { typesRegistry: createTypesRegistry("jquery") }, { isEnabled: () => true, writeLine: msg => messages.push(msg) });
474                }
475                enqueueInstallTypingsRequest(project: server.Project, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray<string>) {
476                    super.enqueueInstallTypingsRequest(project, typeAcquisition, unresolvedImports);
477                }
478                installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction): void {
479                    const installedTypings: string[] = [];
480                    const typingFiles: File[] = [];
481                    executeCommand(this, host, installedTypings, typingFiles, cb);
482                }
483            })();
484
485            const projectFileName = "/a/app/test.csproj";
486            const projectService = createProjectService(host, { typingsInstaller: installer });
487            projectService.openExternalProject({
488                projectFileName,
489                options: { allowJS: true, moduleResolution: ModuleResolutionKind.NodeJs },
490                rootFiles: [toExternalFile(jqueryJs.path)],
491                typeAcquisition: { enable: true, disableFilenameBasedTypeAcquisition: true }
492            });
493
494            const p = projectService.externalProjects[0];
495            projectService.checkNumberOfProjects({ externalProjects: 1 });
496            checkProjectActualFiles(p, [jqueryJs.path]);
497
498            installer.installAll(/*expectedCount*/ 0);
499            projectService.checkNumberOfProjects({ externalProjects: 1 });
500            // files should not be removed from project if ATA is skipped
501            checkProjectActualFiles(p, [jqueryJs.path]);
502            assert.isTrue(messages.indexOf("No new typings were requested as a result of typings discovery") > 0, "Should not request filename-based typings");
503        });
504
505        it("external project - no type acquisition, with js & ts files", () => {
506            // Tests:
507            // 1. No typings are included for JS projects when the project contains ts files
508            const jqueryJs = {
509                path: "/a/b/jquery.js",
510                content: ""
511            };
512            const file2Ts = {
513                path: "/a/b/file2.ts",
514                content: ""
515            };
516
517            const host = createServerHost([jqueryJs, file2Ts]);
518            const installer = new (class extends Installer {
519                constructor() {
520                    super(host, { typesRegistry: createTypesRegistry("jquery") });
521                }
522                enqueueInstallTypingsRequest(project: server.Project, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray<string>) {
523                    super.enqueueInstallTypingsRequest(project, typeAcquisition, unresolvedImports);
524                }
525                installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction): void {
526                    const installedTypings: string[] = [];
527                    const typingFiles: File[] = [];
528                    executeCommand(this, host, installedTypings, typingFiles, cb);
529                }
530            })();
531
532            const projectFileName = "/a/app/test.csproj";
533            const projectService = createProjectService(host, { typingsInstaller: installer });
534            projectService.openExternalProject({
535                projectFileName,
536                options: { allowJS: true, moduleResolution: ModuleResolutionKind.NodeJs },
537                rootFiles: [toExternalFile(jqueryJs.path), toExternalFile(file2Ts.path)],
538                typeAcquisition: {}
539            });
540
541            const p = projectService.externalProjects[0];
542            projectService.checkNumberOfProjects({ externalProjects: 1 });
543
544            checkProjectActualFiles(p, [jqueryJs.path, file2Ts.path]);
545
546            installer.checkPendingCommands(/*expectedCount*/ 0);
547
548            checkNumberOfProjects(projectService, { externalProjects: 1 });
549            checkProjectActualFiles(p, [jqueryJs.path, file2Ts.path]);
550        });
551
552        it("external project - with type acquisition, with only js, d.ts files", () => {
553            // Tests:
554            // 1. Safelist matching, type acquisition includes/excludes and package.json typings are all acquired
555            // 2. Types for safelist matches are not included when they also appear in the type acquisition exclude list
556            // 3. Multiple includes and excludes are respected in type acquisition
557            const lodashJs = {
558                path: "/a/b/lodash.js",
559                content: ""
560            };
561            const commanderJs = {
562                path: "/a/b/commander.js",
563                content: ""
564            };
565            const file3dts = {
566                path: "/a/b/file3.d.ts",
567                content: ""
568            };
569            const packageJson = {
570                path: "/a/b/package.json",
571                content: JSON.stringify({
572                    name: "test",
573                    dependencies: {
574                        express: "^3.1.0"
575                    }
576                })
577            };
578
579            const commander = {
580                path: "/a/data/node_modules/@types/commander/index.d.ts",
581                content: "declare const commander: { x: number }"
582            };
583            const express = {
584                path: "/a/data/node_modules/@types/express/index.d.ts",
585                content: "declare const express: { x: number }"
586            };
587            const jquery = {
588                path: "/a/data/node_modules/@types/jquery/index.d.ts",
589                content: "declare const jquery: { x: number }"
590            };
591            const moment = {
592                path: "/a/data/node_modules/@types/moment/index.d.ts",
593                content: "declare const moment: { x: number }"
594            };
595
596            const host = createServerHost([lodashJs, commanderJs, file3dts, packageJson, customTypesMap]);
597            const installer = new (class extends Installer {
598                constructor() {
599                    super(host, { typesRegistry: createTypesRegistry("jquery", "commander", "moment", "express") });
600                }
601                installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction): void {
602                    const installedTypings = ["@types/commander", "@types/express", "@types/jquery", "@types/moment"];
603                    const typingFiles = [commander, express, jquery, moment];
604                    executeCommand(this, host, installedTypings, typingFiles, cb);
605                }
606            })();
607
608            const projectFileName = "/a/app/test.csproj";
609            const projectService = createProjectService(host, { typingsInstaller: installer });
610            projectService.openExternalProject({
611                projectFileName,
612                options: { allowJS: true, moduleResolution: ModuleResolutionKind.NodeJs },
613                rootFiles: [toExternalFile(lodashJs.path), toExternalFile(commanderJs.path), toExternalFile(file3dts.path)],
614                typeAcquisition: { enable: true, include: ["jquery", "moment"], exclude: ["lodash"] }
615            });
616
617            const p = projectService.externalProjects[0];
618            projectService.checkNumberOfProjects({ externalProjects: 1 });
619            checkProjectActualFiles(p, [file3dts.path]);
620
621            installer.installAll(/*expectedCount*/ 1);
622
623            checkNumberOfProjects(projectService, { externalProjects: 1 });
624            host.checkTimeoutQueueLengthAndRun(1);
625            checkNumberOfProjects(projectService, { externalProjects: 1 });
626            // Commander: Existed as a JS file
627            // JQuery: Specified in 'include'
628            // Moment: Specified in 'include'
629            // Express: Specified in package.json
630            // lodash: Excluded (not present)
631            checkProjectActualFiles(p, [file3dts.path, commander.path, jquery.path, moment.path, express.path]);
632        });
633
634        it("Throttle - delayed typings to install", () => {
635            const lodashJs = {
636                path: "/a/b/lodash.js",
637                content: ""
638            };
639            const commanderJs = {
640                path: "/a/b/commander.js",
641                content: ""
642            };
643            const file3 = {
644                path: "/a/b/file3.d.ts",
645                content: ""
646            };
647            const packageJson = {
648                path: "/a/b/package.json",
649                content: JSON.stringify({
650                    name: "test",
651                    dependencies: {
652                        express: "^3.1.0"
653                    }
654                })
655            };
656
657            const commander = {
658                path: "/a/data/node_modules/@types/commander/index.d.ts",
659                content: "declare const commander: { x: number }"
660            };
661            const express = {
662                path: "/a/data/node_modules/@types/express/index.d.ts",
663                content: "declare const express: { x: number }"
664            };
665            const jquery = {
666                path: "/a/data/node_modules/@types/jquery/index.d.ts",
667                content: "declare const jquery: { x: number }"
668            };
669            const moment = {
670                path: "/a/data/node_modules/@types/moment/index.d.ts",
671                content: "declare const moment: { x: number }"
672            };
673            const lodash = {
674                path: "/a/data/node_modules/@types/lodash/index.d.ts",
675                content: "declare const lodash: { x: number }"
676            };
677
678            const typingFiles = [commander, express, jquery, moment, lodash];
679            const host = createServerHost([lodashJs, commanderJs, file3, packageJson, customTypesMap]);
680            const installer = new (class extends Installer {
681                constructor() {
682                    super(host, { throttleLimit: 3, typesRegistry: createTypesRegistry("commander", "express", "jquery", "moment", "lodash") });
683                }
684                installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction): void {
685                    const installedTypings = ["@types/commander", "@types/express", "@types/jquery", "@types/moment", "@types/lodash"];
686                    executeCommand(this, host, installedTypings, typingFiles, cb);
687                }
688            })();
689
690            const projectFileName = "/a/app/test.csproj";
691            const projectService = createProjectService(host, { typingsInstaller: installer });
692            projectService.openExternalProject({
693                projectFileName,
694                options: { allowJS: true, moduleResolution: ModuleResolutionKind.NodeJs },
695                rootFiles: [toExternalFile(lodashJs.path), toExternalFile(commanderJs.path), toExternalFile(file3.path)],
696                typeAcquisition: { include: ["jquery", "moment"] }
697            });
698
699            const p = projectService.externalProjects[0];
700            projectService.checkNumberOfProjects({ externalProjects: 1 });
701            checkProjectActualFiles(p, [file3.path]);
702            installer.checkPendingCommands(/*expectedCount*/ 1);
703            installer.executePendingCommands();
704            // expected all typings file to exist
705            for (const f of typingFiles) {
706                assert.isTrue(host.fileExists(f.path), `expected file ${f.path} to exist`);
707            }
708            host.checkTimeoutQueueLengthAndRun(1);
709            checkNumberOfProjects(projectService, { externalProjects: 1 });
710            checkProjectActualFiles(p, [file3.path, commander.path, express.path, jquery.path, moment.path, lodash.path]);
711        });
712
713        it("Throttle - delayed run install requests", () => {
714            const lodashJs = {
715                path: "/a/b/lodash.js",
716                content: ""
717            };
718            const commanderJs = {
719                path: "/a/b/commander.js",
720                content: ""
721            };
722            const file3 = {
723                path: "/a/b/file3.d.ts",
724                content: ""
725            };
726
727            const commander = {
728                path: "/a/data/node_modules/@types/commander/index.d.ts",
729                content: "declare const commander: { x: number }",
730                typings: typingsName("commander")
731            };
732            const jquery = {
733                path: "/a/data/node_modules/@types/jquery/index.d.ts",
734                content: "declare const jquery: { x: number }",
735                typings: typingsName("jquery")
736            };
737            const lodash = {
738                path: "/a/data/node_modules/@types/lodash/index.d.ts",
739                content: "declare const lodash: { x: number }",
740                typings: typingsName("lodash")
741            };
742            const cordova = {
743                path: "/a/data/node_modules/@types/cordova/index.d.ts",
744                content: "declare const cordova: { x: number }",
745                typings: typingsName("cordova")
746            };
747            const grunt = {
748                path: "/a/data/node_modules/@types/grunt/index.d.ts",
749                content: "declare const grunt: { x: number }",
750                typings: typingsName("grunt")
751            };
752            const gulp = {
753                path: "/a/data/node_modules/@types/gulp/index.d.ts",
754                content: "declare const gulp: { x: number }",
755                typings: typingsName("gulp")
756            };
757
758            const host = createServerHost([lodashJs, commanderJs, file3, customTypesMap]);
759            const installer = new (class extends Installer {
760                constructor() {
761                    super(host, { throttleLimit: 1, typesRegistry: createTypesRegistry("commander", "jquery", "lodash", "cordova", "gulp", "grunt") });
762                }
763                installWorker(_requestId: number, args: string[], _cwd: string, cb: TI.RequestCompletedAction): void {
764                    let typingFiles: (File & { typings: string })[] = [];
765                    if (args.indexOf(typingsName("commander")) >= 0) {
766                        typingFiles = [commander, jquery, lodash, cordova];
767                    }
768                    else {
769                        typingFiles = [grunt, gulp];
770                    }
771                    executeCommand(this, host, typingFiles.map(f => f.typings), typingFiles, cb);
772                }
773            })();
774
775            // Create project #1 with 4 typings
776            const projectService = createProjectService(host, { typingsInstaller: installer });
777            const projectFileName1 = "/a/app/test1.csproj";
778            projectService.openExternalProject({
779                projectFileName: projectFileName1,
780                options: { allowJS: true, moduleResolution: ModuleResolutionKind.NodeJs },
781                rootFiles: [toExternalFile(lodashJs.path), toExternalFile(commanderJs.path), toExternalFile(file3.path)],
782                typeAcquisition: { include: ["jquery", "cordova"] }
783            });
784
785            installer.checkPendingCommands(/*expectedCount*/ 1);
786            assert.equal(installer.pendingRunRequests.length, 0, "expect no throttled requests");
787
788            // Create project #2 with 2 typings
789            const projectFileName2 = "/a/app/test2.csproj";
790            projectService.openExternalProject({
791                projectFileName: projectFileName2,
792                options: { allowJS: true, moduleResolution: ModuleResolutionKind.NodeJs },
793                rootFiles: [toExternalFile(file3.path)],
794                typeAcquisition: { include: ["grunt", "gulp"] }
795            });
796            assert.equal(installer.pendingRunRequests.length, 1, "expect one throttled request");
797
798            const p1 = projectService.externalProjects[0];
799            const p2 = projectService.externalProjects[1];
800            projectService.checkNumberOfProjects({ externalProjects: 2 });
801            checkProjectActualFiles(p1, [file3.path]);
802            checkProjectActualFiles(p2, [file3.path]);
803
804            installer.executePendingCommands();
805
806            // expected one install request from the second project
807            installer.checkPendingCommands(/*expectedCount*/ 1);
808            assert.equal(installer.pendingRunRequests.length, 0, "expected no throttled requests");
809
810            installer.executePendingCommands();
811            host.checkTimeoutQueueLengthAndRun(2); // for 2 projects
812            checkProjectActualFiles(p1, [file3.path, commander.path, jquery.path, lodash.path, cordova.path]);
813            checkProjectActualFiles(p2, [file3.path, grunt.path, gulp.path]);
814        });
815
816        it("configured scoped name projects discover from node_modules", () => {
817            const app = {
818                path: "/app.js",
819                content: ""
820            };
821            const pkgJson = {
822                path: "/package.json",
823                content: JSON.stringify({
824                    dependencies: {
825                        "@zkat/cacache": "1.0.0"
826                    }
827                })
828            };
829            const jsconfig = {
830                path: "/jsconfig.json",
831                content: JSON.stringify({})
832            };
833            // Should only accept direct dependencies.
834            const commander = {
835                path: "/node_modules/commander/index.js",
836                content: ""
837            };
838            const commanderPackage = {
839                path: "/node_modules/commander/package.json",
840                content: JSON.stringify({
841                    name: "commander",
842                })
843            };
844            const cacache = {
845                path: "/node_modules/@zkat/cacache/index.js",
846                content: ""
847            };
848            const cacachePackage = {
849                path: "/node_modules/@zkat/cacache/package.json",
850                content: JSON.stringify({ name: "@zkat/cacache" })
851            };
852            const cacacheDTS = {
853                path: "/tmp/node_modules/@types/zkat__cacache/index.d.ts",
854                content: ""
855            };
856            const host = createServerHost([app, jsconfig, pkgJson, commander, commanderPackage, cacache, cacachePackage]);
857            const installer = new (class extends Installer {
858                constructor() {
859                    super(host, { globalTypingsCacheLocation: "/tmp", typesRegistry: createTypesRegistry("zkat__cacache", "nested", "commander") });
860                }
861                installWorker(_requestId: number, args: string[], _cwd: string, cb: TI.RequestCompletedAction) {
862                    assert.deepEqual(args, [`@types/zkat__cacache@ts${versionMajorMinor}`]);
863                    const installedTypings = ["@types/zkat__cacache"];
864                    const typingFiles = [cacacheDTS];
865                    executeCommand(this, host, installedTypings, typingFiles, cb);
866                }
867            })();
868
869            const projectService = createProjectService(host, { useSingleInferredProject: true, typingsInstaller: installer });
870            projectService.openClientFile(app.path);
871
872            checkNumberOfProjects(projectService, { configuredProjects: 1 });
873            const p = configuredProjectAt(projectService, 0);
874            checkProjectActualFiles(p, [app.path, jsconfig.path]);
875
876            installer.installAll(/*expectedCount*/ 1);
877
878            checkNumberOfProjects(projectService, { configuredProjects: 1 });
879            host.checkTimeoutQueueLengthAndRun(2);
880            checkProjectActualFiles(p, [app.path, cacacheDTS.path, jsconfig.path]);
881        });
882
883        function testConfiguredProjectNodeModules({ jsconfigContent, appJsContent, jQueryJsInProjectBeforeInstall, jQueryDtsInProjectAfterInstall }: {
884            jsconfigContent?: object,
885            appJsContent?: string,
886            jQueryJsInProjectBeforeInstall?: boolean,
887            jQueryDtsInProjectAfterInstall?: boolean,
888        } = {}) {
889        const app = {
890            path: "/app.js",
891                content: appJsContent || ""
892            };
893            const pkgJson = {
894                path: "/package.json",
895                content: JSON.stringify({
896                    dependencies: {
897                        jquery: "1.0.0"
898                    }
899                })
900            };
901            const jsconfig = {
902                path: "/jsconfig.json",
903                content: JSON.stringify(jsconfigContent || {})
904            };
905            // Should only accept direct dependencies.
906            const commander = {
907                path: "/node_modules/commander/index.js",
908                content: ""
909            };
910            const commanderPackage = {
911                path: "/node_modules/commander/package.json",
912                content: JSON.stringify({
913                    name: "commander",
914                })
915            };
916            const jquery = {
917                path: "/node_modules/jquery/index.js",
918                content: ""
919            };
920            const jqueryPackage = {
921                path: "/node_modules/jquery/package.json",
922                content: JSON.stringify({ name: "jquery" })
923            };
924            // Should not search deeply in node_modules.
925            const nestedPackage = {
926                path: "/node_modules/jquery/nested/package.json",
927                content: JSON.stringify({ name: "nested" }),
928            };
929            const jqueryDTS = {
930                path: "/tmp/node_modules/@types/jquery/index.d.ts",
931                content: ""
932            };
933            const host = createServerHost([app, jsconfig, pkgJson, commander, commanderPackage, jquery, jqueryPackage, nestedPackage]);
934            const installer = new (class extends Installer {
935                constructor() {
936                    super(host, { globalTypingsCacheLocation: "/tmp", typesRegistry: createTypesRegistry("jquery", "nested", "commander") });
937                }
938                installWorker(_requestId: number, args: string[], _cwd: string, cb: TI.RequestCompletedAction) {
939                    assert.deepEqual(args, [`@types/jquery@ts${versionMajorMinor}`]);
940                    const installedTypings = ["@types/jquery"];
941                    const typingFiles = [jqueryDTS];
942                    executeCommand(this, host, installedTypings, typingFiles, cb);
943                }
944            })();
945
946            const projectService = createProjectService(host, { useSingleInferredProject: true, typingsInstaller: installer });
947            projectService.openClientFile(app.path);
948
949            checkNumberOfProjects(projectService, { configuredProjects: 1 });
950            const p = configuredProjectAt(projectService, 0);
951            const filesBeforeInstall = jQueryJsInProjectBeforeInstall ? [app.path, jquery.path, jsconfig.path] : [app.path, jsconfig.path];
952            checkProjectActualFiles(p, filesBeforeInstall);
953
954            installer.installAll(jQueryDtsInProjectAfterInstall ? 1 : 0);
955
956            checkNumberOfProjects(projectService, { configuredProjects: 1 });
957            host.checkTimeoutQueueLengthAndRun(jQueryDtsInProjectAfterInstall ? 2 : 0);
958            checkProjectActualFiles(p, jQueryDtsInProjectAfterInstall ? [app.path, jqueryDTS.path, jsconfig.path] : filesBeforeInstall);
959        }
960
961        it("configured projects discover from node_modules", () => {
962            testConfiguredProjectNodeModules({
963                jQueryJsInProjectBeforeInstall: false,
964                jQueryDtsInProjectAfterInstall: true,
965            });
966        });
967
968        it("configured projects discover from node_modules - empty types", () => {
969            // Explicit types prevent automatic inclusion from package.json listing
970            testConfiguredProjectNodeModules({
971                jsconfigContent: { compilerOptions: { types: [] } },
972                jQueryJsInProjectBeforeInstall: false,
973                jQueryDtsInProjectAfterInstall: false,
974            });
975        });
976
977        it("configured projects discover from node_modules - explicit types", () => {
978            // A type reference directive will not resolve to the global typings cache
979            testConfiguredProjectNodeModules({
980                jsconfigContent: { compilerOptions: { types: ["jquery"] } },
981                jQueryJsInProjectBeforeInstall: false,
982                jQueryDtsInProjectAfterInstall: false
983            });
984        });
985
986        it("configured projects discover from node_modules - empty types but has import", () => {
987            // However, explicit types will not prevent unresolved imports from pulling in typings
988            testConfiguredProjectNodeModules({
989                jsconfigContent: { compilerOptions: { types: [] } },
990                appJsContent: `import "jquery";`,
991                jQueryJsInProjectBeforeInstall: true,
992                jQueryDtsInProjectAfterInstall: true,
993            });
994        });
995
996        it("configured projects discover from bower_components", () => {
997            const app = {
998                path: "/app.js",
999                content: ""
1000            };
1001            const jsconfig = {
1002                path: "/jsconfig.json",
1003                content: JSON.stringify({})
1004            };
1005            const jquery = {
1006                path: "/bower_components/jquery/index.js",
1007                content: ""
1008            };
1009            const jqueryPackage = {
1010                path: "/bower_components/jquery/bower.json",
1011                content: JSON.stringify({ name: "jquery" })
1012            };
1013            const jqueryDTS = {
1014                path: "/tmp/node_modules/@types/jquery/index.d.ts",
1015                content: ""
1016            };
1017            const host = createServerHost([app, jsconfig, jquery, jqueryPackage]);
1018            const installer = new (class extends Installer {
1019                constructor() {
1020                    super(host, { globalTypingsCacheLocation: "/tmp", typesRegistry: createTypesRegistry("jquery") });
1021                }
1022                installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) {
1023                    const installedTypings = ["@types/jquery"];
1024                    const typingFiles = [jqueryDTS];
1025                    executeCommand(this, host, installedTypings, typingFiles, cb);
1026                }
1027            })();
1028
1029            const projectService = createProjectService(host, {
1030                useSingleInferredProject: true,
1031                typingsInstaller: installer,
1032                logger: createLoggerWithInMemoryLogs(host),
1033            });
1034            projectService.openClientFile(app.path);
1035
1036            installer.installAll(/*expectedCount*/ 1);
1037
1038            host.checkTimeoutQueueLengthAndRun(2);
1039            baselineTsserverLogs("typingsInstaller", "configured projects discover from bower_components", projectService);
1040        });
1041
1042        it("configured projects discover from bower.json", () => {
1043            const app = {
1044                path: "/app.js",
1045                content: ""
1046            };
1047            const jsconfig = {
1048                path: "/jsconfig.json",
1049                content: JSON.stringify({})
1050            };
1051            const bowerJson = {
1052                path: "/bower.json",
1053                content: JSON.stringify({
1054                    dependencies: {
1055                        jquery: "^3.1.0"
1056                    }
1057                })
1058            };
1059            const jqueryDTS = {
1060                path: "/tmp/node_modules/@types/jquery/index.d.ts",
1061                content: ""
1062            };
1063            const host = createServerHost([app, jsconfig, bowerJson]);
1064            const installer = new (class extends Installer {
1065                constructor() {
1066                    super(host, { globalTypingsCacheLocation: "/tmp", typesRegistry: createTypesRegistry("jquery") });
1067                }
1068                installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) {
1069                    const installedTypings = ["@types/jquery"];
1070                    const typingFiles = [jqueryDTS];
1071                    executeCommand(this, host, installedTypings, typingFiles, cb);
1072                }
1073            })();
1074
1075            const projectService = createProjectService(host, { useSingleInferredProject: true, typingsInstaller: installer });
1076            projectService.openClientFile(app.path);
1077
1078            checkNumberOfProjects(projectService, { configuredProjects: 1 });
1079            const p = configuredProjectAt(projectService, 0);
1080            checkProjectActualFiles(p, [app.path, jsconfig.path]);
1081
1082            installer.installAll(/*expectedCount*/ 1);
1083
1084            checkNumberOfProjects(projectService, { configuredProjects: 1 });
1085            host.checkTimeoutQueueLengthAndRun(2);
1086            checkProjectActualFiles(p, [app.path, jqueryDTS.path, jsconfig.path]);
1087        });
1088
1089        it("Malformed package.json should be watched", () => {
1090            const f = {
1091                path: "/a/b/app.js",
1092                content: "var x = 1"
1093            };
1094            const brokenPackageJson = {
1095                path: "/a/b/package.json",
1096                content: `{ "dependencies": { "co } }`
1097            };
1098            const fixedPackageJson = {
1099                path: brokenPackageJson.path,
1100                content: `{ "dependencies": { "commander": "0.0.2" } }`
1101            };
1102            const cachePath = "/a/cache/";
1103            const commander = {
1104                path: cachePath + "node_modules/@types/commander/index.d.ts",
1105                content: "export let x: number"
1106            };
1107            const host = createServerHost([f, brokenPackageJson]);
1108            const installer = new (class extends Installer {
1109                constructor() {
1110                    super(host, { globalTypingsCacheLocation: cachePath, typesRegistry: createTypesRegistry("commander") });
1111                }
1112                installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) {
1113                    const installedTypings = ["@types/commander"];
1114                    const typingFiles = [commander];
1115                    executeCommand(this, host, installedTypings, typingFiles, cb);
1116                }
1117            })();
1118            const service = createProjectService(host, { typingsInstaller: installer });
1119            service.openClientFile(f.path);
1120
1121            installer.checkPendingCommands(/*expectedCount*/ 0);
1122            host.writeFile(fixedPackageJson.path, fixedPackageJson.content);
1123            host.checkTimeoutQueueLength(0);
1124            // expected install request
1125            installer.installAll(/*expectedCount*/ 1);
1126            host.checkTimeoutQueueLengthAndRun(2);
1127            service.checkNumberOfProjects({ inferredProjects: 1 });
1128            checkProjectActualFiles(service.inferredProjects[0], [f.path, commander.path]);
1129        });
1130
1131        it("should install typings for unresolved imports", () => {
1132            const file = {
1133                path: "/a/b/app.js",
1134                content: `
1135                import * as fs from "fs";
1136                import * as commander from "commander";
1137                import * as component from "@ember/component";`
1138            };
1139            const cachePath = "/a/cache";
1140            const node = {
1141                path: cachePath + "/node_modules/@types/node/index.d.ts",
1142                content: "export let x: number"
1143            };
1144            const commander = {
1145                path: cachePath + "/node_modules/@types/commander/index.d.ts",
1146                content: "export let y: string"
1147            };
1148            const emberComponentDirectory = "ember__component";
1149            const emberComponent = {
1150                path: `${cachePath}/node_modules/@types/${emberComponentDirectory}/index.d.ts`,
1151                content: "export let x: number"
1152            };
1153            const host = createServerHost([file]);
1154            const installer = new (class extends Installer {
1155                constructor() {
1156                    super(host, { globalTypingsCacheLocation: cachePath, typesRegistry: createTypesRegistry("node", "commander") });
1157                }
1158                installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) {
1159                    const installedTypings = ["@types/node", "@types/commander", `@types/${emberComponentDirectory}`];
1160                    const typingFiles = [node, commander, emberComponent];
1161                    executeCommand(this, host, installedTypings, typingFiles, cb);
1162                }
1163            })();
1164            const service = createProjectService(host, { typingsInstaller: installer });
1165            service.openClientFile(file.path);
1166
1167            service.checkNumberOfProjects({ inferredProjects: 1 });
1168            checkProjectActualFiles(service.inferredProjects[0], [file.path]);
1169
1170            installer.installAll(/*expectedCount*/1);
1171
1172            assert.isTrue(host.fileExists(node.path), "typings for 'node' should be created");
1173            assert.isTrue(host.fileExists(commander.path), "typings for 'commander' should be created");
1174            assert.isTrue(host.fileExists(emberComponent.path), "typings for 'commander' should be created");
1175
1176            host.checkTimeoutQueueLengthAndRun(2);
1177            checkProjectActualFiles(service.inferredProjects[0], [file.path, node.path, commander.path, emberComponent.path]);
1178        });
1179
1180        it("should redo resolution that resolved to '.js' file after typings are installed", () => {
1181            const file: TestFSWithWatch.File = {
1182                path: `${tscWatch.projects}/a/b/app.js`,
1183                content: `
1184                import * as commander from "commander";`
1185            };
1186            const cachePath = `${tscWatch.projects}/a/cache`;
1187            const commanderJS: TestFSWithWatch.File = {
1188                path: `${tscWatch.projects}/node_modules/commander/index.js`,
1189                content: "module.exports = 0",
1190            };
1191
1192            const typeNames: readonly string[] = ["commander"];
1193            const typePath = (name: string): string => `${cachePath}/node_modules/@types/${name}/index.d.ts`;
1194            const host = createServerHost([file, commanderJS]);
1195            const installer = new (class extends Installer {
1196                constructor() {
1197                    super(host, { globalTypingsCacheLocation: cachePath, typesRegistry: createTypesRegistry(...typeNames) });
1198                }
1199                installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) {
1200                    const installedTypings = typeNames.map(name => `@types/${name}`);
1201                    const typingFiles = typeNames.map((name): TestFSWithWatch.File => ({ path: typePath(name), content: "" }));
1202                    executeCommand(this, host, installedTypings, typingFiles, cb);
1203                }
1204            })();
1205            const service = createProjectService(host, {
1206                typingsInstaller: installer,
1207                logger: createLoggerWithInMemoryLogs(host),
1208            });
1209            service.openClientFile(file.path);
1210
1211            installer.installAll(/*expectedCount*/1);
1212            for (const name of typeNames) {
1213                assert.isTrue(host.fileExists(typePath(name)), `typings for '${name}' should be created`);
1214            }
1215            host.checkTimeoutQueueLengthAndRun(2);
1216            baselineTsserverLogs("typingsInstaller", "redo resolutions pointing to js on typing install", service);
1217        });
1218
1219        it("should pick typing names from non-relative unresolved imports", () => {
1220            const f1 = {
1221                path: "/a/b/app.js",
1222                content: `
1223                import * as a from "foo/a/a";
1224                import * as b from "foo/a/b";
1225                import * as c from "foo/a/c";
1226                import * as d from "@bar/router/";
1227                import * as e from "@bar/common/shared";
1228                import * as e from "@bar/common/apps";
1229                import * as f from "./lib"
1230                `
1231            };
1232
1233            const host = createServerHost([f1]);
1234            const installer = new (class extends Installer {
1235                constructor() {
1236                    super(host, { globalTypingsCacheLocation: "/tmp", typesRegistry: createTypesRegistry("foo") });
1237                }
1238                installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) {
1239                    executeCommand(this, host, ["foo"], [], cb);
1240                }
1241            })();
1242            const projectService = createProjectService(host, { typingsInstaller: installer });
1243            projectService.openClientFile(f1.path);
1244            projectService.checkNumberOfProjects({ inferredProjects: 1 });
1245
1246            const proj = projectService.inferredProjects[0];
1247            proj.updateGraph();
1248
1249            assert.deepEqual(
1250                proj.cachedUnresolvedImportsPerFile.get(f1.path as Path),
1251                ["foo", "foo", "foo", "@bar/router", "@bar/common", "@bar/common"]
1252            );
1253
1254            installer.installAll(/*expectedCount*/ 1);
1255        });
1256
1257        it("cached unresolved typings are not recomputed if program structure did not change", () => {
1258            const host = createServerHost([]);
1259            const session = createSession(host);
1260            const f = {
1261                path: "/a/app.js",
1262                content: `
1263                import * as fs from "fs";
1264                import * as cmd from "commander
1265                `
1266            };
1267            const openRequest: server.protocol.OpenRequest = {
1268                seq: 1,
1269                type: "request",
1270                command: server.protocol.CommandTypes.Open,
1271                arguments: {
1272                    file: f.path,
1273                    fileContent: f.content
1274                }
1275            };
1276            session.executeCommand(openRequest);
1277            const projectService = session.getProjectService();
1278            checkNumberOfProjects(projectService, { inferredProjects: 1 });
1279            const proj = projectService.inferredProjects[0];
1280            const version1 = proj.lastCachedUnresolvedImportsList;
1281
1282            // make a change that should not affect the structure of the program
1283            const changeRequest: server.protocol.ChangeRequest = {
1284                seq: 2,
1285                type: "request",
1286                command: server.protocol.CommandTypes.Change,
1287                arguments: {
1288                    file: f.path,
1289                    insertString: "\nlet x = 1;",
1290                    line: 2,
1291                    offset: 0,
1292                    endLine: 2,
1293                    endOffset: 0
1294                }
1295            };
1296            session.executeCommand(changeRequest);
1297            host.checkTimeoutQueueLength(0);
1298            proj.updateGraph();
1299            const version2 = proj.lastCachedUnresolvedImportsList;
1300            assert.strictEqual(version1, version2, "set of unresolved imports should change");
1301        });
1302
1303        it("expired cache entry (inferred project, should install typings)", () => {
1304            const file1 = {
1305                path: "/a/b/app.js",
1306                content: ""
1307            };
1308            const packageJson = {
1309                path: "/a/b/package.json",
1310                content: JSON.stringify({
1311                    name: "test",
1312                    dependencies: {
1313                        jquery: "^3.1.0"
1314                    }
1315                })
1316            };
1317            const jquery = {
1318                path: "/a/data/node_modules/@types/jquery/index.d.ts",
1319                content: "declare const $: { x: number }"
1320            };
1321            const cacheConfig = {
1322                path: "/a/data/package.json",
1323                content: JSON.stringify({
1324                    dependencies: {
1325                        "types-registry": "^0.1.317"
1326                    },
1327                    devDependencies: {
1328                        "@types/jquery": "^1.0.0"
1329                    }
1330                })
1331            };
1332            const cacheLockConfig = {
1333                path: "/a/data/package-lock.json",
1334                content: JSON.stringify({
1335                    dependencies: {
1336                        "@types/jquery": {
1337                            version: "1.0.0"
1338                        }
1339                    }
1340                })
1341            };
1342            const host = createServerHost([file1, packageJson, jquery, cacheConfig, cacheLockConfig]);
1343            const installer = new (class extends Installer {
1344                constructor() {
1345                    super(host, { typesRegistry: createTypesRegistry("jquery") });
1346                }
1347                installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) {
1348                    const installedTypings = ["@types/jquery"];
1349                    const typingFiles = [jquery];
1350                    executeCommand(this, host, installedTypings, typingFiles, cb);
1351                }
1352            })();
1353
1354            const projectService = createProjectService(host, { useSingleInferredProject: true, typingsInstaller: installer });
1355            projectService.openClientFile(file1.path);
1356
1357            checkNumberOfProjects(projectService, { inferredProjects: 1 });
1358            const p = projectService.inferredProjects[0];
1359            checkProjectActualFiles(p, [file1.path]);
1360
1361            installer.installAll(/*expectedCount*/ 1);
1362            host.checkTimeoutQueueLengthAndRun(2);
1363            checkNumberOfProjects(projectService, { inferredProjects: 1 });
1364            checkProjectActualFiles(p, [file1.path, jquery.path]);
1365        });
1366
1367        it("non-expired cache entry (inferred project, should not install typings)", () => {
1368            const file1 = {
1369                path: "/a/b/app.js",
1370                content: ""
1371            };
1372            const packageJson = {
1373                path: "/a/b/package.json",
1374                content: JSON.stringify({
1375                    name: "test",
1376                    dependencies: {
1377                        jquery: "^3.1.0"
1378                    }
1379                })
1380            };
1381            const timestamps = {
1382                path: "/a/data/timestamps.json",
1383                content: JSON.stringify({
1384                    entries: {
1385                        "@types/jquery": Date.now()
1386                    }
1387                })
1388            };
1389            const cacheConfig = {
1390                path: "/a/data/package.json",
1391                content: JSON.stringify({
1392                    dependencies: {
1393                        "types-registry": "^0.1.317"
1394                    },
1395                    devDependencies: {
1396                        "@types/jquery": "^1.3.0"
1397                    }
1398                })
1399            };
1400            const cacheLockConfig = {
1401                path: "/a/data/package-lock.json",
1402                content: JSON.stringify({
1403                    dependencies: {
1404                        "@types/jquery": {
1405                            version: "1.3.0"
1406                        }
1407                    }
1408                })
1409            };
1410            const jquery = {
1411                path: "/a/data/node_modules/@types/jquery/index.d.ts",
1412                content: "declare const $: { x: number }"
1413            };
1414            const host = createServerHost([file1, packageJson, timestamps, cacheConfig, cacheLockConfig, jquery]);
1415            const installer = new (class extends Installer {
1416                constructor() {
1417                    super(host, { typesRegistry: createTypesRegistry("jquery") });
1418                }
1419                installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) {
1420                    const installedTypings: string[] = [];
1421                    const typingFiles: File[] = [];
1422                    executeCommand(this, host, installedTypings, typingFiles, cb);
1423                }
1424            })();
1425
1426            const projectService = createProjectService(host, { useSingleInferredProject: true, typingsInstaller: installer });
1427            projectService.openClientFile(file1.path);
1428
1429            checkNumberOfProjects(projectService, { inferredProjects: 1 });
1430            const p = projectService.inferredProjects[0];
1431            checkProjectActualFiles(p, [file1.path]);
1432
1433            installer.installAll(/*expectedCount*/ 0);
1434
1435            checkNumberOfProjects(projectService, { inferredProjects: 1 });
1436            checkProjectActualFiles(p, [file1.path]);
1437        });
1438    });
1439
1440    describe("unittests:: tsserver:: typingsInstaller:: Validate package name:", () => {
1441        it("name cannot be too long", () => {
1442            let packageName = "a";
1443            for (let i = 0; i < 8; i++) {
1444                packageName += packageName;
1445            }
1446            assert.equal(validatePackageName(packageName), NameValidationResult.NameTooLong);
1447        });
1448        it("package name cannot start with dot", () => {
1449            assert.equal(validatePackageName(".foo"), NameValidationResult.NameStartsWithDot);
1450        });
1451        it("package name cannot start with underscore", () => {
1452            assert.equal(validatePackageName("_foo"), NameValidationResult.NameStartsWithUnderscore);
1453        });
1454        it("package non URI safe characters are not supported", () => {
1455            assert.equal(validatePackageName("  scope  "), NameValidationResult.NameContainsNonURISafeCharacters);
1456            assert.equal(validatePackageName("; say ‘Hello from TypeScript!’ #"), NameValidationResult.NameContainsNonURISafeCharacters);
1457            assert.equal(validatePackageName("a/b/c"), NameValidationResult.NameContainsNonURISafeCharacters);
1458        });
1459        it("scoped package name is supported", () => {
1460            assert.equal(validatePackageName("@scope/bar"), NameValidationResult.Ok);
1461        });
1462        it("scoped name in scoped package name cannot start with dot", () => {
1463            assert.deepEqual(validatePackageName("@.scope/bar"), { name: ".scope", isScopeName: true, result: NameValidationResult.NameStartsWithDot });
1464            assert.deepEqual(validatePackageName("@.scope/.bar"), { name: ".scope", isScopeName: true, result: NameValidationResult.NameStartsWithDot });
1465        });
1466        it("scope name in scoped package name cannot start with underscore", () => {
1467            assert.deepEqual(validatePackageName("@_scope/bar"), { name: "_scope", isScopeName: true, result: NameValidationResult.NameStartsWithUnderscore });
1468            assert.deepEqual(validatePackageName("@_scope/_bar"), { name: "_scope", isScopeName: true, result: NameValidationResult.NameStartsWithUnderscore });
1469        });
1470        it("scope name in scoped package name with non URI safe characters are not supported", () => {
1471            assert.deepEqual(validatePackageName("@  scope  /bar"), { name: "  scope  ", isScopeName: true, result: NameValidationResult.NameContainsNonURISafeCharacters });
1472            assert.deepEqual(validatePackageName("@; say ‘Hello from TypeScript!’ #/bar"), { name: "; say ‘Hello from TypeScript!’ #", isScopeName: true, result: NameValidationResult.NameContainsNonURISafeCharacters });
1473            assert.deepEqual(validatePackageName("@  scope  /  bar  "), { name: "  scope  ", isScopeName: true, result: NameValidationResult.NameContainsNonURISafeCharacters });
1474        });
1475        it("package name in scoped package name cannot start with dot", () => {
1476            assert.deepEqual(validatePackageName("@scope/.bar"), { name: ".bar", isScopeName: false, result: NameValidationResult.NameStartsWithDot });
1477        });
1478        it("package name in scoped package name cannot start with underscore", () => {
1479            assert.deepEqual(validatePackageName("@scope/_bar"), { name: "_bar", isScopeName: false, result: NameValidationResult.NameStartsWithUnderscore });
1480        });
1481        it("package name in scoped package name with non URI safe characters are not supported", () => {
1482            assert.deepEqual(validatePackageName("@scope/  bar  "), { name: "  bar  ", isScopeName: false, result: NameValidationResult.NameContainsNonURISafeCharacters });
1483            assert.deepEqual(validatePackageName("@scope/; say ‘Hello from TypeScript!’ #"), { name: "; say ‘Hello from TypeScript!’ #", isScopeName: false, result: NameValidationResult.NameContainsNonURISafeCharacters });
1484        });
1485    });
1486
1487    describe("unittests:: tsserver:: typingsInstaller:: Invalid package names", () => {
1488        it("should not be installed", () => {
1489            const f1 = {
1490                path: "/a/b/app.js",
1491                content: "let x = 1"
1492            };
1493            const packageJson = {
1494                path: "/a/b/package.json",
1495                content: JSON.stringify({
1496                    dependencies: {
1497                        "; say ‘Hello from TypeScript!’ #": "0.0.x"
1498                    }
1499                })
1500            };
1501            const messages: string[] = [];
1502            const host = createServerHost([f1, packageJson]);
1503            const installer = new (class extends Installer {
1504                constructor() {
1505                    super(host, { globalTypingsCacheLocation: "/tmp" }, { isEnabled: () => true, writeLine: msg => messages.push(msg) });
1506                }
1507                installWorker(_requestId: number, _args: string[], _cwd: string, _cb: TI.RequestCompletedAction) {
1508                    assert(false, "runCommand should not be invoked");
1509                }
1510            })();
1511            const projectService = createProjectService(host, { typingsInstaller: installer });
1512            projectService.openClientFile(f1.path);
1513
1514            installer.checkPendingCommands(/*expectedCount*/ 0);
1515            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");
1516        });
1517    });
1518
1519    describe("unittests:: tsserver:: typingsInstaller:: discover typings", () => {
1520        const emptySafeList = emptyMap;
1521
1522        it("should use mappings from safe list", () => {
1523            const app = {
1524                path: "/a/b/app.js",
1525                content: ""
1526            };
1527            const jquery = {
1528                path: "/a/b/jquery.js",
1529                content: ""
1530            };
1531            const chroma = {
1532                path: "/a/b/chroma.min.js",
1533                content: ""
1534            };
1535
1536            const safeList = new Map(getEntries({ jquery: "jquery", chroma: "chroma-js" }));
1537
1538            const host = createServerHost([app, jquery, chroma]);
1539            const logger = trackingLogger();
1540            const result = JsTyping.discoverTypings(host, logger.log, [app.path, jquery.path, chroma.path], getDirectoryPath(app.path as Path), safeList, emptyMap, { enable: true }, emptyArray, emptyMap, emptyOptions);
1541            const finish = logger.finish();
1542            assert.deepEqual(finish, [
1543                'Inferred typings from file names: ["jquery","chroma-js"]',
1544                "Inferred typings from unresolved imports: []",
1545                'Result: {"cachedTypingPaths":[],"newTypingNames":["jquery","chroma-js"],"filesToWatch":["/a/b/bower_components","/a/b/node_modules","/a/b/oh_modules"]}',
1546            ], finish.join("\r\n"));
1547            assert.deepEqual(result.newTypingNames, ["jquery", "chroma-js"]);
1548        });
1549
1550        it("should return node for core modules", () => {
1551            const f = {
1552                path: "/a/b/app.js",
1553                content: ""
1554            };
1555            const host = createServerHost([f]);
1556            const cache = new Map<string, JsTyping.CachedTyping>();
1557
1558            for (const name of JsTyping.nodeCoreModuleList) {
1559                const logger = trackingLogger();
1560                const result = JsTyping.discoverTypings(host, logger.log, [f.path], getDirectoryPath(f.path as Path), emptySafeList, cache, { enable: true }, [name, "somename"], emptyMap, emptyOptions);
1561                assert.deepEqual(logger.finish(), [
1562                    'Inferred typings from unresolved imports: ["node","somename"]',
1563                    'Result: {"cachedTypingPaths":[],"newTypingNames":["node","somename"],"filesToWatch":["/a/b/bower_components","/a/b/node_modules","/a/b/oh_modules"]}',
1564                ]);
1565                assert.deepEqual(result.newTypingNames.sort(), ["node", "somename"]);
1566            }
1567        });
1568
1569        it("should use cached locations", () => {
1570            const f = {
1571                path: "/a/b/app.js",
1572                content: ""
1573            };
1574            const node = {
1575                path: "/a/b/node.d.ts",
1576                content: ""
1577            };
1578            const host = createServerHost([f, node]);
1579            const cache = new Map(getEntries<JsTyping.CachedTyping>({ node: { typingLocation: node.path, version: new Version("1.3.0") } }));
1580            const registry = createTypesRegistry("node");
1581            const logger = trackingLogger();
1582            const result = JsTyping.discoverTypings(host, logger.log, [f.path], getDirectoryPath(f.path as Path), emptySafeList, cache, { enable: true }, ["fs", "bar"], registry, emptyOptions);
1583            assert.deepEqual(logger.finish(), [
1584                'Inferred typings from unresolved imports: ["node","bar"]',
1585                'Result: {"cachedTypingPaths":["/a/b/node.d.ts"],"newTypingNames":["bar"],"filesToWatch":["/a/b/bower_components","/a/b/node_modules","/a/b/oh_modules"]}',
1586            ]);
1587            assert.deepEqual(result.cachedTypingPaths, [node.path]);
1588            assert.deepEqual(result.newTypingNames, ["bar"]);
1589        });
1590
1591        it("should gracefully handle packages that have been removed from the types-registry", () => {
1592            const f = {
1593                path: "/a/b/app.js",
1594                content: ""
1595            };
1596            const node = {
1597                path: "/a/b/node.d.ts",
1598                content: ""
1599            };
1600            const host = createServerHost([f, node]);
1601            const cache = new Map(getEntries<JsTyping.CachedTyping>({ node: { typingLocation: node.path, version: new Version("1.3.0") } }));
1602            const logger = trackingLogger();
1603            const result = JsTyping.discoverTypings(host, logger.log, [f.path], getDirectoryPath(f.path as Path), emptySafeList, cache, { enable: true }, ["fs", "bar"], emptyMap, emptyOptions);
1604            assert.deepEqual(logger.finish(), [
1605                'Inferred typings from unresolved imports: ["node","bar"]',
1606                'Result: {"cachedTypingPaths":[],"newTypingNames":["node","bar"],"filesToWatch":["/a/b/bower_components","/a/b/node_modules","/a/b/oh_modules"]}',
1607            ]);
1608            assert.deepEqual(result.cachedTypingPaths, []);
1609            assert.deepEqual(result.newTypingNames, ["node", "bar"]);
1610        });
1611
1612        it("should search only 2 levels deep", () => {
1613            const app = {
1614                path: "/app.js",
1615                content: "",
1616            };
1617            const a = {
1618                path: "/node_modules/a/package.json",
1619                content: JSON.stringify({ name: "a" }),
1620            };
1621            const b = {
1622                path: "/node_modules/a/b/package.json",
1623                content: JSON.stringify({ name: "b" }),
1624            };
1625            const host = createServerHost([app, a, b]);
1626            const cache = new Map<string, JsTyping.CachedTyping>();
1627            const logger = trackingLogger();
1628            const result = JsTyping.discoverTypings(host, logger.log, [app.path], getDirectoryPath(app.path as Path), emptySafeList, cache, { enable: true }, /*unresolvedImports*/ [], emptyMap, emptyOptions);
1629            assert.deepEqual(logger.finish(), [
1630                'Searching for typing names in /node_modules; all files: ["/node_modules/a/package.json"]',
1631                '    Found package names: ["a"]',
1632                "Inferred typings from unresolved imports: []",
1633                'Result: {"cachedTypingPaths":[],"newTypingNames":["a"],"filesToWatch":["/bower_components","/node_modules","/oh_modules"]}',
1634            ]);
1635            assert.deepEqual(result, {
1636                cachedTypingPaths: [],
1637                newTypingNames: ["a"], // But not "b"
1638                filesToWatch: ["/bower_components", "/node_modules", "/oh_modules"],
1639            });
1640        });
1641
1642        it("should support scoped packages", () => {
1643            const app = {
1644                path: "/app.js",
1645                content: "",
1646            };
1647            const a = {
1648                path: "/node_modules/@a/b/package.json",
1649                content: JSON.stringify({ name: "@a/b" }),
1650            };
1651            const host = createServerHost([app, a]);
1652            const cache = new Map<string, JsTyping.CachedTyping>();
1653            const logger = trackingLogger();
1654            const result = JsTyping.discoverTypings(host, logger.log, [app.path], getDirectoryPath(app.path as Path), emptySafeList, cache, { enable: true }, /*unresolvedImports*/ [], emptyMap, emptyOptions);
1655            assert.deepEqual(logger.finish(), [
1656                'Searching for typing names in /node_modules; all files: ["/node_modules/@a/b/package.json"]',
1657                '    Found package names: ["@a/b"]',
1658                "Inferred typings from unresolved imports: []",
1659                'Result: {"cachedTypingPaths":[],"newTypingNames":["@a/b"],"filesToWatch":["/bower_components","/node_modules","/oh_modules"]}',
1660            ]);
1661            assert.deepEqual(result, {
1662                cachedTypingPaths: [],
1663                newTypingNames: ["@a/b"],
1664                filesToWatch: ["/bower_components", "/node_modules", "/oh_modules"],
1665            });
1666        });
1667        it("should install expired typings", () => {
1668            const app = {
1669                path: "/a/app.js",
1670                content: ""
1671            };
1672            const cachePath = "/a/cache/";
1673            const commander = {
1674                path: cachePath + "node_modules/@types/commander/index.d.ts",
1675                content: "export let x: number"
1676            };
1677            const node = {
1678                path: cachePath + "node_modules/@types/node/index.d.ts",
1679                content: "export let y: number"
1680            };
1681            const host = createServerHost([app]);
1682            const cache = new Map(getEntries<JsTyping.CachedTyping>({
1683                node: { typingLocation: node.path, version: new Version("1.3.0") },
1684                commander: { typingLocation: commander.path, version: new Version("1.0.0") }
1685            }));
1686            const registry = createTypesRegistry("node", "commander");
1687            const logger = trackingLogger();
1688            const result = JsTyping.discoverTypings(host, logger.log, [app.path], getDirectoryPath(app.path as Path), emptySafeList, cache, { enable: true }, ["http", "commander"], registry, emptyOptions);
1689            assert.deepEqual(logger.finish(), [
1690                'Inferred typings from unresolved imports: ["node","commander"]',
1691                'Result: {"cachedTypingPaths":["/a/cache/node_modules/@types/node/index.d.ts"],"newTypingNames":["commander"],"filesToWatch":["/a/bower_components","/a/node_modules","/a/oh_modules"]}',
1692            ]);
1693            assert.deepEqual(result.cachedTypingPaths, [node.path]);
1694            assert.deepEqual(result.newTypingNames, ["commander"]);
1695        });
1696
1697        it("should install expired typings with prerelease version of tsserver", () => {
1698            const app = {
1699                path: "/a/app.js",
1700                content: ""
1701            };
1702            const cachePath = "/a/cache/";
1703            const node = {
1704                path: cachePath + "node_modules/@types/node/index.d.ts",
1705                content: "export let y: number"
1706            };
1707            const host = createServerHost([app]);
1708            const cache = new Map(getEntries<JsTyping.CachedTyping>({
1709                node: { typingLocation: node.path, version: new Version("1.0.0") }
1710            }));
1711            const registry = createTypesRegistry("node");
1712            registry.delete(`ts${versionMajorMinor}`);
1713            const logger = trackingLogger();
1714            const result = JsTyping.discoverTypings(host, logger.log, [app.path], getDirectoryPath(app.path as Path), emptySafeList, cache, { enable: true }, ["http"], registry, emptyOptions);
1715            assert.deepEqual(logger.finish(), [
1716                'Inferred typings from unresolved imports: ["node"]',
1717                'Result: {"cachedTypingPaths":[],"newTypingNames":["node"],"filesToWatch":["/a/bower_components","/a/node_modules","/a/oh_modules"]}',
1718            ]);
1719            assert.deepEqual(result.cachedTypingPaths, []);
1720            assert.deepEqual(result.newTypingNames, ["node"]);
1721        });
1722
1723
1724        it("prerelease typings are properly handled", () => {
1725            const app = {
1726                path: "/a/app.js",
1727                content: ""
1728            };
1729            const cachePath = "/a/cache/";
1730            const commander = {
1731                path: cachePath + "node_modules/@types/commander/index.d.ts",
1732                content: "export let x: number"
1733            };
1734            const node = {
1735                path: cachePath + "node_modules/@types/node/index.d.ts",
1736                content: "export let y: number"
1737            };
1738            const host = createServerHost([app]);
1739            const cache = new Map(getEntries<JsTyping.CachedTyping>({
1740                node: { typingLocation: node.path, version: new Version("1.3.0-next.0") },
1741                commander: { typingLocation: commander.path, version: new Version("1.3.0-next.0") }
1742            }));
1743            const registry = createTypesRegistry("node", "commander");
1744            registry.get("node")![`ts${versionMajorMinor}`] = "1.3.0-next.1";
1745            const logger = trackingLogger();
1746            const result = JsTyping.discoverTypings(host, logger.log, [app.path], getDirectoryPath(app.path as Path), emptySafeList, cache, { enable: true }, ["http", "commander"], registry, emptyOptions);
1747            assert.deepEqual(logger.finish(), [
1748                'Inferred typings from unresolved imports: ["node","commander"]',
1749                'Result: {"cachedTypingPaths":[],"newTypingNames":["node","commander"],"filesToWatch":["/a/bower_components","/a/node_modules","/a/oh_modules"]}',
1750            ]);
1751            assert.deepEqual(result.cachedTypingPaths, []);
1752            assert.deepEqual(result.newTypingNames, ["node", "commander"]);
1753        });
1754    });
1755
1756    describe("unittests:: tsserver:: typingsInstaller:: telemetry events", () => {
1757        it("should be received", () => {
1758            const f1 = {
1759                path: "/a/app.js",
1760                content: ""
1761            };
1762            const packageFile = {
1763                path: "/a/package.json",
1764                content: JSON.stringify({ dependencies: { commander: "1.0.0" } })
1765            };
1766            const cachePath = "/a/cache/";
1767            const commander = {
1768                path: cachePath + "node_modules/@types/commander/index.d.ts",
1769                content: "export let x: number"
1770            };
1771            const host = createServerHost([f1, packageFile]);
1772            let seenTelemetryEvent = false;
1773            const installer = new (class extends Installer {
1774                constructor() {
1775                    super(host, { globalTypingsCacheLocation: cachePath, typesRegistry: createTypesRegistry("commander") });
1776                }
1777                installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) {
1778                    const installedTypings = ["@types/commander"];
1779                    const typingFiles = [commander];
1780                    executeCommand(this, host, installedTypings, typingFiles, cb);
1781                }
1782                sendResponse(response: server.SetTypings | server.InvalidateCachedTypings | server.BeginInstallTypes | server.EndInstallTypes) {
1783                    if (response.kind === server.EventBeginInstallTypes) {
1784                        return;
1785                    }
1786                    if (response.kind === server.EventEndInstallTypes) {
1787                        assert.deepEqual(response.packagesToInstall, [typingsName("commander")]);
1788                        seenTelemetryEvent = true;
1789                        return;
1790                    }
1791                    super.sendResponse(response);
1792                }
1793            })();
1794            const projectService = createProjectService(host, { typingsInstaller: installer });
1795            projectService.openClientFile(f1.path);
1796
1797            installer.installAll(/*expectedCount*/ 1);
1798
1799            assert.isTrue(seenTelemetryEvent);
1800            host.checkTimeoutQueueLengthAndRun(2);
1801            checkNumberOfProjects(projectService, { inferredProjects: 1 });
1802            checkProjectActualFiles(projectService.inferredProjects[0], [f1.path, commander.path]);
1803        });
1804    });
1805
1806    describe("unittests:: tsserver:: typingsInstaller:: progress notifications", () => {
1807        it("should be sent for success", () => {
1808            const f1 = {
1809                path: "/a/app.js",
1810                content: ""
1811            };
1812            const packageFile = {
1813                path: "/a/package.json",
1814                content: JSON.stringify({ dependencies: { commander: "1.0.0" } })
1815            };
1816            const packageLockFile = {
1817                path: "/a/cache/package-lock.json",
1818                content: JSON.stringify({
1819                    dependencies: {
1820                        "@types/commander": {
1821                            version: "1.0.0"
1822                        }
1823                    }
1824                })
1825            };
1826            const cachePath = "/a/cache/";
1827            const commander = {
1828                path: cachePath + "node_modules/@types/commander/index.d.ts",
1829                content: "export let x: number"
1830            };
1831            const host = createServerHost([f1, packageFile, packageLockFile]);
1832            let beginEvent!: server.BeginInstallTypes;
1833            let endEvent!: server.EndInstallTypes;
1834            const installer = new (class extends Installer {
1835                constructor() {
1836                    super(host, { globalTypingsCacheLocation: cachePath, typesRegistry: createTypesRegistry("commander") });
1837                }
1838                installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) {
1839                    const installedTypings = ["@types/commander"];
1840                    const typingFiles = [commander];
1841                    executeCommand(this, host, installedTypings, typingFiles, cb);
1842                }
1843                sendResponse(response: server.SetTypings | server.InvalidateCachedTypings | server.BeginInstallTypes | server.EndInstallTypes) {
1844                    if (response.kind === server.EventBeginInstallTypes) {
1845                        beginEvent = response;
1846                        return;
1847                    }
1848                    if (response.kind === server.EventEndInstallTypes) {
1849                        endEvent = response;
1850                        return;
1851                    }
1852                    super.sendResponse(response);
1853                }
1854            })();
1855            const projectService = createProjectService(host, { typingsInstaller: installer });
1856            projectService.openClientFile(f1.path);
1857
1858            installer.installAll(/*expectedCount*/ 1);
1859
1860            assert.isTrue(!!beginEvent);
1861            assert.isTrue(!!endEvent);
1862            assert.isTrue(beginEvent.eventId === endEvent.eventId);
1863            assert.isTrue(endEvent.installSuccess);
1864            host.checkTimeoutQueueLengthAndRun(2);
1865            checkNumberOfProjects(projectService, { inferredProjects: 1 });
1866            checkProjectActualFiles(projectService.inferredProjects[0], [f1.path, commander.path]);
1867        });
1868
1869        it("should be sent for error", () => {
1870            const f1 = {
1871                path: "/a/app.js",
1872                content: ""
1873            };
1874            const packageFile = {
1875                path: "/a/package.json",
1876                content: JSON.stringify({ dependencies: { commander: "1.0.0" } })
1877            };
1878            const cachePath = "/a/cache/";
1879            const host = createServerHost([f1, packageFile]);
1880            let beginEvent: server.BeginInstallTypes | undefined;
1881            let endEvent: server.EndInstallTypes | undefined;
1882            const installer: Installer = new (class extends Installer {
1883                constructor() {
1884                    super(host, { globalTypingsCacheLocation: cachePath, typesRegistry: createTypesRegistry("commander") });
1885                }
1886                installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) {
1887                    executeCommand(this, host, "", [], cb);
1888                }
1889                sendResponse(response: server.SetTypings | server.InvalidateCachedTypings | server.BeginInstallTypes | server.EndInstallTypes) {
1890                    if (response.kind === server.EventBeginInstallTypes) {
1891                        beginEvent = response;
1892                        return;
1893                    }
1894                    if (response.kind === server.EventEndInstallTypes) {
1895                        endEvent = response;
1896                        return;
1897                    }
1898                    super.sendResponse(response);
1899                }
1900            })();
1901            const projectService = createProjectService(host, { typingsInstaller: installer });
1902            projectService.openClientFile(f1.path);
1903
1904            installer.installAll(/*expectedCount*/ 1);
1905
1906            assert.isTrue(!!beginEvent);
1907            assert.isTrue(!!endEvent);
1908            assert.isTrue(beginEvent!.eventId === endEvent!.eventId);
1909            assert.isFalse(endEvent!.installSuccess);
1910            checkNumberOfProjects(projectService, { inferredProjects: 1 });
1911            checkProjectActualFiles(projectService.inferredProjects[0], [f1.path]);
1912        });
1913    });
1914
1915    describe("unittests:: tsserver:: typingsInstaller:: npm installation command", () => {
1916        const npmPath = "npm", tsVersion = "2.9.0-dev.20180410";
1917        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"];
1918        const expectedCommands = [
1919            TI.getNpmCommandForInstallation(npmPath, tsVersion, packageNames, packageNames.length).command,
1920            TI.getNpmCommandForInstallation(npmPath, tsVersion, packageNames, packageNames.length - Math.ceil(packageNames.length / 2)).command
1921        ];
1922        it("works when the command is too long to install all packages at once", () => {
1923            const commands: string[] = [];
1924            const hasError = TI.installNpmPackages(npmPath, tsVersion, packageNames, command => {
1925                commands.push(command);
1926                return false;
1927            });
1928            assert.isFalse(hasError);
1929            assert.deepEqual(commands, expectedCommands, "commands");
1930        });
1931
1932        it("installs remaining packages when one of the partial command fails", () => {
1933            const commands: string[] = [];
1934            const hasError = TI.installNpmPackages(npmPath, tsVersion, packageNames, command => {
1935                commands.push(command);
1936                return commands.length === 1;
1937            });
1938            assert.isTrue(hasError);
1939            assert.deepEqual(commands, expectedCommands, "commands");
1940        });
1941    });
1942
1943    describe("unittests:: tsserver:: typingsInstaller:: recomputing resolutions of unresolved imports", () => {
1944        const globalTypingsCacheLocation = "/tmp";
1945        const appPath = "/a/b/app.js" as Path;
1946        const foooPath = "/a/b/node_modules/fooo/index.d.ts";
1947        function verifyResolvedModuleOfFooo(project: server.Project) {
1948            server.updateProjectIfDirty(project);
1949            const foooResolution = project.getLanguageService().getProgram()!.getSourceFileByPath(appPath)!.resolvedModules!.get("fooo", /*mode*/ undefined)!;
1950            assert.equal(foooResolution.resolvedFileName, foooPath);
1951            return foooResolution;
1952        }
1953
1954        function verifyUnresolvedImportResolutions(appContents: string, typingNames: string[], typingFiles: File[]) {
1955            const app: File = {
1956                path: appPath,
1957                content: `${appContents}import * as x from "fooo";`
1958            };
1959            const fooo: File = {
1960                path: foooPath,
1961                content: `export var x: string;`
1962            };
1963
1964            const host = createServerHost([app, fooo]);
1965            const installer = new (class extends Installer {
1966                constructor() {
1967                    super(host, { globalTypingsCacheLocation, typesRegistry: createTypesRegistry("foo") });
1968                }
1969                installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) {
1970                    executeCommand(this, host, typingNames, typingFiles, cb);
1971                }
1972            })();
1973            const projectService = createProjectService(host, { typingsInstaller: installer });
1974            projectService.openClientFile(app.path);
1975            projectService.checkNumberOfProjects({ inferredProjects: 1 });
1976
1977            const proj = projectService.inferredProjects[0];
1978            checkProjectActualFiles(proj, [app.path, fooo.path]);
1979            const foooResolution1 = verifyResolvedModuleOfFooo(proj);
1980
1981            installer.installAll(/*expectedCount*/ 1);
1982            host.checkTimeoutQueueLengthAndRun(2);
1983            checkProjectActualFiles(proj, typingFiles.map(f => f.path).concat(app.path, fooo.path));
1984            const foooResolution2 = verifyResolvedModuleOfFooo(proj);
1985            assert.strictEqual(foooResolution1, foooResolution2);
1986            projectService.applyChangesInOpenFiles(/*openFiles*/ undefined, arrayIterator([{
1987                fileName: app.path,
1988                changes: arrayIterator([{
1989                    span: { start: 0, length: 0 },
1990                    newText: `import * as bar from "bar";`
1991                }])
1992            }]));
1993            host.runQueuedTimeoutCallbacks(); // Update the graph
1994            // Update the typing
1995            host.checkTimeoutQueueLength(0);
1996            assert.isFalse(proj.resolutionCache.isFileWithInvalidatedNonRelativeUnresolvedImports(app.path as Path));
1997        }
1998
1999        it("correctly invalidate the resolutions with typing names", () => {
2000            verifyUnresolvedImportResolutions('import * as a from "foo";', ["foo"], [{
2001                path: `${globalTypingsCacheLocation}/node_modules/foo/index.d.ts`,
2002                content: "export function a(): void;"
2003            }]);
2004        });
2005
2006        it("correctly invalidate the resolutions with typing names that are trimmed", () => {
2007            const fooIndex: File = {
2008                path: `${globalTypingsCacheLocation}/node_modules/foo/index.d.ts`,
2009                content: "export function aa(): void;"
2010            };
2011            const fooAA: File = {
2012                path: `${globalTypingsCacheLocation}/node_modules/foo/a/a.d.ts`,
2013                content: "export function a (): void;"
2014            };
2015            const fooAB: File = {
2016                path: `${globalTypingsCacheLocation}/node_modules/foo/a/b.d.ts`,
2017                content: "export function b (): void;"
2018            };
2019            const fooAC: File = {
2020                path: `${globalTypingsCacheLocation}/node_modules/foo/a/c.d.ts`,
2021                content: "export function c (): void;"
2022            };
2023            verifyUnresolvedImportResolutions(`
2024                    import * as a from "foo/a/a";
2025                    import * as b from "foo/a/b";
2026                    import * as c from "foo/a/c";
2027            `, ["foo"], [fooIndex, fooAA, fooAB, fooAC]);
2028        });
2029
2030        it("should handle node core modules", () => {
2031            const file: TestFSWithWatch.File = {
2032                path: "/a/b/app.js",
2033                content: `// @ts-check
2034
2035const net = require("net");
2036const stream = require("stream");`
2037            };
2038            const nodeTyping: TestFSWithWatch.File = {
2039                path: `${globalTypingsCacheLocation}/node_modules/node/index.d.ts`,
2040                content: `
2041declare module "net" {
2042    export type n = number;
2043}
2044declare module "stream" {
2045    export type s = string;
2046}`,
2047            };
2048
2049            const host = createServerHost([file, libFile]);
2050            const installer = new (class extends Installer {
2051                constructor() {
2052                    super(host, { globalTypingsCacheLocation, typesRegistry: createTypesRegistry("node") });
2053                }
2054                installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) {
2055                    executeCommand(this, host, ["node"], [nodeTyping], cb);
2056                }
2057            })();
2058            const projectService = createProjectService(host, { typingsInstaller: installer });
2059            projectService.openClientFile(file.path);
2060            projectService.checkNumberOfProjects({ inferredProjects: 1 });
2061
2062            const proj = projectService.inferredProjects[0];
2063            checkProjectActualFiles(proj, [file.path, libFile.path]);
2064            installer.installAll(/*expectedCount*/ 1);
2065            host.checkTimeoutQueueLengthAndRun(2);
2066            checkProjectActualFiles(proj, [file.path, libFile.path, nodeTyping.path]);
2067            projectService.applyChangesInOpenFiles(
2068                /*openFiles*/ undefined,
2069                arrayIterator([{
2070                    fileName: file.path,
2071                    changes: arrayIterator([{
2072                        span: {
2073                            start: file.content.indexOf(`"stream"`) + 2,
2074                            length: 0
2075                        },
2076                        newText: " "
2077                    }])
2078                }]),
2079                /*closedFiles*/ undefined
2080            );
2081            // Below timeout Updates the typings to empty array because of "s tream" as unsresolved import
2082            // and schedules the update graph because of this.
2083            host.checkTimeoutQueueLengthAndRun(2);
2084            checkProjectActualFiles(proj, [file.path, libFile.path, nodeTyping.path]);
2085
2086            // Here, since typings dont change, there is no timeout scheduled
2087            host.checkTimeoutQueueLength(0);
2088            checkProjectActualFiles(proj, [file.path, libFile.path, nodeTyping.path]);
2089            projectService.applyChangesInOpenFiles(/*openFiles*/ undefined, arrayIterator([{
2090                fileName: file.path,
2091                changes: arrayIterator([{
2092                    span: { start: file.content.indexOf("const"), length: 0 },
2093                    newText: `const bar = require("bar");`
2094                }])
2095            }]));
2096            proj.updateGraph(); // Update the graph
2097            checkProjectActualFiles(proj, [file.path, libFile.path, nodeTyping.path]);
2098            // Update the typing
2099            host.checkTimeoutQueueLength(0);
2100            assert.isFalse(proj.resolutionCache.isFileWithInvalidatedNonRelativeUnresolvedImports(file.path as Path));
2101        });
2102    });
2103
2104    describe("unittests:: tsserver:: typingsInstaller:: tsserver:: with inferred Project", () => {
2105        it("when projectRootPath is provided", () => {
2106            const projects = "/users/username/projects";
2107            const projectRootPath = `${projects}/san2`;
2108            const file: File = {
2109                path: `${projectRootPath}/x.js`,
2110                content: "const aaaaaaav = 1;"
2111            };
2112
2113            const currentDirectory = `${projects}/anotherProject`;
2114            const packageJsonInCurrentDirectory: File = {
2115                path: `${currentDirectory}/package.json`,
2116                content: JSON.stringify({
2117                    devDependencies: {
2118                        pkgcurrentdirectory: ""
2119                    },
2120                })
2121            };
2122            const packageJsonOfPkgcurrentdirectory: File = {
2123                path: `${currentDirectory}/node_modules/pkgcurrentdirectory/package.json`,
2124                content: JSON.stringify({
2125                    name: "pkgcurrentdirectory",
2126                    main: "index.js",
2127                    typings: "index.d.ts"
2128                })
2129            };
2130            const indexOfPkgcurrentdirectory: File = {
2131                path: `${currentDirectory}/node_modules/pkgcurrentdirectory/index.d.ts`,
2132                content: "export function foo() { }"
2133            };
2134
2135            const typingsCache = `/users/username/Library/Caches/typescript/2.7`;
2136            const typingsCachePackageJson: File = {
2137                path: `${typingsCache}/package.json`,
2138                content: JSON.stringify({
2139                    devDependencies: {
2140                    },
2141                })
2142            };
2143            const typingsCachePackageLockJson: File = {
2144                path: `${typingsCache}/package-lock.json`,
2145                content: JSON.stringify({
2146                    dependencies: {
2147                    },
2148                })
2149            };
2150
2151            const files = [file, packageJsonInCurrentDirectory, packageJsonOfPkgcurrentdirectory, indexOfPkgcurrentdirectory, typingsCachePackageJson, typingsCachePackageLockJson];
2152            const host = createServerHost(files, { currentDirectory });
2153
2154            const typesRegistry = createTypesRegistry("pkgcurrentdirectory");
2155            const typingsInstaller = new TestTypingsInstaller(typingsCache, /*throttleLimit*/ 5, host, typesRegistry);
2156
2157            const projectService = createProjectService(host, { typingsInstaller });
2158
2159            projectService.setCompilerOptionsForInferredProjects({
2160                module: ModuleKind.CommonJS,
2161                target: ScriptTarget.ES2016,
2162                jsx: JsxEmit.Preserve,
2163                experimentalDecorators: true,
2164                allowJs: true,
2165                allowSyntheticDefaultImports: true,
2166                allowNonTsExtensions: true
2167            });
2168
2169            projectService.openClientFile(file.path, file.content, ScriptKind.JS, projectRootPath);
2170
2171            const project = projectService.inferredProjects[0];
2172            assert.isDefined(project);
2173
2174            // Ensure that we use result from types cache when getting ls
2175            assert.isDefined(project.getLanguageService());
2176
2177            // Verify that the pkgcurrentdirectory from the current directory isnt picked up
2178            checkProjectActualFiles(project, [file.path]);
2179        });
2180    });
2181}
2182