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