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