1namespace ts.projectSystem { 2 describe("unittests:: tsserver:: ConfiguredProjects", () => { 3 it("create configured project without file list", () => { 4 const configFile: File = { 5 path: "/a/b/tsconfig.json", 6 content: ` 7 { 8 "compilerOptions": {}, 9 "exclude": [ 10 "e" 11 ] 12 }` 13 }; 14 const file1: File = { 15 path: "/a/b/c/f1.ts", 16 content: "let x = 1" 17 }; 18 const file2: File = { 19 path: "/a/b/d/f2.ts", 20 content: "let y = 1" 21 }; 22 const file3: File = { 23 path: "/a/b/e/f3.ts", 24 content: "let z = 1" 25 }; 26 27 const host = createServerHost([configFile, libFile, file1, file2, file3]); 28 const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); 29 const { configFileName, configFileErrors } = projectService.openClientFile(file1.path); 30 31 assert(configFileName, "should find config file"); 32 assert.isTrue(!configFileErrors || configFileErrors.length === 0, `expect no errors in config file, got ${JSON.stringify(configFileErrors)}`); 33 34 baselineTsserverLogs("configuredProjects", "create configured project without file list", projectService); 35 }); 36 37 it("create configured project with the file list", () => { 38 const configFile: File = { 39 path: "/a/b/tsconfig.json", 40 content: ` 41 { 42 "compilerOptions": {}, 43 "include": ["*.ts"] 44 }` 45 }; 46 const file1: File = { 47 path: "/a/b/f1.ts", 48 content: "let x = 1" 49 }; 50 const file2: File = { 51 path: "/a/b/f2.ts", 52 content: "let y = 1" 53 }; 54 const file3: File = { 55 path: "/a/b/c/f3.ts", 56 content: "let z = 1" 57 }; 58 59 const host = createServerHost([configFile, libFile, file1, file2, file3]); 60 const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); 61 const { configFileName, configFileErrors } = projectService.openClientFile(file1.path); 62 63 assert(configFileName, "should find config file"); 64 assert.isTrue(!configFileErrors || configFileErrors.length === 0, `expect no errors in config file, got ${JSON.stringify(configFileErrors)}`); 65 66 baselineTsserverLogs("configuredProjects", "create configured project with the file list", projectService); 67 }); 68 69 it("add and then remove a config file in a folder with loose files", () => { 70 const configFile: File = { 71 path: `${tscWatch.projectRoot}/tsconfig.json`, 72 content: `{ 73 "files": ["commonFile1.ts"] 74 }` 75 }; 76 const commonFile1: File = { 77 path: `${tscWatch.projectRoot}/commonFile1.ts`, 78 content: "let x = 1" 79 }; 80 const commonFile2: File = { 81 path: `${tscWatch.projectRoot}/commonFile2.ts`, 82 content: "let y = 1" 83 }; 84 85 const host = createServerHost([libFile, commonFile1, commonFile2]); 86 87 const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); 88 projectService.openClientFile(commonFile1.path); 89 projectService.openClientFile(commonFile2.path); 90 91 // Add a tsconfig file 92 host.writeFile(configFile.path, configFile.content); 93 host.checkTimeoutQueueLengthAndRun(2); // load configured project from disk + ensureProjectsForOpenFiles 94 95 // remove the tsconfig file 96 host.deleteFile(configFile.path); 97 host.checkTimeoutQueueLengthAndRun(1); // Refresh inferred projects 98 99 baselineTsserverLogs("configuredProjects", "add and then remove a config file in a folder with loose files", projectService); 100 }); 101 102 it("add new files to a configured project without file list", () => { 103 const configFile: File = { 104 path: "/a/b/tsconfig.json", 105 content: `{}` 106 }; 107 const host = createServerHost([commonFile1, libFile, configFile]); 108 const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); 109 projectService.openClientFile(commonFile1.path); 110 111 // add a new ts file 112 host.writeFile(commonFile2.path, commonFile2.content); 113 host.checkTimeoutQueueLengthAndRun(2); 114 baselineTsserverLogs("configuredProjects", "add new files to a configured project without file list", projectService); 115 }); 116 117 it("should ignore non-existing files specified in the config file", () => { 118 const configFile: File = { 119 path: "/a/b/tsconfig.json", 120 content: `{ 121 "compilerOptions": {}, 122 "files": [ 123 "commonFile1.ts", 124 "commonFile3.ts" 125 ] 126 }` 127 }; 128 const host = createServerHost([commonFile1, commonFile2, configFile]); 129 const projectService = createProjectService(host); 130 projectService.openClientFile(commonFile1.path); 131 projectService.openClientFile(commonFile2.path); 132 133 checkNumberOfConfiguredProjects(projectService, 1); 134 const project = configuredProjectAt(projectService, 0); 135 checkProjectRootFiles(project, [commonFile1.path]); 136 checkNumberOfInferredProjects(projectService, 1); 137 }); 138 139 it("handle recreated files correctly", () => { 140 const configFile: File = { 141 path: "/a/b/tsconfig.json", 142 content: `{}` 143 }; 144 const host = createServerHost([commonFile1, commonFile2, configFile]); 145 const projectService = createProjectService(host); 146 projectService.openClientFile(commonFile1.path); 147 148 checkNumberOfConfiguredProjects(projectService, 1); 149 const project = configuredProjectAt(projectService, 0); 150 checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); 151 152 // delete commonFile2 153 host.deleteFile(commonFile2.path); 154 host.checkTimeoutQueueLengthAndRun(2); 155 checkProjectRootFiles(project, [commonFile1.path]); 156 157 // re-add commonFile2 158 host.writeFile(commonFile2.path, commonFile2.content); 159 host.checkTimeoutQueueLengthAndRun(2); 160 checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); 161 }); 162 163 it("files explicitly excluded in config file", () => { 164 const configFile: File = { 165 path: "/a/b/tsconfig.json", 166 content: `{ 167 "compilerOptions": {}, 168 "exclude": ["/a/c"] 169 }` 170 }; 171 const excludedFile1: File = { 172 path: "/a/c/excluedFile1.ts", 173 content: `let t = 1;` 174 }; 175 176 const host = createServerHost([commonFile1, commonFile2, excludedFile1, configFile]); 177 const projectService = createProjectService(host); 178 179 projectService.openClientFile(commonFile1.path); 180 checkNumberOfConfiguredProjects(projectService, 1); 181 const project = configuredProjectAt(projectService, 0); 182 checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); 183 projectService.openClientFile(excludedFile1.path); 184 checkNumberOfInferredProjects(projectService, 1); 185 }); 186 187 it("should properly handle module resolution changes in config file", () => { 188 const file1: File = { 189 path: "/a/b/file1.ts", 190 content: `import { T } from "module1";` 191 }; 192 const nodeModuleFile: File = { 193 path: "/a/b/node_modules/module1.ts", 194 content: `export interface T {}` 195 }; 196 const classicModuleFile: File = { 197 path: "/a/module1.ts", 198 content: `export interface T {}` 199 }; 200 const randomFile: File = { 201 path: "/a/file1.ts", 202 content: `export interface T {}` 203 }; 204 const configFile: File = { 205 path: "/a/b/tsconfig.json", 206 content: `{ 207 "compilerOptions": { 208 "moduleResolution": "node" 209 }, 210 "files": ["${file1.path}"] 211 }` 212 }; 213 const files = [file1, nodeModuleFile, classicModuleFile, configFile, randomFile]; 214 const host = createServerHost(files); 215 const projectService = createProjectService(host); 216 projectService.openClientFile(file1.path); 217 projectService.openClientFile(nodeModuleFile.path); 218 projectService.openClientFile(classicModuleFile.path); 219 220 checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 1 }); 221 const project = configuredProjectAt(projectService, 0); 222 const inferredProject0 = projectService.inferredProjects[0]; 223 checkProjectActualFiles(project, [file1.path, nodeModuleFile.path, configFile.path]); 224 checkProjectActualFiles(projectService.inferredProjects[0], [classicModuleFile.path]); 225 226 host.writeFile(configFile.path, `{ 227 "compilerOptions": { 228 "moduleResolution": "classic" 229 }, 230 "files": ["${file1.path}"] 231 }`); 232 host.checkTimeoutQueueLengthAndRun(2); 233 234 checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 2 }); // will not remove project 1 235 checkProjectActualFiles(project, [file1.path, classicModuleFile.path, configFile.path]); 236 assert.strictEqual(projectService.inferredProjects[0], inferredProject0); 237 assert.isTrue(projectService.inferredProjects[0].isOrphan()); 238 const inferredProject1 = projectService.inferredProjects[1]; 239 checkProjectActualFiles(projectService.inferredProjects[1], [nodeModuleFile.path]); 240 241 // Open random file and it will reuse first inferred project 242 projectService.openClientFile(randomFile.path); 243 checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 2 }); 244 checkProjectActualFiles(project, [file1.path, classicModuleFile.path, configFile.path]); 245 assert.strictEqual(projectService.inferredProjects[0], inferredProject0); 246 checkProjectActualFiles(projectService.inferredProjects[0], [randomFile.path]); // Reuses first inferred project 247 assert.strictEqual(projectService.inferredProjects[1], inferredProject1); 248 checkProjectActualFiles(projectService.inferredProjects[1], [nodeModuleFile.path]); 249 }); 250 251 it("should keep the configured project when the opened file is referenced by the project but not its root", () => { 252 const file1: File = { 253 path: "/a/b/main.ts", 254 content: "import { objA } from './obj-a';" 255 }; 256 const file2: File = { 257 path: "/a/b/obj-a.ts", 258 content: `export const objA = Object.assign({foo: "bar"}, {bar: "baz"});` 259 }; 260 const configFile: File = { 261 path: "/a/b/tsconfig.json", 262 content: `{ 263 "compilerOptions": { 264 "target": "es6" 265 }, 266 "files": [ "main.ts" ] 267 }` 268 }; 269 const host = createServerHost([file1, file2, configFile]); 270 const projectService = createProjectService(host); 271 projectService.openClientFile(file1.path); 272 projectService.closeClientFile(file1.path); 273 projectService.openClientFile(file2.path); 274 checkNumberOfConfiguredProjects(projectService, 1); 275 checkNumberOfInferredProjects(projectService, 0); 276 }); 277 278 it("should keep the configured project when the opened file is referenced by the project but not its root", () => { 279 const file1: File = { 280 path: "/a/b/main.ts", 281 content: "import { objA } from './obj-a';" 282 }; 283 const file2: File = { 284 path: "/a/b/obj-a.ts", 285 content: `export const objA = Object.assign({foo: "bar"}, {bar: "baz"});` 286 }; 287 const configFile: File = { 288 path: "/a/b/tsconfig.json", 289 content: `{ 290 "compilerOptions": { 291 "target": "es6" 292 }, 293 "files": [ "main.ts" ] 294 }` 295 }; 296 const host = createServerHost([file1, file2, configFile]); 297 const projectService = createProjectService(host); 298 projectService.openClientFile(file1.path); 299 projectService.closeClientFile(file1.path); 300 projectService.openClientFile(file2.path); 301 checkNumberOfConfiguredProjects(projectService, 1); 302 checkNumberOfInferredProjects(projectService, 0); 303 }); 304 305 it("should tolerate config file errors and still try to build a project", () => { 306 const configFile: File = { 307 path: "/a/b/tsconfig.json", 308 content: `{ 309 "compilerOptions": { 310 "target": "es6", 311 "allowAnything": true 312 }, 313 "someOtherProperty": {} 314 }` 315 }; 316 const host = createServerHost([commonFile1, commonFile2, libFile, configFile]); 317 const projectService = createProjectService(host); 318 projectService.openClientFile(commonFile1.path); 319 checkNumberOfConfiguredProjects(projectService, 1); 320 checkProjectRootFiles(configuredProjectAt(projectService, 0), [commonFile1.path, commonFile2.path]); 321 }); 322 323 it("should reuse same project if file is opened from the configured project that has no open files", () => { 324 const file1 = { 325 path: "/a/b/main.ts", 326 content: "let x =1;" 327 }; 328 const file2 = { 329 path: "/a/b/main2.ts", 330 content: "let y =1;" 331 }; 332 const configFile: File = { 333 path: "/a/b/tsconfig.json", 334 content: `{ 335 "compilerOptions": { 336 "target": "es6" 337 }, 338 "files": [ "main.ts", "main2.ts" ] 339 }` 340 }; 341 const host = createServerHost([file1, file2, configFile, libFile]); 342 const projectService = createProjectService(host, { useSingleInferredProject: true }); 343 projectService.openClientFile(file1.path); 344 checkNumberOfConfiguredProjects(projectService, 1); 345 const project = projectService.configuredProjects.get(configFile.path)!; 346 assert.isTrue(project.hasOpenRef()); // file1 347 348 projectService.closeClientFile(file1.path); 349 checkNumberOfConfiguredProjects(projectService, 1); 350 assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); 351 assert.isFalse(project.hasOpenRef()); // No open files 352 assert.isFalse(project.isClosed()); 353 354 projectService.openClientFile(file2.path); 355 checkNumberOfConfiguredProjects(projectService, 1); 356 assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); 357 assert.isTrue(project.hasOpenRef()); // file2 358 assert.isFalse(project.isClosed()); 359 }); 360 361 it("should not close configured project after closing last open file, but should be closed on next file open if its not the file from same project", () => { 362 const file1 = { 363 path: "/a/b/main.ts", 364 content: "let x =1;" 365 }; 366 const configFile: File = { 367 path: "/a/b/tsconfig.json", 368 content: `{ 369 "compilerOptions": { 370 "target": "es6" 371 }, 372 "files": [ "main.ts" ] 373 }` 374 }; 375 const host = createServerHost([file1, configFile, libFile]); 376 const projectService = createProjectService(host, { useSingleInferredProject: true }); 377 projectService.openClientFile(file1.path); 378 checkNumberOfConfiguredProjects(projectService, 1); 379 const project = projectService.configuredProjects.get(configFile.path)!; 380 assert.isTrue(project.hasOpenRef()); // file1 381 382 projectService.closeClientFile(file1.path); 383 checkNumberOfConfiguredProjects(projectService, 1); 384 assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); 385 assert.isFalse(project.hasOpenRef()); // No files 386 assert.isFalse(project.isClosed()); 387 388 projectService.openClientFile(libFile.path); 389 checkNumberOfConfiguredProjects(projectService, 0); 390 assert.isFalse(project.hasOpenRef()); // No files + project closed 391 assert.isTrue(project.isClosed()); 392 }); 393 394 it("open file become a part of configured project if it is referenced from root file", () => { 395 const file1 = { 396 path: `${tscWatch.projectRoot}/a/b/f1.ts`, 397 content: "export let x = 5" 398 }; 399 const file2 = { 400 path: `${tscWatch.projectRoot}/a/c/f2.ts`, 401 content: `import {x} from "../b/f1"` 402 }; 403 const file3 = { 404 path: `${tscWatch.projectRoot}/a/c/f3.ts`, 405 content: "export let y = 1" 406 }; 407 const configFile = { 408 path: `${tscWatch.projectRoot}/a/c/tsconfig.json`, 409 content: JSON.stringify({ compilerOptions: {}, files: ["f2.ts", "f3.ts"] }) 410 }; 411 412 const host = createServerHost([file1, file2, file3]); 413 const projectService = createProjectService(host); 414 415 projectService.openClientFile(file1.path); 416 checkNumberOfProjects(projectService, { inferredProjects: 1 }); 417 checkProjectActualFiles(projectService.inferredProjects[0], [file1.path]); 418 419 projectService.openClientFile(file3.path); 420 checkNumberOfProjects(projectService, { inferredProjects: 2 }); 421 checkProjectActualFiles(projectService.inferredProjects[0], [file1.path]); 422 checkProjectActualFiles(projectService.inferredProjects[1], [file3.path]); 423 424 host.writeFile(configFile.path, configFile.content); 425 host.checkTimeoutQueueLengthAndRun(2); // load configured project from disk + ensureProjectsForOpenFiles 426 checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 2 }); 427 checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, file3.path, configFile.path]); 428 assert.isTrue(projectService.inferredProjects[0].isOrphan()); 429 assert.isTrue(projectService.inferredProjects[1].isOrphan()); 430 }); 431 432 it("can correctly update configured project when set of root files has changed (new file on disk)", () => { 433 const file1 = { 434 path: "/a/b/f1.ts", 435 content: "let x = 1" 436 }; 437 const file2 = { 438 path: "/a/b/f2.ts", 439 content: "let y = 1" 440 }; 441 const configFile = { 442 path: "/a/b/tsconfig.json", 443 content: JSON.stringify({ compilerOptions: {} }) 444 }; 445 446 const host = createServerHost([file1, configFile]); 447 const projectService = createProjectService(host); 448 449 projectService.openClientFile(file1.path); 450 checkNumberOfProjects(projectService, { configuredProjects: 1 }); 451 checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, configFile.path]); 452 453 host.writeFile(file2.path, file2.content); 454 455 host.checkTimeoutQueueLengthAndRun(2); 456 457 checkNumberOfProjects(projectService, { configuredProjects: 1 }); 458 checkProjectRootFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path]); 459 }); 460 461 it("can correctly update configured project when set of root files has changed (new file in list of files)", () => { 462 const file1 = { 463 path: "/a/b/f1.ts", 464 content: "let x = 1" 465 }; 466 const file2 = { 467 path: "/a/b/f2.ts", 468 content: "let y = 1" 469 }; 470 const configFile = { 471 path: "/a/b/tsconfig.json", 472 content: JSON.stringify({ compilerOptions: {}, files: ["f1.ts"] }) 473 }; 474 475 const host = createServerHost([file1, file2, configFile]); 476 const projectService = createProjectService(host); 477 478 projectService.openClientFile(file1.path); 479 checkNumberOfProjects(projectService, { configuredProjects: 1 }); 480 checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, configFile.path]); 481 482 host.writeFile(configFile.path, JSON.stringify({ compilerOptions: {}, files: ["f1.ts", "f2.ts"] })); 483 484 checkNumberOfProjects(projectService, { configuredProjects: 1 }); 485 host.checkTimeoutQueueLengthAndRun(2); 486 checkProjectRootFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path]); 487 }); 488 489 it("can update configured project when set of root files was not changed", () => { 490 const file1 = { 491 path: "/a/b/f1.ts", 492 content: "let x = 1" 493 }; 494 const file2 = { 495 path: "/a/b/f2.ts", 496 content: "let y = 1" 497 }; 498 const configFile = { 499 path: "/a/b/tsconfig.json", 500 content: JSON.stringify({ compilerOptions: {}, files: ["f1.ts", "f2.ts"] }) 501 }; 502 503 const host = createServerHost([file1, file2, configFile]); 504 const projectService = createProjectService(host); 505 506 projectService.openClientFile(file1.path); 507 checkNumberOfProjects(projectService, { configuredProjects: 1 }); 508 checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, configFile.path]); 509 510 host.writeFile(configFile.path, JSON.stringify({ compilerOptions: { outFile: "out.js" }, files: ["f1.ts", "f2.ts"] })); 511 512 checkNumberOfProjects(projectService, { configuredProjects: 1 }); 513 checkProjectRootFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path]); 514 }); 515 516 it("Open ref of configured project when open file gets added to the project as part of configured file update", () => { 517 const file1: File = { 518 path: "/a/b/src/file1.ts", 519 content: "let x = 1;" 520 }; 521 const file2: File = { 522 path: "/a/b/src/file2.ts", 523 content: "let y = 1;" 524 }; 525 const file3: File = { 526 path: "/a/b/file3.ts", 527 content: "let z = 1;" 528 }; 529 const file4: File = { 530 path: "/a/file4.ts", 531 content: "let z = 1;" 532 }; 533 const configFile = { 534 path: "/a/b/tsconfig.json", 535 content: JSON.stringify({ files: ["src/file1.ts", "file3.ts"] }) 536 }; 537 538 const files = [file1, file2, file3, file4]; 539 const host = createServerHost(files.concat(configFile)); 540 const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); 541 542 projectService.openClientFile(file1.path); 543 projectService.openClientFile(file2.path); 544 projectService.openClientFile(file3.path); 545 projectService.openClientFile(file4.path); 546 547 const configProject1 = projectService.configuredProjects.get(configFile.path)!; 548 assert.isTrue(configProject1.hasOpenRef()); // file1 and file3 549 550 host.writeFile(configFile.path, "{}"); 551 host.runQueuedTimeoutCallbacks(); 552 553 assert.isTrue(configProject1.hasOpenRef()); // file1, file2, file3 554 assert.isTrue(projectService.inferredProjects[0].isOrphan()); 555 556 projectService.closeClientFile(file1.path); 557 projectService.closeClientFile(file2.path); 558 projectService.closeClientFile(file4.path); 559 560 assert.isTrue(configProject1.hasOpenRef()); // file3 561 assert.isTrue(projectService.inferredProjects[0].isOrphan()); 562 assert.isTrue(projectService.inferredProjects[1].isOrphan()); 563 564 projectService.openClientFile(file4.path); 565 assert.isTrue(configProject1.hasOpenRef()); // file3 566 const inferredProject4 = projectService.inferredProjects[0]; 567 checkProjectActualFiles(inferredProject4, [file4.path]); 568 569 projectService.closeClientFile(file3.path); 570 assert.isFalse(configProject1.hasOpenRef()); // No open files 571 const inferredProject5 = projectService.inferredProjects[0]; 572 checkProjectActualFiles(inferredProject4, [file4.path]); 573 assert.strictEqual(inferredProject5, inferredProject4); 574 575 const file5: File = { 576 path: "/file5.ts", 577 content: "let zz = 1;" 578 }; 579 host.writeFile(file5.path, file5.content); 580 projectService.testhost.baselineHost("File5 written"); 581 projectService.openClientFile(file5.path); 582 583 baselineTsserverLogs("configuredProjects", "Open ref of configured project when open file gets added to the project as part of configured file update", projectService); 584 }); 585 586 it("Open ref of configured project when open file gets added to the project as part of configured file update buts its open file references are all closed when the update happens", () => { 587 const file1: File = { 588 path: "/a/b/src/file1.ts", 589 content: "let x = 1;" 590 }; 591 const file2: File = { 592 path: "/a/b/src/file2.ts", 593 content: "let y = 1;" 594 }; 595 const file3: File = { 596 path: "/a/b/file3.ts", 597 content: "let z = 1;" 598 }; 599 const file4: File = { 600 path: "/a/file4.ts", 601 content: "let z = 1;" 602 }; 603 const configFile = { 604 path: "/a/b/tsconfig.json", 605 content: JSON.stringify({ files: ["src/file1.ts", "file3.ts"] }) 606 }; 607 608 const files = [file1, file2, file3]; 609 const hostFiles = files.concat(file4, configFile); 610 const host = createServerHost(hostFiles); 611 const projectService = createProjectService(host); 612 613 projectService.openClientFile(file1.path); 614 projectService.openClientFile(file2.path); 615 projectService.openClientFile(file3.path); 616 617 checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 1 }); 618 const configuredProject = projectService.configuredProjects.get(configFile.path)!; 619 assert.isTrue(configuredProject.hasOpenRef()); // file1 and file3 620 checkProjectActualFiles(configuredProject, [file1.path, file3.path, configFile.path]); 621 const inferredProject1 = projectService.inferredProjects[0]; 622 checkProjectActualFiles(inferredProject1, [file2.path]); 623 624 projectService.closeClientFile(file1.path); 625 projectService.closeClientFile(file3.path); 626 assert.isFalse(configuredProject.hasOpenRef()); // No files 627 628 host.writeFile(configFile.path, "{}"); 629 // Time out is not yet run so there is project update pending 630 assert.isTrue(configuredProject.hasOpenRef()); // Pending update and file2 might get into the project 631 632 projectService.openClientFile(file4.path); 633 634 checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 2 }); 635 assert.strictEqual(projectService.configuredProjects.get(configFile.path), configuredProject); 636 assert.isTrue(configuredProject.hasOpenRef()); // Pending update and F2 might get into the project 637 assert.strictEqual(projectService.inferredProjects[0], inferredProject1); 638 const inferredProject2 = projectService.inferredProjects[1]; 639 checkProjectActualFiles(inferredProject2, [file4.path]); 640 641 host.runQueuedTimeoutCallbacks(); 642 checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 2 }); 643 assert.strictEqual(projectService.configuredProjects.get(configFile.path), configuredProject); 644 assert.isTrue(configuredProject.hasOpenRef()); // file2 645 checkProjectActualFiles(configuredProject, [file1.path, file2.path, file3.path, configFile.path]); 646 assert.strictEqual(projectService.inferredProjects[0], inferredProject1); 647 assert.isTrue(inferredProject1.isOrphan()); 648 assert.strictEqual(projectService.inferredProjects[1], inferredProject2); 649 checkProjectActualFiles(inferredProject2, [file4.path]); 650 }); 651 652 it("files are properly detached when language service is disabled", () => { 653 const f1 = { 654 path: "/a/app.js", 655 content: "var x = 1" 656 }; 657 const f2 = { 658 path: "/a/largefile.js", 659 content: "" 660 }; 661 const f3 = { 662 path: "/a/lib.js", 663 content: "var x = 1" 664 }; 665 const config = { 666 path: "/a/tsconfig.json", 667 content: JSON.stringify({ compilerOptions: { allowJs: true } }) 668 }; 669 const host = createServerHost([f1, f2, f3, config]); 670 const originalGetFileSize = host.getFileSize; 671 host.getFileSize = (filePath: string) => 672 filePath === f2.path ? server.maxProgramSizeForNonTsFiles + 1 : originalGetFileSize.call(host, filePath); 673 674 const projectService = createProjectService(host); 675 projectService.openClientFile(f1.path); 676 projectService.checkNumberOfProjects({ configuredProjects: 1 }); 677 const project = projectService.configuredProjects.get(config.path)!; 678 assert.isTrue(project.hasOpenRef()); // f1 679 assert.isFalse(project.isClosed()); 680 681 projectService.closeClientFile(f1.path); 682 projectService.checkNumberOfProjects({ configuredProjects: 1 }); 683 assert.strictEqual(projectService.configuredProjects.get(config.path), project); 684 assert.isFalse(project.hasOpenRef()); // No files 685 assert.isFalse(project.isClosed()); 686 687 for (const f of [f1, f2, f3]) { 688 // All the script infos should be present and contain the project since it is still alive. 689 const scriptInfo = projectService.getScriptInfoForNormalizedPath(server.toNormalizedPath(f.path))!; 690 assert.equal(scriptInfo.containingProjects.length, 1, `expect 1 containing projects for '${f.path}'`); 691 assert.equal(scriptInfo.containingProjects[0], project, `expect configured project to be the only containing project for '${f.path}'`); 692 } 693 694 const f4 = { 695 path: "/aa.js", 696 content: "var x = 1" 697 }; 698 host.writeFile(f4.path, f4.content); 699 projectService.openClientFile(f4.path); 700 projectService.checkNumberOfProjects({ inferredProjects: 1 }); 701 assert.isFalse(project.hasOpenRef()); // No files 702 assert.isTrue(project.isClosed()); 703 704 for (const f of [f1, f2, f3]) { 705 // All the script infos should not be present since the project is closed and orphan script infos are collected 706 assert.isUndefined(projectService.getScriptInfoForNormalizedPath(server.toNormalizedPath(f.path))); 707 } 708 }); 709 710 it("syntactic features work even if language service is disabled", () => { 711 const f1 = { 712 path: "/a/app.js", 713 content: "let x = 1;" 714 }; 715 const f2 = { 716 path: "/a/largefile.js", 717 content: "" 718 }; 719 const config = { 720 path: "/a/jsconfig.json", 721 content: "{}" 722 }; 723 const host = createServerHost([f1, f2, config]); 724 const originalGetFileSize = host.getFileSize; 725 host.getFileSize = (filePath: string) => 726 filePath === f2.path ? server.maxProgramSizeForNonTsFiles + 1 : originalGetFileSize.call(host, filePath); 727 const { session, events } = createSessionWithEventTracking<server.ProjectLanguageServiceStateEvent>(host, server.ProjectLanguageServiceStateEvent); 728 session.executeCommand({ 729 seq: 0, 730 type: "request", 731 command: "open", 732 arguments: { file: f1.path } 733 } as protocol.OpenRequest); 734 735 const projectService = session.getProjectService(); 736 checkNumberOfProjects(projectService, { configuredProjects: 1 }); 737 const project = configuredProjectAt(projectService, 0); 738 assert.isFalse(project.languageServiceEnabled, "Language service enabled"); 739 assert.equal(events.length, 1, "should receive event"); 740 assert.equal(events[0].data.project, project, "project name"); 741 assert.isFalse(events[0].data.languageServiceEnabled, "Language service state"); 742 743 const options = projectService.getFormatCodeOptions(f1.path as server.NormalizedPath); 744 const edits = project.getLanguageService().getFormattingEditsForDocument(f1.path, options); 745 assert.deepEqual(edits, [{ span: createTextSpan(/*start*/ 7, /*length*/ 3), newText: " " }]); 746 }); 747 748 it("when multiple projects are open, detects correct default project", () => { 749 const barConfig: File = { 750 path: `${tscWatch.projectRoot}/bar/tsconfig.json`, 751 content: JSON.stringify({ 752 include: ["index.ts"], 753 compilerOptions: { 754 lib: ["dom", "es2017"] 755 } 756 }) 757 }; 758 const barIndex: File = { 759 path: `${tscWatch.projectRoot}/bar/index.ts`, 760 content: ` 761export function bar() { 762 console.log("hello world"); 763}` 764 }; 765 const fooConfig: File = { 766 path: `${tscWatch.projectRoot}/foo/tsconfig.json`, 767 content: JSON.stringify({ 768 include: ["index.ts"], 769 compilerOptions: { 770 lib: ["es2017"] 771 } 772 }) 773 }; 774 const fooIndex: File = { 775 path: `${tscWatch.projectRoot}/foo/index.ts`, 776 content: ` 777import { bar } from "bar"; 778bar();` 779 }; 780 const barSymLink: SymLink = { 781 path: `${tscWatch.projectRoot}/foo/node_modules/bar`, 782 symLink: `${tscWatch.projectRoot}/bar` 783 }; 784 785 const lib2017: File = { 786 path: `${getDirectoryPath(libFile.path)}/lib.es2017.d.ts`, 787 content: libFile.content 788 }; 789 const libDom: File = { 790 path: `${getDirectoryPath(libFile.path)}/lib.dom.d.ts`, 791 content: ` 792declare var console: { 793 log(...args: any[]): void; 794};` 795 }; 796 const host = createServerHost([barConfig, barIndex, fooConfig, fooIndex, barSymLink, lib2017, libDom]); 797 const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); 798 openFilesForSession([fooIndex, barIndex], session); 799 verifyGetErrRequest({ session, host, files: [barIndex, fooIndex] }); 800 baselineTsserverLogs("configuredProjects", "when multiple projects are open detects correct default project", session); 801 }); 802 803 it("when file name starts with ^", () => { 804 const file: File = { 805 path: `${tscWatch.projectRoot}/file.ts`, 806 content: "const x = 10;" 807 }; 808 const app: File = { 809 path: `${tscWatch.projectRoot}/^app.ts`, 810 content: "const y = 10;" 811 }; 812 const tsconfig: File = { 813 path: `${tscWatch.projectRoot}/tsconfig.json`, 814 content: "{}" 815 }; 816 const host = createServerHost([file, app, tsconfig, libFile]); 817 const service = createProjectService(host); 818 service.openClientFile(file.path); 819 }); 820 821 describe("when creating new file", () => { 822 const foo: File = { 823 path: `${tscWatch.projectRoot}/src/foo.ts`, 824 content: "export function foo() { }" 825 }; 826 const bar: File = { 827 path: `${tscWatch.projectRoot}/src/bar.ts`, 828 content: "export function bar() { }" 829 }; 830 const config: File = { 831 path: `${tscWatch.projectRoot}/tsconfig.json`, 832 content: JSON.stringify({ 833 include: ["./src"] 834 }) 835 }; 836 const fooBar: File = { 837 path: `${tscWatch.projectRoot}/src/sub/fooBar.ts`, 838 content: "export function fooBar() { }" 839 }; 840 function verifySessionWorker({ withExclude, openFileBeforeCreating }: VerifySession, errorOnNewFileBeforeOldFile: boolean) { 841 const host = createServerHost([ 842 foo, bar, libFile, { path: `${tscWatch.projectRoot}/src/sub` }, 843 withExclude ? 844 { 845 path: config.path, 846 content: JSON.stringify({ 847 include: ["./src"], 848 exclude: ["./src/sub"] 849 }) 850 } : 851 config 852 ]); 853 const session = createSession(host, { 854 canUseEvents: true, 855 logger: createLoggerWithInMemoryLogs(host), 856 }); 857 session.executeCommandSeq<protocol.OpenRequest>({ 858 command: protocol.CommandTypes.Open, 859 arguments: { 860 file: foo.path, 861 fileContent: foo.content, 862 projectRootPath: tscWatch.projectRoot 863 } 864 }); 865 if (!openFileBeforeCreating) { 866 host.writeFile(fooBar.path, fooBar.content); 867 } 868 session.executeCommandSeq<protocol.OpenRequest>({ 869 command: protocol.CommandTypes.Open, 870 arguments: { 871 file: fooBar.path, 872 fileContent: fooBar.content, 873 projectRootPath: tscWatch.projectRoot 874 } 875 }); 876 if (openFileBeforeCreating) { 877 host.writeFile(fooBar.path, fooBar.content); 878 } 879 verifyGetErrRequest({ 880 session, 881 host, 882 files: errorOnNewFileBeforeOldFile ? 883 [fooBar, foo] : 884 [foo, fooBar], 885 existingTimeouts: withExclude ? 0 : 2 886 }); 887 baselineTsserverLogs("configuredProjects", `creating new file and then open it ${openFileBeforeCreating ? "before" : "after"} watcher is invoked, ask errors on it ${errorOnNewFileBeforeOldFile ? "before" : "after"} old one${withExclude ? " without file being in config" : ""}`, session); 888 } 889 interface VerifySession { 890 withExclude?: boolean; 891 openFileBeforeCreating: boolean; 892 } 893 function verifySession(input: VerifySession) { 894 it("when error on new file are asked before old one", () => { 895 verifySessionWorker(input, /*errorOnNewFileBeforeOldFile*/ true); 896 }); 897 898 it("when error on new file are asked after old one", () => { 899 verifySessionWorker(input, /*errorOnNewFileBeforeOldFile*/ false); 900 }); 901 } 902 describe("when new file creation directory watcher is invoked before file is opened in editor", () => { 903 verifySession({ 904 openFileBeforeCreating: false, 905 }); 906 describe("when new file is excluded from config", () => { 907 verifySession({ 908 withExclude: true, 909 openFileBeforeCreating: false, 910 }); 911 }); 912 }); 913 914 describe("when new file creation directory watcher is invoked after file is opened in editor", () => { 915 verifySession({ 916 openFileBeforeCreating: true, 917 }); 918 describe("when new file is excluded from config", () => { 919 verifySession({ 920 withExclude: true, 921 openFileBeforeCreating: true, 922 }); 923 }); 924 }); 925 }); 926 927 it("when default configured project does not contain the file", () => { 928 const barConfig: File = { 929 path: `${tscWatch.projectRoot}/bar/tsconfig.json`, 930 content: "{}" 931 }; 932 const barIndex: File = { 933 path: `${tscWatch.projectRoot}/bar/index.ts`, 934 content: `import {foo} from "../foo/lib"; 935foo();` 936 }; 937 const fooBarConfig: File = { 938 path: `${tscWatch.projectRoot}/foobar/tsconfig.json`, 939 content: barConfig.path 940 }; 941 const fooBarIndex: File = { 942 path: `${tscWatch.projectRoot}/foobar/index.ts`, 943 content: barIndex.content 944 }; 945 const fooConfig: File = { 946 path: `${tscWatch.projectRoot}/foo/tsconfig.json`, 947 content: JSON.stringify({ 948 include: ["index.ts"], 949 compilerOptions: { 950 declaration: true, 951 outDir: "lib" 952 } 953 }) 954 }; 955 const fooIndex: File = { 956 path: `${tscWatch.projectRoot}/foo/index.ts`, 957 content: `export function foo() {}` 958 }; 959 const host = createServerHost([barConfig, barIndex, fooBarConfig, fooBarIndex, fooConfig, fooIndex, libFile]); 960 tscWatch.ensureErrorFreeBuild(host, [fooConfig.path]); 961 const fooDts = `${tscWatch.projectRoot}/foo/lib/index.d.ts`; 962 assert.isTrue(host.fileExists(fooDts)); 963 const session = createSession(host); 964 const service = session.getProjectService(); 965 service.openClientFile(barIndex.path); 966 checkProjectActualFiles(service.configuredProjects.get(barConfig.path)!, [barIndex.path, fooDts, libFile.path, barConfig.path]); 967 service.openClientFile(fooBarIndex.path); 968 checkProjectActualFiles(service.configuredProjects.get(fooBarConfig.path)!, [fooBarIndex.path, fooDts, libFile.path, fooBarConfig.path]); 969 service.openClientFile(fooIndex.path); 970 checkProjectActualFiles(service.configuredProjects.get(fooConfig.path)!, [fooIndex.path, libFile.path, fooConfig.path]); 971 service.openClientFile(fooDts); 972 session.executeCommandSeq<protocol.GetApplicableRefactorsRequest>({ 973 command: protocol.CommandTypes.GetApplicableRefactors, 974 arguments: { 975 file: fooDts, 976 startLine: 1, 977 startOffset: 1, 978 endLine: 1, 979 endOffset: 1 980 } 981 }); 982 assert.equal(service.tryGetDefaultProjectForFile(server.toNormalizedPath(fooDts)), service.configuredProjects.get(barConfig.path)); 983 }); 984 985 describe("watches extended config files", () => { 986 function getService(additionalFiles?: File[]) { 987 const alphaExtendedConfig: File = { 988 path: `${tscWatch.projectRoot}/extended/alpha.tsconfig.json`, 989 content: "{}" 990 }; 991 const bravoExtendedConfig: File = { 992 path: `${tscWatch.projectRoot}/extended/bravo.tsconfig.json`, 993 content: JSON.stringify({ 994 extends: "./alpha.tsconfig.json" 995 }) 996 }; 997 const aConfig: File = { 998 path: `${tscWatch.projectRoot}/a/tsconfig.json`, 999 content: JSON.stringify({ 1000 extends: "../extended/alpha.tsconfig.json", 1001 files: ["a.ts"] 1002 }) 1003 }; 1004 const aFile: File = { 1005 path: `${tscWatch.projectRoot}/a/a.ts`, 1006 content: `let a = 1;` 1007 }; 1008 const bConfig: File = { 1009 path: `${tscWatch.projectRoot}/b/tsconfig.json`, 1010 content: JSON.stringify({ 1011 extends: "../extended/bravo.tsconfig.json", 1012 files: ["b.ts"] 1013 }) 1014 }; 1015 const bFile: File = { 1016 path: `${tscWatch.projectRoot}/b/b.ts`, 1017 content: `let b = 1;` 1018 }; 1019 1020 const host = createServerHost([alphaExtendedConfig, aConfig, aFile, bravoExtendedConfig, bConfig, bFile, ...(additionalFiles || emptyArray)]); 1021 const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); 1022 return { host, projectService, aFile, bFile, aConfig, bConfig, alphaExtendedConfig, bravoExtendedConfig }; 1023 } 1024 1025 it("should watch the extended configs of multiple projects", () => { 1026 const { host, projectService, aFile, bFile, bConfig, alphaExtendedConfig, bravoExtendedConfig } = getService(); 1027 1028 projectService.openClientFile(aFile.path); 1029 projectService.openClientFile(bFile.path); 1030 1031 host.writeFile(alphaExtendedConfig.path, JSON.stringify({ 1032 compilerOptions: { 1033 strict: true 1034 } 1035 })); 1036 host.checkTimeoutQueueLengthAndRun(3); 1037 1038 host.writeFile(bravoExtendedConfig.path, JSON.stringify({ 1039 extends: "./alpha.tsconfig.json", 1040 compilerOptions: { 1041 strict: false 1042 } 1043 })); 1044 host.checkTimeoutQueueLengthAndRun(2); 1045 1046 host.writeFile(bConfig.path, JSON.stringify({ 1047 extends: "../extended/alpha.tsconfig.json", 1048 })); 1049 host.checkTimeoutQueueLengthAndRun(2); 1050 1051 host.writeFile(alphaExtendedConfig.path, "{}"); 1052 host.checkTimeoutQueueLengthAndRun(3); 1053 baselineTsserverLogs("configuredProjects", "should watch the extended configs of multiple projects", projectService); 1054 }); 1055 1056 it("should stop watching the extended configs of closed projects", () => { 1057 const dummy: File = { 1058 path: `${tscWatch.projectRoot}/dummy/dummy.ts`, 1059 content: `let dummy = 1;` 1060 }; 1061 const dummyConfig: File = { 1062 path: `${tscWatch.projectRoot}/dummy/tsconfig.json`, 1063 content: "{}" 1064 }; 1065 const { projectService, aFile, bFile } = getService([dummy, dummyConfig]); 1066 1067 projectService.openClientFile(aFile.path); 1068 projectService.openClientFile(bFile.path); 1069 projectService.openClientFile(dummy.path); 1070 1071 projectService.closeClientFile(bFile.path); 1072 projectService.closeClientFile(dummy.path); 1073 projectService.openClientFile(dummy.path); 1074 1075 1076 projectService.closeClientFile(aFile.path); 1077 projectService.closeClientFile(dummy.path); 1078 projectService.openClientFile(dummy.path); 1079 baselineTsserverLogs("configuredProjects", "should stop watching the extended configs of closed projects", projectService); 1080 }); 1081 }); 1082 }); 1083 1084 describe("unittests:: tsserver:: ConfiguredProjects:: non-existing directories listed in config file input array", () => { 1085 it("should be tolerated without crashing the server", () => { 1086 const configFile = { 1087 path: "/a/b/tsconfig.json", 1088 content: `{ 1089 "compilerOptions": {}, 1090 "include": ["app/*", "test/**/*", "something"] 1091 }` 1092 }; 1093 const file1 = { 1094 path: "/a/b/file1.ts", 1095 content: "let t = 10;" 1096 }; 1097 1098 const host = createServerHost([file1, configFile]); 1099 const projectService = createProjectService(host); 1100 projectService.openClientFile(file1.path); 1101 host.runQueuedTimeoutCallbacks(); 1102 1103 // Since file1 refers to config file as the default project, it needs to be kept alive 1104 checkNumberOfProjects(projectService, { inferredProjects: 1, configuredProjects: 1 }); 1105 const inferredProject = projectService.inferredProjects[0]; 1106 assert.isTrue(inferredProject.containsFile(file1.path as server.NormalizedPath)); 1107 assert.isFalse(projectService.configuredProjects.get(configFile.path)!.containsFile(file1.path as server.NormalizedPath)); 1108 }); 1109 1110 it("should be able to handle @types if input file list is empty", () => { 1111 const f = { 1112 path: "/a/app.ts", 1113 content: "let x = 1" 1114 }; 1115 const config = { 1116 path: "/a/tsconfig.json", 1117 content: JSON.stringify({ 1118 compiler: {}, 1119 files: [] 1120 }) 1121 }; 1122 const t1 = { 1123 path: "/a/node_modules/@types/typings/index.d.ts", 1124 content: `export * from "./lib"` 1125 }; 1126 const t2 = { 1127 path: "/a/node_modules/@types/typings/lib.d.ts", 1128 content: `export const x: number` 1129 }; 1130 const host = createServerHost([f, config, t1, t2], { currentDirectory: getDirectoryPath(f.path) }); 1131 const projectService = createProjectService(host); 1132 1133 projectService.openClientFile(f.path); 1134 // Since f refers to config file as the default project, it needs to be kept alive 1135 projectService.checkNumberOfProjects({ configuredProjects: 1, inferredProjects: 1 }); 1136 }); 1137 1138 it("should tolerate invalid include files that start in subDirectory", () => { 1139 const f = { 1140 path: `${tscWatch.projectRoot}/src/server/index.ts`, 1141 content: "let x = 1" 1142 }; 1143 const config = { 1144 path: `${tscWatch.projectRoot}/src/server/tsconfig.json`, 1145 content: JSON.stringify({ 1146 compiler: { 1147 module: "commonjs", 1148 outDir: "../../build" 1149 }, 1150 include: [ 1151 "../src/**/*.ts" 1152 ] 1153 }) 1154 }; 1155 const host = createServerHost([f, config, libFile], { useCaseSensitiveFileNames: true }); 1156 const projectService = createProjectService(host); 1157 1158 projectService.openClientFile(f.path); 1159 // Since f refers to config file as the default project, it needs to be kept alive 1160 projectService.checkNumberOfProjects({ configuredProjects: 1, inferredProjects: 1 }); 1161 }); 1162 1163 it("Changed module resolution reflected when specifying files list", () => { 1164 const file1: File = { 1165 path: "/a/b/file1.ts", 1166 content: 'import classc from "file2"' 1167 }; 1168 const file2a: File = { 1169 path: "/a/file2.ts", 1170 content: "export classc { method2a() { return 10; } }" 1171 }; 1172 const file2: File = { 1173 path: "/a/b/file2.ts", 1174 content: "export classc { method2() { return 10; } }" 1175 }; 1176 const configFile: File = { 1177 path: "/a/b/tsconfig.json", 1178 content: JSON.stringify({ files: [file1.path], compilerOptions: { module: "amd" } }) 1179 }; 1180 const files = [file1, file2a, configFile, libFile]; 1181 const host = createServerHost(files); 1182 const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); 1183 projectService.openClientFile(file1.path); 1184 1185 host.writeFile(file2.path, file2.content); 1186 host.runQueuedTimeoutCallbacks(); // Scheduled invalidation of resolutions 1187 host.runQueuedTimeoutCallbacks(); // Actual update 1188 1189 // On next file open the files file2a should be closed and not watched any more 1190 projectService.openClientFile(file2.path); 1191 baselineTsserverLogs("configuredProjects", "changed module resolution reflected when specifying files list", projectService); 1192 }); 1193 1194 it("Failed lookup locations uses parent most node_modules directory", () => { 1195 const root = "/user/username/rootfolder"; 1196 const file1: File = { 1197 path: "/a/b/src/file1.ts", 1198 content: 'import { classc } from "module1"' 1199 }; 1200 const module1: File = { 1201 path: "/a/b/node_modules/module1/index.d.ts", 1202 content: `import { class2 } from "module2"; 1203 export classc { method2a(): class2; }` 1204 }; 1205 const module2: File = { 1206 path: "/a/b/node_modules/module2/index.d.ts", 1207 content: "export class2 { method2() { return 10; } }" 1208 }; 1209 const module3: File = { 1210 path: "/a/b/node_modules/module/node_modules/module3/index.d.ts", 1211 content: "export class3 { method2() { return 10; } }" 1212 }; 1213 const configFile: File = { 1214 path: "/a/b/src/tsconfig.json", 1215 content: JSON.stringify({ files: ["file1.ts"] }) 1216 }; 1217 const nonLibFiles = [file1, module1, module2, module3, configFile]; 1218 nonLibFiles.forEach(f => f.path = root + f.path); 1219 const files = nonLibFiles.concat(libFile); 1220 const host = createServerHost(files); 1221 const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); 1222 projectService.openClientFile(file1.path); 1223 baselineTsserverLogs("configuredProjects", "failed lookup locations uses parent most node_modules directory", projectService); 1224 }); 1225 }); 1226 1227 describe("unittests:: tsserver:: ConfiguredProjects:: when reading tsconfig file fails", () => { 1228 it("should be tolerated without crashing the server", () => { 1229 const configFile = { 1230 path: `${tscWatch.projectRoot}/tsconfig.json`, 1231 content: "" 1232 }; 1233 const file1 = { 1234 path: `${tscWatch.projectRoot}/file1.ts`, 1235 content: "let t = 10;" 1236 }; 1237 1238 const host = createServerHost([file1, libFile, configFile]); 1239 const { session, events } = createSessionWithEventTracking<server.ConfigFileDiagEvent>(host, server.ConfigFileDiagEvent); 1240 const originalReadFile = host.readFile; 1241 host.readFile = f => { 1242 return f === configFile.path ? 1243 undefined : 1244 originalReadFile.call(host, f); 1245 }; 1246 openFilesForSession([file1], session); 1247 1248 assert.deepEqual(events, [{ 1249 eventName: server.ConfigFileDiagEvent, 1250 data: { 1251 triggerFile: file1.path, 1252 configFileName: configFile.path, 1253 diagnostics: [ 1254 createCompilerDiagnostic(Diagnostics.Cannot_read_file_0, configFile.path) 1255 ] 1256 } 1257 }]); 1258 }); 1259 }); 1260} 1261