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