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