1namespace ts.tscWatch { 2 describe("unittests:: tsc-watch:: program updates", () => { 3 const scenario = "programUpdates"; 4 const configFilePath = "/a/b/tsconfig.json"; 5 const configFile: File = { 6 path: configFilePath, 7 content: `{}` 8 }; 9 verifyTscWatch({ 10 scenario, 11 subScenario: "create watch without config file", 12 commandLineArgs: ["-w", "/a/b/c/app.ts"], 13 sys: () => { 14 const appFile: File = { 15 path: "/a/b/c/app.ts", 16 content: ` 17 import {f} from "./module" 18 console.log(f) 19 ` 20 }; 21 22 const moduleFile: File = { 23 path: "/a/b/c/module.d.ts", 24 content: `export let x: number` 25 }; 26 return createWatchedSystem([appFile, moduleFile, libFile]); 27 }, 28 changes: emptyArray 29 }); 30 31 verifyTscWatch({ 32 scenario, 33 subScenario: "can handle tsconfig file name with difference casing", 34 commandLineArgs: ["-w", "-p", "/A/B/tsconfig.json"], 35 sys: () => { 36 const f1 = { 37 path: "/a/b/app.ts", 38 content: "let x = 1" 39 }; 40 const config = { 41 path: configFilePath, 42 content: JSON.stringify({ 43 include: ["app.ts"] 44 }) 45 }; 46 return createWatchedSystem([f1, libFile, config], { useCaseSensitiveFileNames: false }); 47 }, 48 changes: emptyArray 49 }); 50 51 verifyTscWatch({ 52 scenario, 53 subScenario: "create configured project without file list", 54 commandLineArgs: ["-w", "-p", configFilePath], 55 sys: () => { 56 const configFile: File = { 57 path: configFilePath, 58 content: ` 59 { 60 "compilerOptions": {}, 61 "exclude": [ 62 "e" 63 ] 64 }` 65 }; 66 const file1: File = { 67 path: "/a/b/c/f1.ts", 68 content: "let x = 1" 69 }; 70 const file2: File = { 71 path: "/a/b/d/f2.ts", 72 content: "let y = 1" 73 }; 74 const file3: File = { 75 path: "/a/b/e/f3.ts", 76 content: "let z = 1" 77 }; 78 return createWatchedSystem([configFile, libFile, file1, file2, file3]); 79 }, 80 changes: emptyArray 81 }); 82 83 verifyTscWatch({ 84 scenario, 85 subScenario: "add new files to a configured program without file list", 86 commandLineArgs: ["-w", "-p", configFilePath], 87 sys: () => createWatchedSystem([commonFile1, libFile, configFile]), 88 changes: [ 89 { 90 caption: "Create commonFile2", 91 change: sys => sys.writeFile(commonFile2.path, commonFile2.content), 92 timeouts: checkSingleTimeoutQueueLengthAndRun, 93 } 94 ] 95 }); 96 97 verifyTscWatch({ 98 scenario, 99 subScenario: "should ignore non-existing files specified in the config file", 100 commandLineArgs: ["-w", "-p", configFilePath], 101 sys: () => { 102 const configFile: File = { 103 path: configFilePath, 104 content: `{ 105 "compilerOptions": {}, 106 "files": [ 107 "commonFile1.ts", 108 "commonFile3.ts" 109 ] 110 }` 111 }; 112 return createWatchedSystem([commonFile1, commonFile2, libFile, configFile]); 113 }, 114 changes: emptyArray 115 }); 116 117 verifyTscWatch({ 118 scenario, 119 subScenario: "handle recreated files correctly", 120 commandLineArgs: ["-w", "-p", configFilePath, "--explainFiles"], 121 sys: () => { 122 return createWatchedSystem([libFile, commonFile1, commonFile2, configFile]); 123 }, 124 changes: [ 125 { 126 caption: "change file to ensure signatures are updated", 127 change: sys => sys.appendFile(commonFile2.path, ";let xy = 10;"), 128 timeouts: checkSingleTimeoutQueueLengthAndRun, 129 }, 130 { 131 caption: "delete file2", 132 change: sys => sys.deleteFile(commonFile2.path), 133 timeouts: checkSingleTimeoutQueueLengthAndRun, 134 }, 135 { 136 caption: "recreate file2", 137 change: sys => sys.writeFile(commonFile2.path, commonFile2.content), 138 timeouts: checkSingleTimeoutQueueLengthAndRun, 139 } 140 ] 141 }); 142 143 verifyTscWatch({ 144 scenario, 145 subScenario: "handles the missing files - that were added to program because they were added with tripleSlashRefs", 146 commandLineArgs: ["-w", "/a/b/commonFile1.ts"], 147 sys: () => { 148 const file1: File = { 149 path: commonFile1.path, 150 content: `/// <reference path="commonFile2.ts"/> 151 let x = y` 152 }; 153 return createWatchedSystem([file1, libFile]); 154 }, 155 changes: [ 156 { 157 caption: "create file2", 158 change: sys => sys.writeFile(commonFile2.path, commonFile2.content), 159 timeouts: checkSingleTimeoutQueueLengthAndRun, 160 } 161 ] 162 }); 163 164 verifyTscWatch({ 165 scenario, 166 subScenario: "should reflect change in config file", 167 commandLineArgs: ["-w", "-p", configFilePath, "--explainFiles"], 168 sys: () => { 169 const configFile: File = { 170 path: configFilePath, 171 content: `{ 172 "compilerOptions": {}, 173 "files": ["${commonFile1.path}", "${commonFile2.path}"] 174 }` 175 }; 176 return createWatchedSystem([libFile, commonFile1, commonFile2, configFile]); 177 }, 178 changes: [ 179 { 180 caption: "change file to ensure signatures are updated", 181 change: sys => sys.appendFile(commonFile2.path, ";let xy = 10;"), 182 timeouts: checkSingleTimeoutQueueLengthAndRun, 183 }, 184 { 185 caption: "Change config", 186 change: sys => sys.writeFile(configFilePath, `{ 187 "compilerOptions": {}, 188 "files": ["${commonFile1.path}"] 189 }`), 190 timeouts: checkSingleTimeoutQueueLengthAndRun, 191 } 192 ] 193 }); 194 195 verifyTscWatch({ 196 scenario, 197 subScenario: "works correctly when config file is changed but its content havent", 198 commandLineArgs: ["-w", "-p", configFilePath], 199 sys: () => { 200 const configFile: File = { 201 path: configFilePath, 202 content: `{ 203 "compilerOptions": {}, 204 "files": ["${commonFile1.path}", "${commonFile2.path}"] 205 }` 206 }; 207 return createWatchedSystem([libFile, commonFile1, commonFile2, configFile]); 208 }, 209 changes: [ 210 { 211 caption: "Modify config without changing content", 212 change: sys => sys.modifyFile(configFilePath, `{ 213 "compilerOptions": {}, 214 "files": ["${commonFile1.path}", "${commonFile2.path}"] 215 }`), 216 timeouts: checkSingleTimeoutQueueLengthAndRun, 217 } 218 ] 219 }); 220 221 verifyTscWatch({ 222 scenario, 223 subScenario: "Updates diagnostics when '--noUnusedLabels' changes", 224 commandLineArgs: ["-w", "-p", "/tsconfig.json"], 225 sys: () => { 226 const aTs: File = { 227 path: "/a.ts", 228 content: "label: while (1) {}" 229 }; 230 const tsconfig: File = { 231 path: "/tsconfig.json", 232 content: JSON.stringify({ 233 compilerOptions: { allowUnusedLabels: true } 234 }) 235 }; 236 return createWatchedSystem([libFile, aTs, tsconfig]); 237 }, 238 changes: [ 239 { 240 caption: "Disable allowUnsusedLabels", 241 change: sys => sys.modifyFile("/tsconfig.json", JSON.stringify({ 242 compilerOptions: { allowUnusedLabels: false } 243 })), 244 timeouts: checkSingleTimeoutQueueLengthAndRun 245 }, 246 { 247 caption: "Enable allowUnsusedLabels", 248 change: sys => sys.modifyFile("/tsconfig.json", JSON.stringify({ 249 compilerOptions: { allowUnusedLabels: true } 250 })), 251 timeouts: checkSingleTimeoutQueueLengthAndRun, 252 } 253 ] 254 }); 255 256 verifyTscWatch({ 257 scenario, 258 subScenario: "updates diagnostics and emit for decorators", 259 commandLineArgs: ["-w"], 260 sys: () => { 261 const aTs: File = { 262 path: "/a.ts", 263 content: `import {B} from './b' 264@((_) => {}) 265export class A { 266 constructor(p: B) {} 267}`, 268 }; 269 const bTs: File = { 270 path: "/b.ts", 271 content: `export class B {}`, 272 }; 273 const tsconfig: File = { 274 path: "/tsconfig.json", 275 content: JSON.stringify({ 276 compilerOptions: { target: "es6", importsNotUsedAsValues: "error" } 277 }) 278 }; 279 return createWatchedSystem([libFile, aTs, bTs, tsconfig]); 280 }, 281 changes: [ 282 { 283 caption: "Enable experimentalDecorators", 284 change: sys => sys.modifyFile("/tsconfig.json", JSON.stringify({ 285 compilerOptions: { target: "es6", importsNotUsedAsValues: "error", experimentalDecorators: true } 286 })), 287 timeouts: checkSingleTimeoutQueueLengthAndRun, 288 289 }, 290 { 291 caption: "Enable emitDecoratorMetadata", 292 change: sys => sys.modifyFile("/tsconfig.json", JSON.stringify({ 293 compilerOptions: { target: "es6", importsNotUsedAsValues: "error", experimentalDecorators: true, emitDecoratorMetadata: true } 294 })), 295 timeouts: checkSingleTimeoutQueueLengthAndRun, 296 } 297 ] 298 }); 299 300 verifyTscWatch({ 301 scenario, 302 subScenario: "files explicitly excluded in config file", 303 commandLineArgs: ["-w", "-p", configFilePath], 304 sys: () => { 305 const configFile: File = { 306 path: configFilePath, 307 content: `{ 308 "compilerOptions": {}, 309 "exclude": ["/a/c"] 310 }` 311 }; 312 const excludedFile1: File = { 313 path: "/a/c/excluedFile1.ts", 314 content: `let t = 1;` 315 }; 316 return createWatchedSystem([libFile, commonFile1, commonFile2, excludedFile1, configFile]); 317 }, 318 changes: emptyArray 319 }); 320 321 verifyTscWatch({ 322 scenario, 323 subScenario: "should properly handle module resolution changes in config file", 324 commandLineArgs: ["-w", "-p", configFilePath], 325 sys: () => { 326 const file1: File = { 327 path: "/a/b/file1.ts", 328 content: `import { T } from "module1";` 329 }; 330 const nodeModuleFile: File = { 331 path: "/a/b/node_modules/module1.ts", 332 content: `export interface T {}` 333 }; 334 const classicModuleFile: File = { 335 path: "/a/module1.ts", 336 content: `export interface T {}` 337 }; 338 const configFile: File = { 339 path: configFilePath, 340 content: `{ 341 "compilerOptions": { 342 "moduleResolution": "node" 343 }, 344 "files": ["${file1.path}"] 345 }` 346 }; 347 return createWatchedSystem([libFile, file1, nodeModuleFile, classicModuleFile, configFile]); 348 }, 349 changes: [ 350 { 351 caption: "Change module resolution to classic", 352 change: sys => sys.writeFile(configFile.path, `{ 353 "compilerOptions": { 354 "moduleResolution": "classic" 355 }, 356 "files": ["/a/b/file1.ts"] 357 }`), 358 timeouts: checkSingleTimeoutQueueLengthAndRun, 359 } 360 ] 361 }); 362 363 verifyTscWatch({ 364 scenario, 365 subScenario: "should tolerate config file errors and still try to build a project", 366 commandLineArgs: ["-w", "-p", configFilePath], 367 sys: () => { 368 const configFile: File = { 369 path: configFilePath, 370 content: `{ 371 "compilerOptions": { 372 "module": "none", 373 "allowAnything": true 374 }, 375 "someOtherProperty": {} 376 }` 377 }; 378 return createWatchedSystem([commonFile1, commonFile2, libFile, configFile]); 379 }, 380 changes: emptyArray 381 }); 382 383 verifyTscWatch({ 384 scenario, 385 subScenario: "changes in files are reflected in project structure", 386 commandLineArgs: ["-w", "/a/b/f1.ts", "--explainFiles"], 387 sys: () => { 388 const file1 = { 389 path: "/a/b/f1.ts", 390 content: `export * from "./f2"` 391 }; 392 const file2 = { 393 path: "/a/b/f2.ts", 394 content: `export let x = 1` 395 }; 396 const file3 = { 397 path: "/a/c/f3.ts", 398 content: `export let y = 1;` 399 }; 400 return createWatchedSystem([file1, file2, file3, libFile]); 401 }, 402 changes: [ 403 { 404 caption: "Modify f2 to include f3", 405 // now inferred project should inclule file3 406 change: sys => sys.modifyFile("/a/b/f2.ts", `export * from "../c/f3"`), 407 timeouts: checkSingleTimeoutQueueLengthAndRun, 408 } 409 ] 410 }); 411 412 verifyTscWatch({ 413 scenario, 414 subScenario: "deleted files affect project structure", 415 commandLineArgs: ["-w", "/a/b/f1.ts", "--noImplicitAny"], 416 sys: () => { 417 const file1 = { 418 path: "/a/b/f1.ts", 419 content: `export * from "./f2"` 420 }; 421 const file2 = { 422 path: "/a/b/f2.ts", 423 content: `export * from "../c/f3"` 424 }; 425 const file3 = { 426 path: "/a/c/f3.ts", 427 content: `export let y = 1;` 428 }; 429 return createWatchedSystem([file1, file2, file3, libFile]); 430 }, 431 changes: [ 432 { 433 caption: "Delete f2", 434 change: sys => sys.deleteFile("/a/b/f2.ts"), 435 timeouts: checkSingleTimeoutQueueLengthAndRun, 436 } 437 ] 438 }); 439 440 verifyTscWatch({ 441 scenario, 442 subScenario: "deleted files affect project structure-2", 443 commandLineArgs: ["-w", "/a/b/f1.ts", "/a/c/f3.ts", "--noImplicitAny"], 444 sys: () => { 445 const file1 = { 446 path: "/a/b/f1.ts", 447 content: `export * from "./f2"` 448 }; 449 const file2 = { 450 path: "/a/b/f2.ts", 451 content: `export * from "../c/f3"` 452 }; 453 const file3 = { 454 path: "/a/c/f3.ts", 455 content: `export let y = 1;` 456 }; 457 return createWatchedSystem([file1, file2, file3, libFile]); 458 }, 459 changes: [ 460 { 461 caption: "Delete f2", 462 change: sys => sys.deleteFile("/a/b/f2.ts"), 463 timeouts: checkSingleTimeoutQueueLengthAndRun, 464 } 465 ] 466 }); 467 468 verifyTscWatch({ 469 scenario, 470 subScenario: "config file includes the file", 471 commandLineArgs: ["-w", "-p", "/a/c/tsconfig.json"], 472 sys: () => { 473 const file1 = { 474 path: "/a/b/f1.ts", 475 content: "export let x = 5" 476 }; 477 const file2 = { 478 path: "/a/c/f2.ts", 479 content: `import {x} from "../b/f1"` 480 }; 481 const file3 = { 482 path: "/a/c/f3.ts", 483 content: "export let y = 1" 484 }; 485 const configFile = { 486 path: "/a/c/tsconfig.json", 487 content: JSON.stringify({ compilerOptions: {}, files: ["f2.ts", "f3.ts"] }) 488 }; 489 return createWatchedSystem([file1, file2, file3, libFile, configFile]); 490 }, 491 changes: emptyArray 492 }); 493 494 verifyTscWatch({ 495 scenario, 496 subScenario: "change module to none", 497 commandLineArgs: ["-w", "-p", configFilePath], 498 sys: () => { 499 const file1 = { 500 path: "/a/b/f1.ts", 501 content: "export {}\ndeclare global {}" 502 }; 503 return createWatchedSystem([file1, libFile, configFile]); 504 }, 505 changes: [{ 506 caption: "change `module` to 'none'", 507 timeouts: checkSingleTimeoutQueueLengthAndRun, 508 change: sys => { 509 sys.writeFile(configFilePath, JSON.stringify({ compilerOptions: { module: "none" } })); 510 } 511 }] 512 }); 513 514 it("two watch programs are not affected by each other", () => { 515 const file1 = { 516 path: "/a/b/f1.ts", 517 content: ` 518 export * from "../c/f2"; 519 export * from "../d/f3";` 520 }; 521 const file2 = { 522 path: "/a/c/f2.ts", 523 content: "export let x = 1;" 524 }; 525 const file3 = { 526 path: "/a/d/f3.ts", 527 content: "export let y = 1;" 528 }; 529 const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(createWatchedSystem([libFile, file1, file2, file3])); 530 const host = createWatchCompilerHostOfFilesAndCompilerOptionsForBaseline({ 531 rootFiles: [file2.path, file3.path], 532 system: sys, 533 options: { allowNonTsExtensions: true }, 534 cb, 535 watchOptions: undefined 536 }); 537 createWatchProgram(host); 538 baseline.push(`${sys.getExecutingFilePath()} --w ${file2.path} ${file3.path}`); 539 watchBaseline({ 540 baseline, 541 getPrograms, 542 oldPrograms: emptyArray, 543 sys, 544 oldSnap, 545 }); 546 547 const {cb: cb2, getPrograms: getPrograms2 } = commandLineCallbacks(sys); 548 const oldSnap2 = sys.snap(); 549 baseline.push("createing separate watcher"); 550 createWatchProgram(createWatchCompilerHostOfFilesAndCompilerOptionsForBaseline({ 551 rootFiles:[file1.path], 552 system: sys, 553 options: { allowNonTsExtensions: true }, 554 cb: cb2, 555 watchOptions: undefined 556 })); 557 watchBaseline({ 558 baseline, 559 getPrograms: getPrograms2, 560 oldPrograms: emptyArray, 561 sys, 562 oldSnap: oldSnap2, 563 }); 564 565 sys.checkTimeoutQueueLength(0); 566 baseline.push(`First program is not updated:: ${getPrograms() === emptyArray}`); 567 baseline.push(`Second program is not updated:: ${getPrograms2() === emptyArray}`); 568 Harness.Baseline.runBaseline(`tscWatch/${scenario}/two-watch-programs-are-not-affected-by-each-other.js`, baseline.join("\r\n")); 569 }); 570 571 verifyTscWatch({ 572 scenario, 573 subScenario: "can correctly update configured project when set of root files has changed (new file on disk)", 574 commandLineArgs: ["-w", "-p", configFilePath], 575 sys: () => { 576 const file1 = { 577 path: "/a/b/f1.ts", 578 content: "let x = 1" 579 }; 580 return createWatchedSystem([file1, libFile, configFile]); 581 }, 582 changes: [ 583 { 584 caption: "Write f2", 585 change: sys => sys.writeFile("/a/b/f2.ts", "let y = 1"), 586 timeouts: checkSingleTimeoutQueueLengthAndRun, 587 } 588 ] 589 }); 590 591 verifyTscWatch({ 592 scenario, 593 subScenario: "can correctly update configured project when set of root files has changed (new file in list of files)", 594 commandLineArgs: ["-w", "-p", configFilePath], 595 sys: () => { 596 const file1 = { 597 path: "/a/b/f1.ts", 598 content: "let x = 1" 599 }; 600 const file2 = { 601 path: "/a/b/f2.ts", 602 content: "let y = 1" 603 }; 604 const configFile = { 605 path: configFilePath, 606 content: JSON.stringify({ compilerOptions: {}, files: ["f1.ts"] }) 607 }; 608 return createWatchedSystem([file1, file2, libFile, configFile]); 609 }, 610 changes: [ 611 { 612 caption: "Modify config to make f2 as root too", 613 change: sys => sys.writeFile(configFilePath, JSON.stringify({ compilerOptions: {}, files: ["f1.ts", "f2.ts"] })), 614 timeouts: checkSingleTimeoutQueueLengthAndRun, 615 } 616 ] 617 }); 618 619 verifyTscWatch({ 620 scenario, 621 subScenario: "correctly parses wild card directories from implicit glob when two keys differ only in directory seperator", 622 commandLineArgs: ["-w", "--extendedDiagnostics"], 623 sys: () => { 624 const file1 = { 625 path: `${projectRoot}/f1.ts`, 626 content: "export const x = 1" 627 }; 628 const file2 = { 629 path: `${projectRoot}/f2.ts`, 630 content: "export const y = 1" 631 }; 632 const configFile = { 633 path: `${projectRoot}/tsconfig.json`, 634 content: JSON.stringify({ compilerOptions: { composite: true }, include: ["./", "./**/*.json"] }) 635 }; 636 return createWatchedSystem([file1, file2, libFile, configFile], { currentDirectory: projectRoot }); 637 }, 638 changes: [ 639 { 640 caption: "Add new file", 641 change: sys => sys.writeFile(`${projectRoot}/new-file.ts`, "export const z = 1;"), 642 timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1), 643 }, 644 { 645 caption: "Import new file", 646 change: sys => sys.prependFile(`${projectRoot}/f1.ts`, `import { z } from "./new-file";`), 647 timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1), 648 } 649 ] 650 }); 651 652 verifyTscWatch({ 653 scenario, 654 subScenario: "can correctly update configured project when set of root files has changed through include", 655 commandLineArgs: ["-w", "-p", "."], 656 sys: () => { 657 const file1 = { 658 path: `${projectRoot}/Project/file1.ts`, 659 content: "export const x = 10;" 660 }; 661 const configFile = { 662 path: `${projectRoot}/Project/tsconfig.json`, 663 content: JSON.stringify({ include: [".", "./**/*.json"] }) 664 }; 665 return createWatchedSystem([file1, libFile, configFile], { currentDirectory: `${projectRoot}/Project` }); 666 }, 667 changes: [ 668 { 669 caption: "Write file2", 670 change: sys => sys.writeFile(`${projectRoot}/Project/file2.ts`, "export const y = 10;"), 671 timeouts: checkSingleTimeoutQueueLengthAndRun 672 } 673 ] 674 }); 675 676 verifyTscWatch({ 677 scenario, 678 subScenario: "can update configured project when set of root files was not changed", 679 commandLineArgs: ["-w", "-p", configFilePath], 680 sys: () => { 681 const file1 = { 682 path: "/a/b/f1.ts", 683 content: "let x = 1" 684 }; 685 const file2 = { 686 path: "/a/b/f2.ts", 687 content: "let y = 1" 688 }; 689 const configFile = { 690 path: configFilePath, 691 content: JSON.stringify({ compilerOptions: {}, files: ["f1.ts", "f2.ts"] }) 692 }; 693 return createWatchedSystem([file1, file2, libFile, configFile]); 694 }, 695 changes: [ 696 { 697 caption: "Modify config to set outFile option", 698 change: sys => sys.writeFile(configFilePath, JSON.stringify({ compilerOptions: { outFile: "out.js" }, files: ["f1.ts", "f2.ts"] })), 699 timeouts: checkSingleTimeoutQueueLengthAndRun, 700 } 701 ] 702 }); 703 704 verifyTscWatch({ 705 scenario, 706 subScenario: "file in files is deleted", 707 commandLineArgs: ["-w", "-p", configFilePath], 708 sys: () => { 709 const file1 = { 710 path: "/a/b/f1.ts", 711 content: "let x = 1" 712 }; 713 const file2 = { 714 path: "/a/b/f2.ts", 715 content: "let y = 1" 716 }; 717 const configFile = { 718 path: configFilePath, 719 content: JSON.stringify({ compilerOptions: {}, files: ["f1.ts", "f2.ts"] }) 720 }; 721 return createWatchedSystem([file1, file2, libFile, configFile]); 722 }, 723 changes: [ 724 { 725 caption: "Delete f2", 726 change: sys => sys.deleteFile("/a/b/f2.ts"), 727 timeouts: checkSingleTimeoutQueueLengthAndRun, 728 } 729 ] 730 }); 731 732 verifyTscWatch({ 733 scenario, 734 subScenario: "config file is deleted", 735 commandLineArgs: ["-w", "-p", configFilePath], 736 sys: () => { 737 const file1 = { 738 path: "/a/b/f1.ts", 739 content: "let x = 1;" 740 }; 741 const file2 = { 742 path: "/a/b/f2.ts", 743 content: "let y = 2;" 744 }; 745 return createWatchedSystem([file1, file2, libFile, configFile]); 746 }, 747 changes: [ 748 { 749 caption: "Delete config file", 750 change: sys => sys.deleteFile(configFilePath), 751 timeouts: checkSingleTimeoutQueueLengthAndRun, 752 } 753 ] 754 }); 755 756 verifyTscWatch({ 757 scenario, 758 subScenario: "Proper errors document is not contained in project", 759 commandLineArgs: ["-w", "-p", configFilePath], 760 sys: () => { 761 const file1 = { 762 path: "/a/b/app.ts", 763 content: "" 764 }; 765 const corruptedConfig = { 766 path: configFilePath, 767 content: "{" 768 }; 769 return createWatchedSystem([file1, libFile, corruptedConfig]); 770 }, 771 changes: emptyArray 772 }); 773 774 verifyTscWatch({ 775 scenario, 776 subScenario: "correctly handles changes in lib section of config file", 777 commandLineArgs: ["-w", "-p", "/src/tsconfig.json"], 778 sys: () => { 779 const libES5 = { 780 path: "/compiler/lib.es5.d.ts", 781 content: `${libFile.content} 782declare const eval: any` 783 }; 784 const libES2015Promise = { 785 path: "/compiler/lib.es2015.promise.d.ts", 786 content: `declare class Promise<T> {}` 787 }; 788 const app = { 789 path: "/src/app.ts", 790 content: "var x: Promise<string>;" 791 }; 792 const config1 = { 793 path: "/src/tsconfig.json", 794 content: JSON.stringify( 795 { 796 compilerOptions: { 797 module: "commonjs", 798 target: "es5", 799 noImplicitAny: true, 800 sourceMap: false, 801 lib: [ 802 "es5" 803 ] 804 } 805 }) 806 }; 807 return createWatchedSystem([libES5, libES2015Promise, app, config1], { executingFilePath: "/compiler/tsc.js" }); 808 }, 809 changes: [ 810 { 811 caption: "Change the lib in config", 812 change: sys => sys.writeFile("/src/tsconfig.json", JSON.stringify( 813 { 814 compilerOptions: { 815 module: "commonjs", 816 target: "es5", 817 noImplicitAny: true, 818 sourceMap: false, 819 lib: [ 820 "es5", 821 "es2015.promise" 822 ] 823 } 824 }) 825 ), 826 timeouts: checkSingleTimeoutQueueLengthAndRun, 827 } 828 ] 829 }); 830 831 verifyTscWatch({ 832 scenario, 833 subScenario: "should handle non-existing directories in config file", 834 commandLineArgs: ["-w", "-p", "/a/tsconfig.json"], 835 sys: () => { 836 const f = { 837 path: "/a/src/app.ts", 838 content: "let x = 1;" 839 }; 840 const config = { 841 path: "/a/tsconfig.json", 842 content: JSON.stringify({ 843 compilerOptions: {}, 844 include: [ 845 "src/**/*", 846 "notexistingfolder/*" 847 ] 848 }) 849 }; 850 return createWatchedSystem([f, config, libFile]); 851 }, 852 changes: emptyArray 853 }); 854 855 function runQueuedTimeoutCallbacksTwice(sys: WatchedSystem) { 856 sys.runQueuedTimeoutCallbacks(); // Scheduled invalidation of resolutions 857 sys.runQueuedTimeoutCallbacks(); // Actual update 858 } 859 860 const changeModuleFileToModuleFile1: TscWatchCompileChange = { 861 caption: "Rename moduleFile to moduleFile1", 862 change: sys => { 863 sys.renameFile("/a/b/moduleFile.ts", "/a/b/moduleFile1.ts"); 864 sys.deleteFile("/a/b/moduleFile.js"); 865 }, 866 timeouts: runQueuedTimeoutCallbacksTwice 867 }; 868 const changeModuleFile1ToModuleFile: TscWatchCompileChange = { 869 caption: "Rename moduleFile1 back to moduleFile", 870 change: sys => sys.renameFile("/a/b/moduleFile1.ts", "/a/b/moduleFile.ts"), 871 timeouts: runQueuedTimeoutCallbacksTwice, 872 }; 873 874 verifyTscWatch({ 875 scenario, 876 subScenario: "rename a module file and rename back should restore the states for inferred projects", 877 commandLineArgs: ["-w", "/a/b/file1.ts"], 878 sys: () => { 879 const moduleFile = { 880 path: "/a/b/moduleFile.ts", 881 content: "export function bar() { };" 882 }; 883 const file1 = { 884 path: "/a/b/file1.ts", 885 content: 'import * as T from "./moduleFile"; T.bar();' 886 }; 887 return createWatchedSystem([moduleFile, file1, libFile]); 888 }, 889 changes: [ 890 changeModuleFileToModuleFile1, 891 changeModuleFile1ToModuleFile 892 ] 893 }); 894 895 verifyTscWatch({ 896 scenario, 897 subScenario: "rename a module file and rename back should restore the states for configured projects", 898 commandLineArgs: ["-w", "-p", configFilePath], 899 sys: () => { 900 const moduleFile = { 901 path: "/a/b/moduleFile.ts", 902 content: "export function bar() { };" 903 }; 904 const file1 = { 905 path: "/a/b/file1.ts", 906 content: 'import * as T from "./moduleFile"; T.bar();' 907 }; 908 return createWatchedSystem([moduleFile, file1, configFile, libFile]); 909 }, 910 changes: [ 911 changeModuleFileToModuleFile1, 912 changeModuleFile1ToModuleFile 913 ] 914 }); 915 916 verifyTscWatch({ 917 scenario, 918 subScenario: "types should load from config file path if config exists", 919 commandLineArgs: ["-w", "-p", configFilePath], 920 sys: () => { 921 const f1 = { 922 path: "/a/b/app.ts", 923 content: "let x = 1" 924 }; 925 const config = { 926 path: configFilePath, 927 content: JSON.stringify({ compilerOptions: { types: ["node"], typeRoots: [] } }) 928 }; 929 const node = { 930 path: "/a/b/node_modules/@types/node/index.d.ts", 931 content: "declare var process: any" 932 }; 933 const cwd = { 934 path: "/a/c" 935 }; 936 return createWatchedSystem([f1, config, node, cwd, libFile], { currentDirectory: cwd.path }); 937 }, 938 changes: emptyArray 939 }); 940 941 verifyTscWatch({ 942 scenario, 943 subScenario: "add the missing module file for inferred project-should remove the module not found error", 944 commandLineArgs: ["-w", "/a/b/file1.ts"], 945 sys: () => { 946 const file1 = { 947 path: "/a/b/file1.ts", 948 content: 'import * as T from "./moduleFile"; T.bar();' 949 }; 950 return createWatchedSystem([file1, libFile]); 951 }, 952 changes: [ 953 { 954 caption: "Create module file", 955 change: sys => sys.writeFile("/a/b/moduleFile.ts", "export function bar() { }"), 956 timeouts: runQueuedTimeoutCallbacksTwice, 957 } 958 ] 959 }); 960 961 verifyTscWatch({ 962 scenario, 963 subScenario: "Configure file diagnostics events are generated when the config file has errors", 964 commandLineArgs: ["-w", "-p", configFilePath], 965 sys: () => { 966 const file = { 967 path: "/a/b/app.ts", 968 content: "let x = 10" 969 }; 970 const configFile = { 971 path: configFilePath, 972 content: `{ 973 "compilerOptions": { 974 "foo": "bar", 975 "allowJS": true 976 } 977 }` 978 }; 979 return createWatchedSystem([file, configFile, libFile]); 980 }, 981 changes: emptyArray 982 }); 983 984 verifyTscWatch({ 985 scenario, 986 subScenario: "if config file doesnt have errors, they are not reported", 987 commandLineArgs: ["-w", "-p", configFilePath], 988 sys: () => { 989 const file = { 990 path: "/a/b/app.ts", 991 content: "let x = 10" 992 }; 993 const configFile = { 994 path: configFilePath, 995 content: `{ 996 "compilerOptions": {} 997 }` 998 }; 999 return createWatchedSystem([file, configFile, libFile]); 1000 }, 1001 changes: emptyArray 1002 }); 1003 1004 verifyTscWatch({ 1005 scenario, 1006 subScenario: "Reports errors when the config file changes", 1007 commandLineArgs: ["-w", "-p", configFilePath], 1008 sys: () => { 1009 const file = { 1010 path: "/a/b/app.ts", 1011 content: "let x = 10" 1012 }; 1013 return createWatchedSystem([file, configFile, libFile]); 1014 }, 1015 changes: [ 1016 { 1017 caption: "change config file to add error", 1018 change: sys => sys.writeFile(configFilePath, `{ 1019 "compilerOptions": { 1020 "haha": 123 1021 } 1022 }`), 1023 timeouts: runQueuedTimeoutCallbacks, 1024 }, 1025 { 1026 caption: "change config file to remove error", 1027 change: sys => sys.writeFile(configFilePath, `{ 1028 "compilerOptions": { 1029 } 1030 }`), 1031 timeouts: runQueuedTimeoutCallbacks, 1032 } 1033 ] 1034 }); 1035 1036 verifyTscWatch({ 1037 scenario, 1038 subScenario: "non-existing directories listed in config file input array should be tolerated without crashing the server", 1039 commandLineArgs: ["-w", "-p", configFilePath], 1040 sys: () => { 1041 const configFile = { 1042 path: configFilePath, 1043 content: `{ 1044 "compilerOptions": {}, 1045 "include": ["app/*", "test/**/*", "something"] 1046 }` 1047 }; 1048 const file1 = { 1049 path: "/a/b/file1.ts", 1050 content: "let t = 10;" 1051 }; 1052 return createWatchedSystem([file1, configFile, libFile]); 1053 }, 1054 changes: emptyArray 1055 }); 1056 1057 verifyTscWatch({ 1058 scenario, 1059 subScenario: "non-existing directories listed in config file input array should be able to handle @types if input file list is empty", 1060 commandLineArgs: ["-w", "-p", "/a/tsconfig.json"], 1061 sys: () => { 1062 const f = { 1063 path: "/a/app.ts", 1064 content: "let x = 1" 1065 }; 1066 const config = { 1067 path: "/a/tsconfig.json", 1068 content: JSON.stringify({ 1069 compiler: {}, 1070 files: [] 1071 }) 1072 }; 1073 const t1 = { 1074 path: "/a/node_modules/@types/typings/index.d.ts", 1075 content: `export * from "./lib"` 1076 }; 1077 const t2 = { 1078 path: "/a/node_modules/@types/typings/lib.d.ts", 1079 content: `export const x: number` 1080 }; 1081 return createWatchedSystem([f, config, t1, t2, libFile], { currentDirectory: getDirectoryPath(f.path) }); 1082 }, 1083 changes: emptyArray 1084 }); 1085 1086 it("should support files without extensions", () => { 1087 const f = { 1088 path: "/a/compile", 1089 content: "let x = 1" 1090 }; 1091 const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(createWatchedSystem([f, libFile])); 1092 const watch = createWatchProgram(createWatchCompilerHostOfFilesAndCompilerOptionsForBaseline({ 1093 rootFiles: [f.path], 1094 system: sys, 1095 options: { allowNonTsExtensions: true }, 1096 cb, 1097 watchOptions: undefined 1098 })); 1099 runWatchBaseline({ 1100 scenario, 1101 subScenario: "should support files without extensions", 1102 commandLineArgs: ["--w", f.path], 1103 sys, 1104 baseline, 1105 oldSnap, 1106 getPrograms, 1107 changes: emptyArray, 1108 watchOrSolution: watch 1109 }); 1110 }); 1111 1112 verifyTscWatch({ 1113 scenario, 1114 subScenario: "Options Diagnostic locations reported correctly with changes in configFile contents when options change", 1115 commandLineArgs: ["-w", "-p", configFilePath], 1116 sys: () => { 1117 const file = { 1118 path: "/a/b/app.ts", 1119 content: "let x = 10" 1120 }; 1121 const configFile = { 1122 path: configFilePath, 1123 content: ` 1124{ 1125 // comment 1126 // More comment 1127 "compilerOptions": { 1128 "inlineSourceMap": true, 1129 "mapRoot": "./" 1130 } 1131}` 1132 }; 1133 return createWatchedSystem([file, libFile, configFile]); 1134 }, 1135 changes: [ 1136 { 1137 caption: "Remove the comment from config file", 1138 change: sys => sys.writeFile(configFilePath, ` 1139{ 1140 "compilerOptions": { 1141 "inlineSourceMap": true, 1142 "mapRoot": "./" 1143 } 1144}`), 1145 timeouts: runQueuedTimeoutCallbacks, 1146 } 1147 ] 1148 }); 1149 1150 describe("should not trigger recompilation because of program emit", () => { 1151 function verifyWithOptions(subScenario: string, options: CompilerOptions) { 1152 verifyTscWatch({ 1153 scenario, 1154 subScenario: `should not trigger recompilation because of program emit/${subScenario}`, 1155 commandLineArgs: ["-w", "-p", `${projectRoot}/tsconfig.json`], 1156 sys: () => { 1157 const file1: File = { 1158 path: `${projectRoot}/file1.ts`, 1159 content: "export const c = 30;" 1160 }; 1161 const file2: File = { 1162 path: `${projectRoot}/src/file2.ts`, 1163 content: `import {c} from "file1"; export const d = 30;` 1164 }; 1165 const tsconfig: File = { 1166 path: `${projectRoot}/tsconfig.json`, 1167 content: generateTSConfig(options, emptyArray, "\n") 1168 }; 1169 return createWatchedSystem([file1, file2, libFile, tsconfig], { currentDirectory: projectRoot }); 1170 }, 1171 changes: [ 1172 noopChange, 1173 { 1174 caption: "Add new file", 1175 change: sys => sys.writeFile(`${projectRoot}/src/file3.ts`, `export const y = 10;`), 1176 timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2), // To update program and failed lookups 1177 }, 1178 noopChange, 1179 ] 1180 }); 1181 } 1182 1183 verifyWithOptions( 1184 "without outDir or outFile is specified", 1185 { module: ModuleKind.AMD } 1186 ); 1187 1188 verifyWithOptions( 1189 "with outFile", 1190 { module: ModuleKind.AMD, outFile: "build/outFile.js" } 1191 ); 1192 1193 verifyWithOptions( 1194 "when outDir is specified", 1195 { module: ModuleKind.AMD, outDir: "build" } 1196 ); 1197 1198 verifyWithOptions( 1199 "without outDir or outFile is specified with declaration enabled", 1200 { module: ModuleKind.AMD, declaration: true } 1201 ); 1202 1203 verifyWithOptions( 1204 "when outDir and declarationDir is specified", 1205 { module: ModuleKind.AMD, outDir: "build", declaration: true, declarationDir: "decls" } 1206 ); 1207 1208 verifyWithOptions( 1209 "declarationDir is specified", 1210 { module: ModuleKind.AMD, declaration: true, declarationDir: "decls" } 1211 ); 1212 }); 1213 1214 verifyTscWatch({ 1215 scenario, 1216 subScenario: "shouldnt report error about unused function incorrectly when file changes from global to module", 1217 commandLineArgs: ["-w", "/a/b/file.ts", "--noUnusedLocals"], 1218 sys: () => { 1219 const file: File = { 1220 path: "/a/b/file.ts", 1221 content: `function one() {} 1222function two() { 1223 return function three() { 1224 one(); 1225 } 1226}` 1227 }; 1228 return createWatchedSystem([file, libFile]); 1229 }, 1230 changes: [ 1231 { 1232 caption: "Change file to module", 1233 change: sys => sys.writeFile("/a/b/file.ts", `function one() {} 1234export function two() { 1235 return function three() { 1236 one(); 1237 } 1238}`), 1239 timeouts: runQueuedTimeoutCallbacks, 1240 1241 } 1242 ] 1243 }); 1244 1245 verifyTscWatch({ 1246 scenario, 1247 subScenario: "watched files when file is deleted and new file is added as part of change", 1248 commandLineArgs: ["-w", "-p", "/home/username/project/tsconfig.json"], 1249 sys: () => { 1250 const projectLocation = "/home/username/project"; 1251 const file: File = { 1252 path: `${projectLocation}/src/file1.ts`, 1253 content: "var a = 10;" 1254 }; 1255 const configFile: File = { 1256 path: `${projectLocation}/tsconfig.json`, 1257 content: "{}" 1258 }; 1259 return createWatchedSystem([file, libFile, configFile]); 1260 }, 1261 changes: [ 1262 { 1263 caption: "Rename file1 to file2", 1264 change: sys => sys.renameFile("/home/username/project/src/file1.ts", "/home/username/project/src/file2.ts"), 1265 timeouts: runQueuedTimeoutCallbacks, 1266 } 1267 ] 1268 }); 1269 1270 function changeParameterTypeOfBFile(parameterName: string, toType: string): TscWatchCompileChange { 1271 return { 1272 caption: `Changed ${parameterName} type to ${toType}`, 1273 change: sys => replaceFileText(sys, `${projectRoot}/b.ts`, new RegExp(`${parameterName}\: [a-z]*`), `${parameterName}: ${toType}`), 1274 timeouts: runQueuedTimeoutCallbacks, 1275 }; 1276 } 1277 1278 verifyTscWatch({ 1279 scenario, 1280 subScenario: "updates errors correctly when declaration emit is disabled in compiler options", 1281 commandLineArgs: ["-w"], 1282 sys: () => { 1283 const aFile: File = { 1284 path: `${projectRoot}/a.ts`, 1285 content: `import test from './b'; 1286test(4, 5);` 1287 }; 1288 const bFile: File = { 1289 path: `${projectRoot}/b.ts`, 1290 content: `function test(x: number, y: number) { 1291 return x + y / 5; 1292} 1293export default test;` 1294 }; 1295 const tsconfigFile: File = { 1296 path: `${projectRoot}/tsconfig.json`, 1297 content: JSON.stringify({ 1298 compilerOptions: { 1299 module: "commonjs", 1300 noEmit: true, 1301 strict: true, 1302 } 1303 }) 1304 }; 1305 return createWatchedSystem([aFile, bFile, libFile, tsconfigFile], { currentDirectory: projectRoot }); 1306 }, 1307 changes: [ 1308 changeParameterTypeOfBFile("x", "string"), 1309 changeParameterTypeOfBFile("x", "number"), 1310 changeParameterTypeOfBFile("y", "string"), 1311 changeParameterTypeOfBFile("y", "number"), 1312 ] 1313 }); 1314 1315 verifyTscWatch({ 1316 scenario, 1317 subScenario: "updates errors when strictNullChecks changes", 1318 commandLineArgs: ["-w"], 1319 sys: () => { 1320 const aFile: File = { 1321 path: `${projectRoot}/a.ts`, 1322 content: `declare function foo(): null | { hello: any }; 1323foo().hello` 1324 }; 1325 const config: File = { 1326 path: `${projectRoot}/tsconfig.json`, 1327 content: JSON.stringify({ compilerOptions: {} }) 1328 }; 1329 return createWatchedSystem([aFile, config, libFile], { currentDirectory: projectRoot }); 1330 }, 1331 changes: [ 1332 { 1333 caption: "Enable strict null checks", 1334 change: sys => sys.writeFile(`${projectRoot}/tsconfig.json`, JSON.stringify({ compilerOptions: { strictNullChecks: true } })), 1335 timeouts: runQueuedTimeoutCallbacks, 1336 }, 1337 { 1338 caption: "Set always strict false", 1339 change: sys => sys.writeFile(`${projectRoot}/tsconfig.json`, JSON.stringify({ compilerOptions: { strict: true, alwaysStrict: false } })), // Avoid changing 'alwaysStrict' or must re-bind 1340 timeouts: runQueuedTimeoutCallbacks, 1341 }, 1342 { 1343 caption: "Disable strict", 1344 change: sys => sys.writeFile(`${projectRoot}/tsconfig.json`, JSON.stringify({ compilerOptions: {} })), 1345 timeouts: runQueuedTimeoutCallbacks, 1346 }, 1347 ] 1348 }); 1349 1350 verifyTscWatch({ 1351 scenario, 1352 subScenario: "updates errors when noErrorTruncation changes", 1353 commandLineArgs: ["-w"], 1354 sys: () => { 1355 const aFile: File = { 1356 path: `${projectRoot}/a.ts`, 1357 content: `declare var v: { 1358 reallyLongPropertyName1: string | number | boolean | object | symbol | bigint; 1359 reallyLongPropertyName2: string | number | boolean | object | symbol | bigint; 1360 reallyLongPropertyName3: string | number | boolean | object | symbol | bigint; 1361 reallyLongPropertyName4: string | number | boolean | object | symbol | bigint; 1362 reallyLongPropertyName5: string | number | boolean | object | symbol | bigint; 1363 reallyLongPropertyName6: string | number | boolean | object | symbol | bigint; 1364 reallyLongPropertyName7: string | number | boolean | object | symbol | bigint; 1365}; 1366v === 'foo';` 1367 }; 1368 const config: File = { 1369 path: `${projectRoot}/tsconfig.json`, 1370 content: JSON.stringify({ compilerOptions: {} }) 1371 }; 1372 return createWatchedSystem([aFile, config, libFile], { currentDirectory: projectRoot }); 1373 }, 1374 changes: [ 1375 { 1376 caption: "Enable noErrorTruncation", 1377 change: sys => sys.writeFile(`${projectRoot}/tsconfig.json`, JSON.stringify({ compilerOptions: { noErrorTruncation: true } })), 1378 timeouts: runQueuedTimeoutCallbacks, 1379 }, 1380 ] 1381 }); 1382 1383 verifyTscWatch({ 1384 scenario, 1385 subScenario: "updates diagnostics and emit when useDefineForClassFields changes", 1386 commandLineArgs: ["-w"], 1387 sys: () => { 1388 const aFile: File = { 1389 path: `/a.ts`, 1390 content: `class C { get prop() { return 1; } } 1391class D extends C { prop = 1; }` 1392 }; 1393 const config: File = { 1394 path: `/tsconfig.json`, 1395 content: JSON.stringify({ compilerOptions: { target: "es6" } }) 1396 }; 1397 return createWatchedSystem([aFile, config, libFile]); 1398 }, 1399 changes: [ 1400 { 1401 caption: "Enable useDefineForClassFields", 1402 change: sys => sys.writeFile(`/tsconfig.json`, JSON.stringify({ compilerOptions: { target: "es6", useDefineForClassFields: true } })), 1403 timeouts: runQueuedTimeoutCallbacks, 1404 }, 1405 ] 1406 }); 1407 1408 verifyTscWatch({ 1409 scenario, 1410 subScenario: "updates errors and emit when importsNotUsedAsValues changes", 1411 commandLineArgs: ["-w"], 1412 sys: () => { 1413 const aFile: File = { 1414 path: `${projectRoot}/a.ts`, 1415 content: `export class C {}` 1416 }; 1417 const bFile: File = { 1418 path: `${projectRoot}/b.ts`, 1419 content: `import {C} from './a'; 1420export function f(p: C) { return p; }` 1421 }; 1422 const config: File = { 1423 path: `${projectRoot}/tsconfig.json`, 1424 content: JSON.stringify({ compilerOptions: {} }) 1425 }; 1426 return createWatchedSystem([aFile, bFile, config, libFile], { currentDirectory: projectRoot }); 1427 }, 1428 changes: [ 1429 { 1430 caption: 'Set to "remove"', 1431 change: sys => sys.writeFile(`${projectRoot}/tsconfig.json`, JSON.stringify({ compilerOptions: { importsNotUsedAsValues: "remove" } })), 1432 timeouts: runQueuedTimeoutCallbacks, 1433 }, 1434 { 1435 caption: 'Set to "error"', 1436 change: sys => sys.writeFile(`${projectRoot}/tsconfig.json`, JSON.stringify({ compilerOptions: { importsNotUsedAsValues: "error" } })), 1437 timeouts: runQueuedTimeoutCallbacks, 1438 }, 1439 { 1440 caption: 'Set to "preserve"', 1441 change: sys => sys.writeFile(`${projectRoot}/tsconfig.json`, JSON.stringify({ compilerOptions: { importsNotUsedAsValues: "preserve" } })), 1442 timeouts: runQueuedTimeoutCallbacks, 1443 }, 1444 ] 1445 }); 1446 1447 1448 verifyTscWatch({ 1449 scenario, 1450 subScenario: "updates errors when forceConsistentCasingInFileNames changes", 1451 commandLineArgs: ["-w"], 1452 sys: () => { 1453 const aFile: File = { 1454 path: `/a.ts`, 1455 content: `export class C {}` 1456 }; 1457 const bFile: File = { 1458 path: `/b.ts`, 1459 content: `import {C} from './a'; import * as A from './A';` 1460 }; 1461 const config: File = { 1462 path: `/tsconfig.json`, 1463 content: JSON.stringify({ compilerOptions: {} }) 1464 }; 1465 return createWatchedSystem([aFile, bFile, config, libFile], { useCaseSensitiveFileNames: false }); 1466 }, 1467 changes: [ 1468 { 1469 caption: "Enable forceConsistentCasingInFileNames", 1470 change: sys => sys.writeFile(`/tsconfig.json`, JSON.stringify({ compilerOptions: { forceConsistentCasingInFileNames: true } })), 1471 timeouts: runQueuedTimeoutCallbacks, 1472 }, 1473 ] 1474 }); 1475 1476 verifyTscWatch({ 1477 scenario, 1478 subScenario: "updates moduleResolution when resolveJsonModule changes", 1479 commandLineArgs: ["-w"], 1480 sys: () => { 1481 const aFile: File = { 1482 path: `${projectRoot}/a.ts`, 1483 content: `import * as data from './data.json'` 1484 }; 1485 const jsonFile: File = { 1486 path: `${projectRoot}/data.json`, 1487 content: `{ "foo": 1 }` 1488 }; 1489 const config: File = { 1490 path: `${projectRoot}/tsconfig.json`, 1491 content: JSON.stringify({ compilerOptions: { moduleResolution: "node" } }) 1492 }; 1493 return createWatchedSystem([aFile, jsonFile, config, libFile], { currentDirectory: projectRoot }); 1494 }, 1495 changes: [ 1496 { 1497 caption: "Enable resolveJsonModule", 1498 change: sys => sys.writeFile(`${projectRoot}/tsconfig.json`, JSON.stringify({ compilerOptions: { moduleResolution: "node", resolveJsonModule: true } })), 1499 timeouts: runQueuedTimeoutCallbacks, 1500 }, 1501 ] 1502 }); 1503 1504 verifyTscWatch({ 1505 scenario, 1506 subScenario: "updates errors when ambient modules of program changes", 1507 commandLineArgs: ["-w"], 1508 sys: () => { 1509 const aFile: File = { 1510 path: `${projectRoot}/a.ts`, 1511 content: `declare module 'a' { 1512 type foo = number; 1513}` 1514 }; 1515 const config: File = { 1516 path: `${projectRoot}/tsconfig.json`, 1517 content: "{}" 1518 }; 1519 return createWatchedSystem([aFile, config, libFile], { currentDirectory: projectRoot }); 1520 }, 1521 changes: [ 1522 { 1523 caption: "Create b.ts with same content", 1524 // Create bts with same file contents 1525 change: sys => sys.writeFile(`${projectRoot}/b.ts`, `declare module 'a' { 1526 type foo = number; 1527}`), 1528 timeouts: runQueuedTimeoutCallbacks, 1529 }, 1530 { 1531 caption: "Delete b.ts", 1532 change: sys => sys.deleteFile(`${projectRoot}/b.ts`), 1533 timeouts: runQueuedTimeoutCallbacks, 1534 }, 1535 ] 1536 }); 1537 1538 describe("updates errors in lib file", () => { 1539 const field = "fullscreen"; 1540 const fieldWithoutReadonly = `interface Document { 1541 ${field}: boolean; 1542}`; 1543 1544 const libFileWithDocument: File = { 1545 path: libFile.path, 1546 content: `${libFile.content} 1547interface Document { 1548 readonly ${field}: boolean; 1549}` 1550 }; 1551 1552 function verifyLibFileErrorsWith(subScenario: string, aFile: File) { 1553 function verifyLibErrors(subScenario: string, commandLineOptions: readonly string[]) { 1554 verifyTscWatch({ 1555 scenario, 1556 subScenario: `updates errors in lib file/${subScenario}`, 1557 commandLineArgs: ["-w", aFile.path, ...commandLineOptions], 1558 sys: () => createWatchedSystem([aFile, libFileWithDocument], { currentDirectory: projectRoot }), 1559 changes: [ 1560 { 1561 caption: "Remove document declaration from file", 1562 change: sys => sys.writeFile(aFile.path, aFile.content.replace(fieldWithoutReadonly, "var x: string;")), 1563 timeouts: runQueuedTimeoutCallbacks, 1564 }, 1565 { 1566 caption: "Rever the file to contain document declaration", 1567 change: sys => sys.writeFile(aFile.path, aFile.content), 1568 timeouts: runQueuedTimeoutCallbacks, 1569 }, 1570 ] 1571 }); 1572 } 1573 1574 verifyLibErrors(`${subScenario}/with default options`, emptyArray); 1575 verifyLibErrors(`${subScenario}/with skipLibCheck`, ["--skipLibCheck"]); 1576 verifyLibErrors(`${subScenario}/with skipDefaultLibCheck`, ["--skipDefaultLibCheck"]); 1577 } 1578 1579 describe("when non module file changes", () => { 1580 const aFile: File = { 1581 path: `${projectRoot}/a.ts`, 1582 content: `${fieldWithoutReadonly} 1583var y: number;` 1584 }; 1585 verifyLibFileErrorsWith("when non module file changes", aFile); 1586 }); 1587 1588 describe("when module file with global definitions changes", () => { 1589 const aFile: File = { 1590 path: `${projectRoot}/a.ts`, 1591 content: `export {} 1592declare global { 1593${fieldWithoutReadonly} 1594var y: number; 1595}` 1596 }; 1597 verifyLibFileErrorsWith("when module file with global definitions changes", aFile); 1598 }); 1599 }); 1600 1601 function changeWhenLibCheckChanges(compilerOptions: CompilerOptions): TscWatchCompileChange { 1602 const configFileContent = JSON.stringify({ compilerOptions }); 1603 return { 1604 caption: `Changing config to ${configFileContent}`, 1605 change: sys => sys.writeFile(`${projectRoot}/tsconfig.json`, configFileContent), 1606 timeouts: runQueuedTimeoutCallbacks, 1607 }; 1608 } 1609 1610 verifyTscWatch({ 1611 scenario, 1612 subScenario: "when skipLibCheck and skipDefaultLibCheck changes", 1613 commandLineArgs: ["-w"], 1614 sys: () => { 1615 const field = "fullscreen"; 1616 const aFile: File = { 1617 path: `${projectRoot}/a.ts`, 1618 content: `interface Document { 1619 ${field}: boolean; 1620}` 1621 }; 1622 const bFile: File = { 1623 path: `${projectRoot}/b.d.ts`, 1624 content: `interface Document { 1625 ${field}: boolean; 1626}` 1627 }; 1628 const libFileWithDocument: File = { 1629 path: libFile.path, 1630 content: `${libFile.content} 1631interface Document { 1632 readonly ${field}: boolean; 1633}` 1634 }; 1635 const configFile: File = { 1636 path: `${projectRoot}/tsconfig.json`, 1637 content: "{}" 1638 }; 1639 return createWatchedSystem([aFile, bFile, configFile, libFileWithDocument], { currentDirectory: projectRoot }); 1640 }, 1641 changes: [ 1642 changeWhenLibCheckChanges({ skipLibCheck: true }), 1643 changeWhenLibCheckChanges({ skipDefaultLibCheck: true }), 1644 changeWhenLibCheckChanges({}), 1645 changeWhenLibCheckChanges({ skipDefaultLibCheck: true }), 1646 changeWhenLibCheckChanges({ skipLibCheck: true }), 1647 changeWhenLibCheckChanges({}), 1648 ] 1649 }); 1650 1651 verifyTscWatch({ 1652 scenario, 1653 subScenario: "reports errors correctly with isolatedModules", 1654 commandLineArgs: ["-w"], 1655 sys: () => { 1656 const aFile: File = { 1657 path: `${projectRoot}/a.ts`, 1658 content: `export const a: string = "";` 1659 }; 1660 const bFile: File = { 1661 path: `${projectRoot}/b.ts`, 1662 content: `import { a } from "./a"; 1663const b: string = a;` 1664 }; 1665 const configFile: File = { 1666 path: `${projectRoot}/tsconfig.json`, 1667 content: JSON.stringify({ 1668 compilerOptions: { 1669 isolatedModules: true 1670 } 1671 }) 1672 }; 1673 return createWatchedSystem([aFile, bFile, configFile, libFile], { currentDirectory: projectRoot }); 1674 }, 1675 changes: [ 1676 { 1677 caption: "Change shape of a", 1678 change: sys => sys.writeFile(`${projectRoot}/a.ts`, `export const a: number = 1`), 1679 timeouts: runQueuedTimeoutCallbacks, 1680 }, 1681 ] 1682 }); 1683 1684 verifyTscWatch({ 1685 scenario, 1686 subScenario: "reports errors correctly with file not in rootDir", 1687 commandLineArgs: ["-w"], 1688 sys: () => { 1689 const aFile: File = { 1690 path: `${projectRoot}/a.ts`, 1691 content: `import { x } from "../b";` 1692 }; 1693 const bFile: File = { 1694 path: `/user/username/projects/b.ts`, 1695 content: `export const x = 10;` 1696 }; 1697 const configFile: File = { 1698 path: `${projectRoot}/tsconfig.json`, 1699 content: JSON.stringify({ 1700 compilerOptions: { 1701 rootDir: ".", 1702 outDir: "lib" 1703 } 1704 }) 1705 }; 1706 return createWatchedSystem([aFile, bFile, configFile, libFile], { currentDirectory: projectRoot }); 1707 }, 1708 changes: [ 1709 { 1710 caption: "Make changes to file a", 1711 change: sys => sys.writeFile(`${projectRoot}/a.ts`, ` 1712 1713import { x } from "../b";`), 1714 timeouts: runQueuedTimeoutCallbacks, 1715 }, 1716 ] 1717 }); 1718 1719 verifyTscWatch({ 1720 scenario, 1721 subScenario: "updates emit on jsx option change", 1722 commandLineArgs: ["-w"], 1723 sys: () => { 1724 const index: File = { 1725 path: `${projectRoot}/index.tsx`, 1726 content: `declare var React: any;\nconst d = <div />;` 1727 }; 1728 const configFile: File = { 1729 path: `${projectRoot}/tsconfig.json`, 1730 content: JSON.stringify({ 1731 compilerOptions: { 1732 jsx: "preserve" 1733 } 1734 }) 1735 }; 1736 return createWatchedSystem([index, configFile, libFile], { currentDirectory: projectRoot }); 1737 }, 1738 changes: [ 1739 { 1740 caption: "Update 'jsx' to 'react'", 1741 change: sys => sys.writeFile(`${projectRoot}/tsconfig.json`, '{ "compilerOptions": { "jsx": "react" } }'), 1742 timeouts: runQueuedTimeoutCallbacks, 1743 }, 1744 ] 1745 }); 1746 1747 verifyTscWatch({ 1748 scenario, 1749 subScenario: "extended source files are watched", 1750 commandLineArgs: ["-w", "-p", configFilePath], 1751 sys: () => { 1752 const firstExtendedConfigFile: File = { 1753 path: "/a/b/first.tsconfig.json", 1754 content: JSON.stringify({ 1755 compilerOptions: { 1756 strict: true 1757 } 1758 }) 1759 }; 1760 const secondExtendedConfigFile: File = { 1761 path: "/a/b/second.tsconfig.json", 1762 content: JSON.stringify({ 1763 extends: "./first.tsconfig.json" 1764 }) 1765 }; 1766 const configFile: File = { 1767 path: configFilePath, 1768 content: JSON.stringify({ 1769 compilerOptions: {}, 1770 files: [commonFile1.path, commonFile2.path] 1771 }) 1772 }; 1773 return createWatchedSystem([ 1774 libFile, commonFile1, commonFile2, configFile, firstExtendedConfigFile, secondExtendedConfigFile 1775 ]); 1776 }, 1777 changes: [ 1778 { 1779 caption: "Change config to extend another config", 1780 change: sys => sys.modifyFile(configFilePath, JSON.stringify({ 1781 extends: "./second.tsconfig.json", 1782 compilerOptions: {}, 1783 files: [commonFile1.path, commonFile2.path] 1784 })), 1785 timeouts: checkSingleTimeoutQueueLengthAndRun, 1786 }, 1787 { 1788 caption: "Change first extended config", 1789 change: sys => sys.modifyFile("/a/b/first.tsconfig.json", JSON.stringify({ 1790 compilerOptions: { 1791 strict: false, 1792 } 1793 })), 1794 timeouts: checkSingleTimeoutQueueLengthAndRun, 1795 }, 1796 { 1797 caption: "Change second extended config", 1798 change: sys => sys.modifyFile("/a/b/second.tsconfig.json", JSON.stringify({ 1799 extends: "./first.tsconfig.json", 1800 compilerOptions: { 1801 strictNullChecks: true, 1802 } 1803 })), 1804 timeouts: checkSingleTimeoutQueueLengthAndRun, 1805 }, 1806 { 1807 caption: "Change config to stop extending another config", 1808 change: sys => sys.modifyFile(configFilePath, JSON.stringify({ 1809 compilerOptions: {}, 1810 files: [commonFile1.path, commonFile2.path] 1811 })), 1812 timeouts: checkSingleTimeoutQueueLengthAndRun, 1813 }, 1814 ] 1815 }); 1816 1817 verifyTscWatch({ 1818 scenario, 1819 subScenario: "when creating new file in symlinked folder", 1820 commandLineArgs: ["-w", "-p", ".", "--extendedDiagnostics"], 1821 sys: () => { 1822 const module1: File = { 1823 path: `${projectRoot}/client/folder1/module1.ts`, 1824 content: `export class Module1Class { }` 1825 }; 1826 const module2: File = { 1827 path: `${projectRoot}/folder2/module2.ts`, 1828 content: `import * as M from "folder1/module1";` 1829 }; 1830 const symlink: SymLink = { 1831 path: `${projectRoot}/client/linktofolder2`, 1832 symLink: `${projectRoot}/folder2`, 1833 }; 1834 const config: File = { 1835 path: `${projectRoot}/tsconfig.json`, 1836 content: JSON.stringify({ 1837 compilerOptions: { 1838 baseUrl: "client", 1839 paths: { "*": ["*"] }, 1840 }, 1841 include: ["client/**/*", "folder2"] 1842 }) 1843 }; 1844 return createWatchedSystem([module1, module2, symlink, config, libFile], { currentDirectory: projectRoot }); 1845 }, 1846 changes: [ 1847 { 1848 caption: "Add module3 to folder2", 1849 change: sys => sys.writeFile(`${projectRoot}/client/linktofolder2/module3.ts`, `import * as M from "folder1/module1";`), 1850 timeouts: checkSingleTimeoutQueueLengthAndRun, 1851 }, 1852 ] 1853 }); 1854 1855 verifyTscWatch({ 1856 scenario, 1857 subScenario: "when new file is added to the referenced project", 1858 commandLineArgs: ["-w", "-p", `${projectRoot}/projects/project2/tsconfig.json`, "--extendedDiagnostics"], 1859 sys: () => { 1860 const config1: File = { 1861 path: `${projectRoot}/projects/project1/tsconfig.json`, 1862 content: JSON.stringify({ 1863 compilerOptions: { 1864 module: "none", 1865 composite: true 1866 }, 1867 exclude: ["temp"] 1868 }) 1869 }; 1870 const class1: File = { 1871 path: `${projectRoot}/projects/project1/class1.ts`, 1872 content: `class class1 {}` 1873 }; 1874 // Built file 1875 const class1Dt: File = { 1876 path: `${projectRoot}/projects/project1/class1.d.ts`, 1877 content: `declare class class1 {}` 1878 }; 1879 const config2: File = { 1880 path: `${projectRoot}/projects/project2/tsconfig.json`, 1881 content: JSON.stringify({ 1882 compilerOptions: { 1883 module: "none", 1884 composite: true 1885 }, 1886 references: [ 1887 { path: "../project1" } 1888 ] 1889 }) 1890 }; 1891 const class2: File = { 1892 path: `${projectRoot}/projects/project2/class2.ts`, 1893 content: `class class2 {}` 1894 }; 1895 return createWatchedSystem([config1, class1, config2, class2, libFile, class1Dt]); 1896 }, 1897 changes: [ 1898 { 1899 caption: "Add class3 to project1", 1900 change: sys => sys.writeFile(`${projectRoot}/projects/project1/class3.ts`, `class class3 {}`), 1901 timeouts: checkSingleTimeoutQueueLengthAndRun, 1902 }, 1903 { 1904 caption: "Add output of class3", 1905 change: sys => sys.writeFile(`${projectRoot}/projects/project1/class3.d.ts`, `declare class class3 {}`), 1906 timeouts: checkSingleTimeoutQueueLengthAndRun, 1907 }, 1908 { 1909 caption: "Add excluded file to project1", 1910 change: sys => sys.ensureFileOrFolder({ path: `${projectRoot}/projects/project1/temp/file.d.ts`, content: `declare class file {}` }), 1911 timeouts: sys => sys.checkTimeoutQueueLength(0), 1912 }, 1913 { 1914 caption: "Delete output of class3", 1915 change: sys => sys.deleteFile(`${projectRoot}/projects/project1/class3.d.ts`), 1916 timeouts: checkSingleTimeoutQueueLengthAndRun, 1917 }, 1918 { 1919 caption: "Add output of class3", 1920 change: sys => sys.writeFile(`${projectRoot}/projects/project1/class3.d.ts`, `declare class class3 {}`), 1921 timeouts: checkSingleTimeoutQueueLengthAndRun, 1922 }, 1923 ] 1924 }); 1925 1926 verifyTscWatch({ 1927 scenario, 1928 subScenario: "when creating extensionless file", 1929 commandLineArgs: ["-w", "-p", ".", "--extendedDiagnostics"], 1930 sys: () => { 1931 const module1: File = { 1932 path: `${projectRoot}/index.ts`, 1933 content: `` 1934 }; 1935 const config: File = { 1936 path: `${projectRoot}/tsconfig.json`, 1937 content: `{}` 1938 }; 1939 return createWatchedSystem([module1, config, libFile], { currentDirectory: projectRoot }); 1940 }, 1941 changes: [ 1942 { 1943 caption: "Create foo in project root", 1944 change: sys => sys.writeFile(`${projectRoot}/foo`, ``), 1945 timeouts: checkSingleTimeoutQueueLengthAndRun, 1946 }, 1947 ] 1948 }); 1949 }); 1950} 1951