• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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