1namespace ts { 2 describe("unittests:: config:: commandLineParsing:: parseCommandLine", () => { 3 4 function assertParseResult(commandLine: string[], expectedParsedCommandLine: ParsedCommandLine, workerDiagnostic?: () => ParseCommandLineWorkerDiagnostics) { 5 const parsed = parseCommandLineWorker(workerDiagnostic?.() || compilerOptionsDidYouMeanDiagnostics, commandLine); 6 assert.deepEqual(parsed.options, expectedParsedCommandLine.options); 7 assert.deepEqual(parsed.watchOptions, expectedParsedCommandLine.watchOptions); 8 9 const parsedErrors = parsed.errors; 10 const expectedErrors = expectedParsedCommandLine.errors; 11 assert.isTrue(parsedErrors.length === expectedErrors.length, `Expected error: ${JSON.stringify(expectedErrors)}. Actual error: ${JSON.stringify(parsedErrors)}.`); 12 for (let i = 0; i < parsedErrors.length; i++) { 13 const parsedError = parsedErrors[i]; 14 const expectedError = expectedErrors[i]; 15 assert.equal(parsedError.code, expectedError.code); 16 assert.equal(parsedError.category, expectedError.category); 17 // Allow matching a prefix of the error message 18 if (typeof expectedError.messageText === "string" && expectedError.messageText.includes("[...]")) { 19 const prefix = expectedError.messageText.split("[...]")[0]; 20 assert(expectedError.messageText.startsWith(prefix)); 21 } 22 else { 23 assert.equal(parsedError.messageText, expectedError.messageText); 24 } 25 } 26 27 const parsedFileNames = parsed.fileNames; 28 const expectedFileNames = expectedParsedCommandLine.fileNames; 29 assert.isTrue(parsedFileNames.length === expectedFileNames.length, `Expected fileNames: [${JSON.stringify(expectedFileNames)}]. Actual fileNames: [${JSON.stringify(parsedFileNames)}].`); 30 for (let i = 0; i < parsedFileNames.length; i++) { 31 const parsedFileName = parsedFileNames[i]; 32 const expectedFileName = expectedFileNames[i]; 33 assert.equal(parsedFileName, expectedFileName); 34 } 35 } 36 37 it("Parse single option of library flag ", () => { 38 // --lib es6 0.ts 39 assertParseResult(["--lib", "es6", "0.ts"], 40 { 41 errors: [], 42 fileNames: ["0.ts"], 43 options: { 44 lib: ["lib.es2015.d.ts"] 45 } 46 }); 47 }); 48 49 it("Handles 'did you mean?' for misspelt flags", () => { 50 // --declarations --allowTS 51 assertParseResult(["--declarations", "--allowTS"], { 52 errors: [ 53 { 54 messageText: "Unknown compiler option '--declarations'. Did you mean 'declaration'?", 55 category: Diagnostics.Unknown_compiler_option_0_Did_you_mean_1.category, 56 code: Diagnostics.Unknown_compiler_option_0_Did_you_mean_1.code, 57 file: undefined, 58 start: undefined, 59 length: undefined 60 }, 61 { 62 messageText: "Unknown compiler option '--allowTS'. Did you mean 'allowJs'?", 63 category: Diagnostics.Unknown_compiler_option_0_Did_you_mean_1.category, 64 code: Diagnostics.Unknown_compiler_option_0_Did_you_mean_1.code, 65 file: undefined, 66 start: undefined, 67 length: undefined 68 } 69 ], 70 fileNames: [], 71 options: {} 72 }); 73 }); 74 75 76 it("Parse multiple options of library flags ", () => { 77 // --lib es5,es2015.symbol.wellknown 0.ts 78 assertParseResult(["--lib", "es5,es2015.symbol.wellknown", "0.ts"], 79 { 80 errors: [], 81 fileNames: ["0.ts"], 82 options: { 83 lib: ["lib.es5.d.ts", "lib.es2015.symbol.wellknown.d.ts"] 84 } 85 }); 86 }); 87 88 it("Parse invalid option of library flags ", () => { 89 // --lib es5,invalidOption 0.ts 90 assertParseResult(["--lib", "es5,invalidOption", "0.ts"], 91 { 92 errors: [{ 93 messageText: "Argument for '--lib' option must be: 'es5', 'es6' [...]", 94 category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category, 95 code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, 96 file: undefined, 97 start: undefined, 98 length: undefined, 99 }], 100 fileNames: ["0.ts"], 101 options: { 102 lib: ["lib.es5.d.ts"] 103 } 104 }); 105 }); 106 it("Parse empty options of --jsx ", () => { 107 // 0.ts --jsx 108 assertParseResult(["0.ts", "--jsx"], 109 { 110 errors: [{ 111 messageText: "Compiler option 'jsx' expects an argument.", 112 category: Diagnostics.Compiler_option_0_expects_an_argument.category, 113 code: Diagnostics.Compiler_option_0_expects_an_argument.code, 114 115 file: undefined, 116 start: undefined, 117 length: undefined, 118 }, { 119 messageText: "Argument for '--jsx' option must be: 'preserve', 'react-native', 'react', 'react-jsx', 'react-jsxdev'.", 120 category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category, 121 code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, 122 123 file: undefined, 124 start: undefined, 125 length: undefined, 126 }], 127 fileNames: ["0.ts"], 128 options: { jsx: undefined } 129 }); 130 }); 131 132 it("Parse empty options of --module ", () => { 133 // 0.ts -- 134 assertParseResult(["0.ts", "--module"], 135 { 136 errors: [{ 137 messageText: "Compiler option 'module' expects an argument.", 138 category: Diagnostics.Compiler_option_0_expects_an_argument.category, 139 code: Diagnostics.Compiler_option_0_expects_an_argument.code, 140 141 file: undefined, 142 start: undefined, 143 length: undefined, 144 }, { 145 messageText: "Argument for '--module' option must be: 'none', 'commonjs', 'amd', 'system', 'umd', 'es6', 'es2015', 'es2020', 'esnext'.", 146 category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category, 147 code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, 148 149 file: undefined, 150 start: undefined, 151 length: undefined, 152 }], 153 fileNames: ["0.ts"], 154 options: { module: undefined } 155 }); 156 }); 157 158 it("Parse empty options of --newLine ", () => { 159 // 0.ts --newLine 160 assertParseResult(["0.ts", "--newLine"], 161 { 162 errors: [{ 163 messageText: "Compiler option 'newLine' expects an argument.", 164 category: Diagnostics.Compiler_option_0_expects_an_argument.category, 165 code: Diagnostics.Compiler_option_0_expects_an_argument.code, 166 167 file: undefined, 168 start: undefined, 169 length: undefined, 170 }, { 171 messageText: "Argument for '--newLine' option must be: 'crlf', 'lf'.", 172 category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category, 173 code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, 174 175 file: undefined, 176 start: undefined, 177 length: undefined, 178 }], 179 fileNames: ["0.ts"], 180 options: { newLine: undefined } 181 }); 182 }); 183 184 it("Parse empty options of --target ", () => { 185 // 0.ts --target 186 assertParseResult(["0.ts", "--target"], 187 { 188 errors: [{ 189 messageText: "Compiler option 'target' expects an argument.", 190 category: Diagnostics.Compiler_option_0_expects_an_argument.category, 191 code: Diagnostics.Compiler_option_0_expects_an_argument.code, 192 193 file: undefined, 194 start: undefined, 195 length: undefined, 196 }, { 197 messageText: "Argument for '--target' option must be: 'es3', 'es5', 'es6', 'es2015', 'es2016', 'es2017', 'es2018', 'es2019', 'es2020', 'esnext'.", 198 category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category, 199 code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, 200 201 file: undefined, 202 start: undefined, 203 length: undefined, 204 }], 205 fileNames: ["0.ts"], 206 options: { target: undefined } 207 }); 208 }); 209 210 it("Parse empty options of --moduleResolution ", () => { 211 // 0.ts --moduleResolution 212 assertParseResult(["0.ts", "--moduleResolution"], 213 { 214 errors: [{ 215 messageText: "Compiler option 'moduleResolution' expects an argument.", 216 category: Diagnostics.Compiler_option_0_expects_an_argument.category, 217 code: Diagnostics.Compiler_option_0_expects_an_argument.code, 218 219 file: undefined, 220 start: undefined, 221 length: undefined, 222 }, { 223 messageText: "Argument for '--moduleResolution' option must be: 'node', 'classic'.", 224 category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category, 225 code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, 226 227 file: undefined, 228 start: undefined, 229 length: undefined, 230 }], 231 fileNames: ["0.ts"], 232 options: { moduleResolution: undefined } 233 }); 234 }); 235 236 it("Parse empty options of --lib ", () => { 237 // 0.ts --lib 238 assertParseResult(["0.ts", "--lib"], 239 { 240 errors: [{ 241 messageText: "Compiler option 'lib' expects an argument.", 242 category: Diagnostics.Compiler_option_0_expects_an_argument.category, 243 code: Diagnostics.Compiler_option_0_expects_an_argument.code, 244 245 file: undefined, 246 start: undefined, 247 length: undefined, 248 }], 249 fileNames: ["0.ts"], 250 options: { 251 lib: [] 252 } 253 }); 254 }); 255 256 it("Parse empty string of --lib ", () => { 257 // 0.ts --lib 258 // This test is an error because the empty string is falsey 259 assertParseResult(["0.ts", "--lib", ""], 260 { 261 errors: [{ 262 messageText: "Compiler option 'lib' expects an argument.", 263 category: Diagnostics.Compiler_option_0_expects_an_argument.category, 264 code: Diagnostics.Compiler_option_0_expects_an_argument.code, 265 266 file: undefined, 267 start: undefined, 268 length: undefined, 269 }], 270 fileNames: ["0.ts"], 271 options: { 272 lib: [] 273 } 274 }); 275 }); 276 277 it("Parse immediately following command line argument of --lib ", () => { 278 // 0.ts --lib 279 assertParseResult(["0.ts", "--lib", "--sourcemap"], 280 { 281 errors: [], 282 fileNames: ["0.ts"], 283 options: { 284 lib: [], 285 sourceMap: true 286 } 287 }); 288 }); 289 290 it("Parse --lib option with extra comma ", () => { 291 // --lib es5, es7 0.ts 292 assertParseResult(["--lib", "es5,", "es7", "0.ts"], 293 { 294 errors: [{ 295 messageText: "Argument for '--lib' option must be: 'es5', 'es6' [...].", 296 category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category, 297 code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, 298 file: undefined, 299 start: undefined, 300 length: undefined, 301 }], 302 fileNames: ["es7", "0.ts"], 303 options: { 304 lib: ["lib.es5.d.ts"] 305 } 306 }); 307 }); 308 309 it("Parse --lib option with trailing white-space ", () => { 310 // --lib es5, es7 0.ts 311 assertParseResult(["--lib", "es5, ", "es7", "0.ts"], 312 { 313 errors: [{ 314 messageText: "Argument for '--lib' option must be: 'es5', 'es6', [...]", 315 category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category, 316 code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, 317 file: undefined, 318 start: undefined, 319 length: undefined, 320 }], 321 fileNames: ["es7", "0.ts"], 322 options: { 323 lib: ["lib.es5.d.ts"] 324 } 325 }); 326 }); 327 328 it("Parse multiple compiler flags with input files at the end", () => { 329 // --lib es5,es2015.symbol.wellknown --target es5 0.ts 330 assertParseResult(["--lib", "es5,es2015.symbol.wellknown", "--target", "es5", "0.ts"], 331 { 332 errors: [], 333 fileNames: ["0.ts"], 334 options: { 335 lib: ["lib.es5.d.ts", "lib.es2015.symbol.wellknown.d.ts"], 336 target: ScriptTarget.ES5, 337 } 338 }); 339 }); 340 341 it("Parse multiple compiler flags with input files in the middle", () => { 342 // --module commonjs --target es5 0.ts --lib es5,es2015.symbol.wellknown 343 assertParseResult(["--module", "commonjs", "--target", "es5", "0.ts", "--lib", "es5,es2015.symbol.wellknown"], 344 { 345 errors: [], 346 fileNames: ["0.ts"], 347 options: { 348 module: ModuleKind.CommonJS, 349 target: ScriptTarget.ES5, 350 lib: ["lib.es5.d.ts", "lib.es2015.symbol.wellknown.d.ts"], 351 } 352 }); 353 }); 354 355 it("Parse multiple library compiler flags ", () => { 356 // --module commonjs --target es5 --lib es5 0.ts --library es2015.array,es2015.symbol.wellknown 357 assertParseResult(["--module", "commonjs", "--target", "es5", "--lib", "es5", "0.ts", "--lib", "es2015.core, es2015.symbol.wellknown "], 358 { 359 errors: [], 360 fileNames: ["0.ts"], 361 options: { 362 module: ModuleKind.CommonJS, 363 target: ScriptTarget.ES5, 364 lib: ["lib.es2015.core.d.ts", "lib.es2015.symbol.wellknown.d.ts"], 365 } 366 }); 367 }); 368 369 it("Parse explicit boolean flag value", () => { 370 assertParseResult(["--strictNullChecks", "false", "0.ts"], 371 { 372 errors: [], 373 fileNames: ["0.ts"], 374 options: { 375 strictNullChecks: false, 376 } 377 }); 378 }); 379 380 it("Parse non boolean argument after boolean flag", () => { 381 assertParseResult(["--noImplicitAny", "t", "0.ts"], 382 { 383 errors: [], 384 fileNames: ["t", "0.ts"], 385 options: { 386 noImplicitAny: true, 387 } 388 }); 389 }); 390 391 it("Parse implicit boolean flag value", () => { 392 assertParseResult(["--strictNullChecks"], 393 { 394 errors: [], 395 fileNames: [], 396 options: { 397 strictNullChecks: true, 398 } 399 }); 400 }); 401 402 it("parse --incremental", () => { 403 // --lib es6 0.ts 404 assertParseResult(["--incremental", "0.ts"], 405 { 406 errors: [], 407 fileNames: ["0.ts"], 408 options: { incremental: true } 409 }); 410 }); 411 412 it("parse --tsBuildInfoFile", () => { 413 // --lib es6 0.ts 414 assertParseResult(["--tsBuildInfoFile", "build.tsbuildinfo", "0.ts"], 415 { 416 errors: [], 417 fileNames: ["0.ts"], 418 options: { tsBuildInfoFile: "build.tsbuildinfo" } 419 }); 420 }); 421 422 describe("parses command line null for tsconfig only option", () => { 423 interface VerifyNull { 424 optionName: string; 425 nonNullValue?: string; 426 workerDiagnostic?: () => ParseCommandLineWorkerDiagnostics; 427 diagnosticMessage: DiagnosticMessage; 428 } 429 function verifyNull({ optionName, nonNullValue, workerDiagnostic, diagnosticMessage }: VerifyNull) { 430 it("allows setting it to null", () => { 431 assertParseResult( 432 [`--${optionName}`, "null", "0.ts"], 433 { 434 errors: [], 435 fileNames: ["0.ts"], 436 options: { [optionName]: undefined } 437 }, 438 workerDiagnostic 439 ); 440 }); 441 442 if (nonNullValue) { 443 it("errors if non null value is passed", () => { 444 assertParseResult( 445 [`--${optionName}`, nonNullValue, "0.ts"], 446 { 447 errors: [{ 448 messageText: formatStringFromArgs(diagnosticMessage.message, [optionName]), 449 category: diagnosticMessage.category, 450 code: diagnosticMessage.code, 451 file: undefined, 452 start: undefined, 453 length: undefined 454 }], 455 fileNames: ["0.ts"], 456 options: {} 457 }, 458 workerDiagnostic 459 ); 460 }); 461 } 462 463 it("errors if its followed by another option", () => { 464 assertParseResult( 465 ["0.ts", "--strictNullChecks", `--${optionName}`], 466 { 467 errors: [{ 468 messageText: formatStringFromArgs(diagnosticMessage.message, [optionName]), 469 category: diagnosticMessage.category, 470 code: diagnosticMessage.code, 471 file: undefined, 472 start: undefined, 473 length: undefined 474 }], 475 fileNames: ["0.ts"], 476 options: { strictNullChecks: true } 477 }, 478 workerDiagnostic 479 ); 480 }); 481 482 it("errors if its last option", () => { 483 assertParseResult( 484 ["0.ts", `--${optionName}`], 485 { 486 errors: [{ 487 messageText: formatStringFromArgs(diagnosticMessage.message, [optionName]), 488 category: diagnosticMessage.category, 489 code: diagnosticMessage.code, 490 file: undefined, 491 start: undefined, 492 length: undefined 493 }], 494 fileNames: ["0.ts"], 495 options: {} 496 }, 497 workerDiagnostic 498 ); 499 }); 500 } 501 502 interface VerifyNullNonIncludedOption { 503 type: () => "string" | "number" | ESMap<string, number | string>; 504 nonNullValue?: string; 505 } 506 function verifyNullNonIncludedOption({ type, nonNullValue }: VerifyNullNonIncludedOption) { 507 verifyNull({ 508 optionName: "optionName", 509 nonNullValue, 510 diagnosticMessage: Diagnostics.Option_0_can_only_be_specified_in_tsconfig_json_file_or_set_to_null_on_command_line, 511 workerDiagnostic: () => { 512 const optionDeclarations = [ 513 ...compilerOptionsDidYouMeanDiagnostics.optionDeclarations, 514 { 515 name: "optionName", 516 type: type(), 517 isTSConfigOnly: true, 518 category: Diagnostics.Basic_Options, 519 description: Diagnostics.Enable_project_compilation, 520 } 521 ]; 522 return { 523 ...compilerOptionsDidYouMeanDiagnostics, 524 optionDeclarations, 525 getOptionsNameMap: () => createOptionNameMap(optionDeclarations) 526 }; 527 } 528 }); 529 } 530 531 describe("option of type boolean", () => { 532 it("allows setting it to false", () => { 533 assertParseResult( 534 ["--composite", "false", "0.ts"], 535 { 536 errors: [], 537 fileNames: ["0.ts"], 538 options: { composite: false } 539 } 540 ); 541 }); 542 543 verifyNull({ 544 optionName: "composite", 545 nonNullValue: "true", 546 diagnosticMessage: Diagnostics.Option_0_can_only_be_specified_in_tsconfig_json_file_or_set_to_false_or_null_on_command_line 547 }); 548 }); 549 550 describe("option of type object", () => { 551 verifyNull({ 552 optionName: "paths", 553 diagnosticMessage: Diagnostics.Option_0_can_only_be_specified_in_tsconfig_json_file_or_set_to_null_on_command_line 554 }); 555 }); 556 557 describe("option of type list", () => { 558 verifyNull({ 559 optionName: "rootDirs", 560 nonNullValue: "abc,xyz", 561 diagnosticMessage: Diagnostics.Option_0_can_only_be_specified_in_tsconfig_json_file_or_set_to_null_on_command_line 562 }); 563 }); 564 565 describe("option of type string", () => { 566 verifyNullNonIncludedOption({ 567 type: () => "string", 568 nonNullValue: "hello" 569 }); 570 }); 571 572 describe("option of type number", () => { 573 verifyNullNonIncludedOption({ 574 type: () => "number", 575 nonNullValue: "10" 576 }); 577 }); 578 579 describe("option of type Map<number | string>", () => { 580 verifyNullNonIncludedOption({ 581 type: () => new Map(getEntries({ 582 node: ModuleResolutionKind.NodeJs, 583 classic: ModuleResolutionKind.Classic, 584 })), 585 nonNullValue: "node" 586 }); 587 }); 588 }); 589 590 it("allows tsconfig only option to be set to null", () => { 591 assertParseResult(["--composite", "null", "-tsBuildInfoFile", "null", "0.ts"], 592 { 593 errors: [], 594 fileNames: ["0.ts"], 595 options: { composite: undefined, tsBuildInfoFile: undefined } 596 }); 597 }); 598 599 describe("Watch options", () => { 600 it("parse --watchFile", () => { 601 assertParseResult(["--watchFile", "UseFsEvents", "0.ts"], 602 { 603 errors: [], 604 fileNames: ["0.ts"], 605 options: {}, 606 watchOptions: { watchFile: WatchFileKind.UseFsEvents } 607 }); 608 }); 609 610 it("parse --watchDirectory", () => { 611 assertParseResult(["--watchDirectory", "FixedPollingInterval", "0.ts"], 612 { 613 errors: [], 614 fileNames: ["0.ts"], 615 options: {}, 616 watchOptions: { watchDirectory: WatchDirectoryKind.FixedPollingInterval } 617 }); 618 }); 619 620 it("parse --fallbackPolling", () => { 621 assertParseResult(["--fallbackPolling", "PriorityInterval", "0.ts"], 622 { 623 errors: [], 624 fileNames: ["0.ts"], 625 options: {}, 626 watchOptions: { fallbackPolling: PollingWatchKind.PriorityInterval } 627 }); 628 }); 629 630 it("parse --synchronousWatchDirectory", () => { 631 assertParseResult(["--synchronousWatchDirectory", "0.ts"], 632 { 633 errors: [], 634 fileNames: ["0.ts"], 635 options: {}, 636 watchOptions: { synchronousWatchDirectory: true } 637 }); 638 }); 639 640 it("errors on missing argument to --fallbackPolling", () => { 641 assertParseResult(["0.ts", "--fallbackPolling"], 642 { 643 errors: [ 644 { 645 messageText: "Watch option 'fallbackPolling' requires a value of type string.", 646 category: Diagnostics.Watch_option_0_requires_a_value_of_type_1.category, 647 code: Diagnostics.Watch_option_0_requires_a_value_of_type_1.code, 648 file: undefined, 649 start: undefined, 650 length: undefined 651 }, 652 { 653 messageText: "Argument for '--fallbackPolling' option must be: 'fixedinterval', 'priorityinterval', 'dynamicpriority'.", 654 category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category, 655 code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, 656 file: undefined, 657 start: undefined, 658 length: undefined 659 } 660 ], 661 fileNames: ["0.ts"], 662 options: {}, 663 watchOptions: { fallbackPolling: undefined } 664 }); 665 }); 666 667 it("parse --excludeDirectories", () => { 668 assertParseResult(["--excludeDirectories", "**/temp", "0.ts"], 669 { 670 errors: [], 671 fileNames: ["0.ts"], 672 options: {}, 673 watchOptions: { excludeDirectories: ["**/temp"] } 674 }); 675 }); 676 677 it("errors on invalid excludeDirectories", () => { 678 assertParseResult(["--excludeDirectories", "**/../*", "0.ts"], 679 { 680 errors: [ 681 { 682 messageText: `File specification cannot contain a parent directory ('..') that appears after a recursive directory wildcard ('**'): '**/../*'.`, 683 category: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.category, 684 code: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.code, 685 file: undefined, 686 start: undefined, 687 length: undefined 688 } 689 ], 690 fileNames: ["0.ts"], 691 options: {}, 692 watchOptions: { excludeDirectories: [] } 693 }); 694 }); 695 696 it("parse --excludeFiles", () => { 697 assertParseResult(["--excludeFiles", "**/temp/*.ts", "0.ts"], 698 { 699 errors: [], 700 fileNames: ["0.ts"], 701 options: {}, 702 watchOptions: { excludeFiles: ["**/temp/*.ts"] } 703 }); 704 }); 705 706 it("errors on invalid excludeFiles", () => { 707 assertParseResult(["--excludeFiles", "**/../*", "0.ts"], 708 { 709 errors: [ 710 { 711 messageText: `File specification cannot contain a parent directory ('..') that appears after a recursive directory wildcard ('**'): '**/../*'.`, 712 category: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.category, 713 code: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.code, 714 file: undefined, 715 start: undefined, 716 length: undefined 717 } 718 ], 719 fileNames: ["0.ts"], 720 options: {}, 721 watchOptions: { excludeFiles: [] } 722 }); 723 }); 724 }); 725 }); 726 727 describe("unittests:: config:: commandLineParsing:: parseBuildOptions", () => { 728 function assertParseResult(commandLine: string[], expectedParsedBuildCommand: ParsedBuildCommand) { 729 const parsed = parseBuildCommand(commandLine); 730 assert.deepEqual(parsed.buildOptions, expectedParsedBuildCommand.buildOptions); 731 assert.deepEqual(parsed.watchOptions, expectedParsedBuildCommand.watchOptions); 732 733 const parsedErrors = parsed.errors; 734 const expectedErrors = expectedParsedBuildCommand.errors; 735 assert.isTrue(parsedErrors.length === expectedErrors.length, `Expected error: ${JSON.stringify(expectedErrors)}. Actual error: ${JSON.stringify(parsedErrors)}.`); 736 for (let i = 0; i < parsedErrors.length; i++) { 737 const parsedError = parsedErrors[i]; 738 const expectedError = expectedErrors[i]; 739 assert.equal(parsedError.code, expectedError.code); 740 assert.equal(parsedError.category, expectedError.category); 741 assert.equal(parsedError.messageText, expectedError.messageText); 742 } 743 744 const parsedProjects = parsed.projects; 745 const expectedProjects = expectedParsedBuildCommand.projects; 746 assert.deepEqual(parsedProjects, expectedProjects, `Expected projects: [${JSON.stringify(expectedProjects)}]. Actual projects: [${JSON.stringify(parsedProjects)}].`); 747 } 748 it("parse build without any options ", () => { 749 // --lib es6 0.ts 750 assertParseResult([], 751 { 752 errors: [], 753 projects: ["."], 754 buildOptions: {}, 755 watchOptions: undefined 756 }); 757 }); 758 759 it("Parse multiple options", () => { 760 // --lib es5,es2015.symbol.wellknown 0.ts 761 assertParseResult(["--verbose", "--force", "tests"], 762 { 763 errors: [], 764 projects: ["tests"], 765 buildOptions: { verbose: true, force: true }, 766 watchOptions: undefined 767 }); 768 }); 769 770 it("Parse option with invalid option ", () => { 771 // --lib es5,invalidOption 0.ts 772 assertParseResult(["--verbose", "--invalidOption"], 773 { 774 errors: [{ 775 messageText: "Unknown build option '--invalidOption'.", 776 category: Diagnostics.Unknown_build_option_0.category, 777 code: Diagnostics.Unknown_build_option_0.code, 778 file: undefined, 779 start: undefined, 780 length: undefined, 781 }], 782 projects: ["."], 783 buildOptions: { verbose: true }, 784 watchOptions: undefined 785 }); 786 }); 787 788 it("parse build with listFilesOnly ", () => { 789 // --lib es6 0.ts 790 assertParseResult(["--listFilesOnly"], 791 { 792 errors: [{ 793 messageText: "Unknown build option '--listFilesOnly'.", 794 category: Diagnostics.Unknown_build_option_0.category, 795 code: Diagnostics.Unknown_build_option_0.code, 796 file: undefined, 797 start: undefined, 798 length: undefined, 799 }], 800 projects: ["."], 801 buildOptions: {}, 802 watchOptions: undefined, 803 }); 804 }); 805 806 it("Parse multiple flags with input projects at the end", () => { 807 // --lib es5,es2015.symbol.wellknown --target es5 0.ts 808 assertParseResult(["--force", "--verbose", "src", "tests"], 809 { 810 errors: [], 811 projects: ["src", "tests"], 812 buildOptions: { force: true, verbose: true }, 813 watchOptions: undefined, 814 }); 815 }); 816 817 it("Parse multiple flags with input projects in the middle", () => { 818 // --module commonjs --target es5 0.ts --lib es5,es2015.symbol.wellknown 819 assertParseResult(["--force", "src", "tests", "--verbose"], 820 { 821 errors: [], 822 projects: ["src", "tests"], 823 buildOptions: { force: true, verbose: true }, 824 watchOptions: undefined, 825 }); 826 }); 827 828 it("Parse multiple flags with input projects in the beginning", () => { 829 // --module commonjs --target es5 0.ts --lib es5,es2015.symbol.wellknown 830 assertParseResult(["src", "tests", "--force", "--verbose"], 831 { 832 errors: [], 833 projects: ["src", "tests"], 834 buildOptions: { force: true, verbose: true }, 835 watchOptions: undefined, 836 }); 837 }); 838 839 it("parse build with --incremental", () => { 840 // --lib es6 0.ts 841 assertParseResult(["--incremental", "tests"], 842 { 843 errors: [], 844 projects: ["tests"], 845 buildOptions: { incremental: true }, 846 watchOptions: undefined, 847 }); 848 }); 849 850 it("parse build with --locale en-us", () => { 851 // --lib es6 0.ts 852 assertParseResult(["--locale", "en-us", "src"], 853 { 854 errors: [], 855 projects: ["src"], 856 buildOptions: { locale: "en-us" }, 857 watchOptions: undefined, 858 }); 859 }); 860 861 it("parse build with --tsBuildInfoFile", () => { 862 // --lib es6 0.ts 863 assertParseResult(["--tsBuildInfoFile", "build.tsbuildinfo", "tests"], 864 { 865 errors: [{ 866 messageText: "Unknown build option '--tsBuildInfoFile'.", 867 category: Diagnostics.Unknown_build_option_0.category, 868 code: Diagnostics.Unknown_build_option_0.code, 869 file: undefined, 870 start: undefined, 871 length: undefined 872 }], 873 projects: ["build.tsbuildinfo", "tests"], 874 buildOptions: {}, 875 watchOptions: undefined, 876 }); 877 }); 878 879 describe("Combining options that make no sense together", () => { 880 function verifyInvalidCombination(flag1: keyof BuildOptions, flag2: keyof BuildOptions) { 881 it(`--${flag1} and --${flag2} together is invalid`, () => { 882 // --module commonjs --target es5 0.ts --lib es5,es2015.symbol.wellknown 883 assertParseResult([`--${flag1}`, `--${flag2}`], 884 { 885 errors: [{ 886 messageText: `Options '${flag1}' and '${flag2}' cannot be combined.`, 887 category: Diagnostics.Options_0_and_1_cannot_be_combined.category, 888 code: Diagnostics.Options_0_and_1_cannot_be_combined.code, 889 file: undefined, 890 start: undefined, 891 length: undefined, 892 }], 893 projects: ["."], 894 buildOptions: { [flag1]: true, [flag2]: true }, 895 watchOptions: undefined, 896 }); 897 }); 898 } 899 900 verifyInvalidCombination("clean", "force"); 901 verifyInvalidCombination("clean", "verbose"); 902 verifyInvalidCombination("clean", "watch"); 903 verifyInvalidCombination("watch", "dry"); 904 }); 905 906 describe("Watch options", () => { 907 it("parse --watchFile", () => { 908 assertParseResult(["--watchFile", "UseFsEvents", "--verbose"], 909 { 910 errors: [], 911 projects: ["."], 912 buildOptions: { verbose: true }, 913 watchOptions: { watchFile: WatchFileKind.UseFsEvents } 914 }); 915 }); 916 917 it("parse --watchDirectory", () => { 918 assertParseResult(["--watchDirectory", "FixedPollingInterval", "--verbose"], 919 { 920 errors: [], 921 projects: ["."], 922 buildOptions: { verbose: true }, 923 watchOptions: { watchDirectory: WatchDirectoryKind.FixedPollingInterval } 924 }); 925 }); 926 927 it("parse --fallbackPolling", () => { 928 assertParseResult(["--fallbackPolling", "PriorityInterval", "--verbose"], 929 { 930 errors: [], 931 projects: ["."], 932 buildOptions: { verbose: true }, 933 watchOptions: { fallbackPolling: PollingWatchKind.PriorityInterval } 934 }); 935 }); 936 937 it("parse --synchronousWatchDirectory", () => { 938 assertParseResult(["--synchronousWatchDirectory", "--verbose"], 939 { 940 errors: [], 941 projects: ["."], 942 buildOptions: { verbose: true }, 943 watchOptions: { synchronousWatchDirectory: true } 944 }); 945 }); 946 947 it("errors on missing argument", () => { 948 assertParseResult(["--verbose", "--fallbackPolling"], 949 { 950 errors: [ 951 { 952 messageText: "Watch option 'fallbackPolling' requires a value of type string.", 953 category: Diagnostics.Watch_option_0_requires_a_value_of_type_1.category, 954 code: Diagnostics.Watch_option_0_requires_a_value_of_type_1.code, 955 file: undefined, 956 start: undefined, 957 length: undefined 958 }, 959 { 960 messageText: "Argument for '--fallbackPolling' option must be: 'fixedinterval', 'priorityinterval', 'dynamicpriority'.", 961 category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category, 962 code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, 963 file: undefined, 964 start: undefined, 965 length: undefined 966 } 967 ], 968 projects: ["."], 969 buildOptions: { verbose: true }, 970 watchOptions: { fallbackPolling: undefined } 971 }); 972 }); 973 974 it("errors on invalid excludeDirectories", () => { 975 assertParseResult(["--excludeDirectories", "**/../*"], 976 { 977 errors: [ 978 { 979 messageText: `File specification cannot contain a parent directory ('..') that appears after a recursive directory wildcard ('**'): '**/../*'.`, 980 category: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.category, 981 code: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.code, 982 file: undefined, 983 start: undefined, 984 length: undefined 985 } 986 ], 987 projects: ["."], 988 buildOptions: {}, 989 watchOptions: { excludeDirectories: [] } 990 }); 991 }); 992 993 it("parse --excludeFiles", () => { 994 assertParseResult(["--excludeFiles", "**/temp/*.ts"], 995 { 996 errors: [], 997 projects: ["."], 998 buildOptions: {}, 999 watchOptions: { excludeFiles: ["**/temp/*.ts"] } 1000 }); 1001 }); 1002 1003 it("errors on invalid excludeFiles", () => { 1004 assertParseResult(["--excludeFiles", "**/../*"], 1005 { 1006 errors: [ 1007 { 1008 messageText: `File specification cannot contain a parent directory ('..') that appears after a recursive directory wildcard ('**'): '**/../*'.`, 1009 category: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.category, 1010 code: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.code, 1011 file: undefined, 1012 start: undefined, 1013 length: undefined 1014 } 1015 ], 1016 projects: ["."], 1017 buildOptions: {}, 1018 watchOptions: { excludeFiles: [] } 1019 }); 1020 }); 1021 }); 1022 }); 1023} 1024