• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1namespace ts {
2    describe("unittests:: config:: tsconfigParsing:: parseConfigFileTextToJson", () => {
3        function assertParseResult(jsonText: string, expectedConfigObject: { config?: any; error?: Diagnostic[] }) {
4            const parsed = parseConfigFileTextToJson("/apath/tsconfig.json", jsonText);
5            assert.equal(JSON.stringify(parsed), JSON.stringify(expectedConfigObject));
6        }
7
8        function assertParseErrorWithExcludesKeyword(jsonText: string) {
9            {
10                const parsed = parseConfigFileTextToJson("/apath/tsconfig.json", jsonText);
11                const parsedCommand = parseJsonConfigFileContent(parsed.config, sys, "tests/cases/unittests");
12                assert.isTrue(parsedCommand.errors && parsedCommand.errors.length === 1 &&
13                    parsedCommand.errors[0].code === Diagnostics.Unknown_option_excludes_Did_you_mean_exclude.code);
14            }
15            {
16                const parsed = parseJsonText("/apath/tsconfig.json", jsonText);
17                const parsedCommand = parseJsonSourceFileConfigFileContent(parsed, sys, "tests/cases/unittests");
18                assert.isTrue(parsedCommand.errors && parsedCommand.errors.length === 1 &&
19                    parsedCommand.errors[0].code === Diagnostics.Unknown_option_excludes_Did_you_mean_exclude.code);
20            }
21        }
22
23        function getParsedCommandJson(jsonText: string, configFileName: string, basePath: string, allFileList: string[]) {
24            const parsed = parseConfigFileTextToJson(configFileName, jsonText);
25            const files = allFileList.reduce((files, value) => (files[value] = "", files), {} as vfs.FileSet);
26            const host: ParseConfigHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ false, { cwd: basePath, files: { "/": {}, ...files } }));
27            return parseJsonConfigFileContent(parsed.config, host, basePath, /*existingOptions*/ undefined, configFileName);
28        }
29
30        function getParsedCommandJsonNode(jsonText: string, configFileName: string, basePath: string, allFileList: string[]) {
31            const parsed = parseJsonText(configFileName, jsonText);
32            const files = allFileList.reduce((files, value) => (files[value] = "", files), {} as vfs.FileSet);
33            const host: ParseConfigHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ false, { cwd: basePath, files: { "/": {}, ...files } }));
34            return parseJsonSourceFileConfigFileContent(parsed, host, basePath, /*existingOptions*/ undefined, configFileName);
35        }
36
37        function assertParseFileList(jsonText: string, configFileName: string, basePath: string, allFileList: string[], expectedFileList: string[]) {
38            {
39                const parsed = getParsedCommandJson(jsonText, configFileName, basePath, allFileList);
40                assert.isTrue(arrayIsEqualTo(parsed.fileNames.sort(), expectedFileList.sort()));
41            }
42            {
43                const parsed = getParsedCommandJsonNode(jsonText, configFileName, basePath, allFileList);
44                assert.isTrue(arrayIsEqualTo(parsed.fileNames.sort(), expectedFileList.sort()));
45            }
46        }
47
48        function assertParseFileDiagnostics(jsonText: string, configFileName: string, basePath: string, allFileList: string[], expectedDiagnosticCode: number, noLocation?: boolean) {
49            {
50                const parsed = getParsedCommandJson(jsonText, configFileName, basePath, allFileList);
51                assert.isTrue(parsed.errors.length >= 0);
52                assert.isTrue(parsed.errors.filter(e => e.code === expectedDiagnosticCode).length > 0, `Expected error code ${expectedDiagnosticCode} to be in ${JSON.stringify(parsed.errors)}`);
53            }
54            {
55                const parsed = getParsedCommandJsonNode(jsonText, configFileName, basePath, allFileList);
56                assert.isTrue(parsed.errors.length >= 0);
57                assert.isTrue(parsed.errors.filter(e => e.code === expectedDiagnosticCode).length > 0, `Expected error code ${expectedDiagnosticCode} to be in ${JSON.stringify(parsed.errors)}`);
58                if (!noLocation) {
59                    assert.isTrue(parsed.errors.filter(e => e.code === expectedDiagnosticCode && e.file && e.start && e.length).length > 0, `Expected error code ${expectedDiagnosticCode} to be in ${JSON.stringify(parsed.errors)} with location information`);
60                }
61            }
62        }
63
64        function assertParseFileDiagnosticsExclusion(jsonText: string, configFileName: string, basePath: string, allFileList: string[], expectedExcludedDiagnosticCode: number) {
65            {
66                const parsed = getParsedCommandJson(jsonText, configFileName, basePath, allFileList);
67                assert.isTrue(parsed.errors.length >= 0);
68                assert.isTrue(parsed.errors.findIndex(e => e.code === expectedExcludedDiagnosticCode) === -1, `Expected error code ${expectedExcludedDiagnosticCode} to not be in ${JSON.stringify(parsed.errors)}`);
69            }
70            {
71                const parsed = getParsedCommandJsonNode(jsonText, configFileName, basePath, allFileList);
72                assert.isTrue(parsed.errors.length >= 0);
73                assert.isTrue(parsed.errors.findIndex(e => e.code === expectedExcludedDiagnosticCode) === -1, `Expected error code ${expectedExcludedDiagnosticCode} to not be in ${JSON.stringify(parsed.errors)}`);
74            }
75        }
76
77        it("returns empty config for file with only whitespaces", () => {
78            assertParseResult("", { config : {} });
79            assertParseResult(" ", { config : {} });
80        });
81
82        it("returns empty config for file with comments only", () => {
83            assertParseResult("// Comment", { config: {} });
84            assertParseResult("/* Comment*/", { config: {} });
85        });
86
87        it("returns empty config when config is empty object", () => {
88            assertParseResult("{}", { config: {} });
89        });
90
91        it("returns config object without comments", () => {
92            assertParseResult(
93                `{ // Excluded files
94                    "exclude": [
95                        // Exclude d.ts
96                        "file.d.ts"
97                    ]
98                }`, { config: { exclude: ["file.d.ts"] } });
99
100            assertParseResult(
101                `{
102                    /* Excluded
103                         Files
104                    */
105                    "exclude": [
106                        /* multiline comments can be in the middle of a line */"file.d.ts"
107                    ]
108                }`, { config: { exclude: ["file.d.ts"] } });
109        });
110
111        it("keeps string content untouched", () => {
112            assertParseResult(
113                `{
114                    "exclude": [
115                        "xx//file.d.ts"
116                    ]
117                }`, { config: { exclude: ["xx//file.d.ts"] } });
118            assertParseResult(
119                `{
120                    "exclude": [
121                        "xx/*file.d.ts*/"
122                    ]
123                }`, { config: { exclude: ["xx/*file.d.ts*/"] } });
124        });
125
126        it("handles escaped characters in strings correctly", () => {
127            assertParseResult(
128                `{
129                    "exclude": [
130                        "xx\\"//files"
131                    ]
132                }`, { config: { exclude: ["xx\"//files"] } });
133
134            assertParseResult(
135                `{
136                    "exclude": [
137                        "xx\\\\" // end of line comment
138                    ]
139                }`, { config: { exclude: ["xx\\"] } });
140        });
141
142        it("returns object with error when json is invalid", () => {
143            const parsed = parseConfigFileTextToJson("/apath/tsconfig.json", "invalid");
144            assert.deepEqual(parsed.config, {});
145            const expected = createCompilerDiagnostic(Diagnostics._0_expected, "{");
146            const error = parsed.error!;
147            assert.equal(error.messageText, expected.messageText);
148            assert.equal(error.category, expected.category);
149            assert.equal(error.code, expected.code);
150            assert.equal(error.start, 0);
151            assert.equal(error.length, "invalid".length);
152        });
153
154        it("returns object when users correctly specify library", () => {
155            assertParseResult(
156                `{
157                    "compilerOptions": {
158                        "lib": ["es5"]
159                    }
160                }`, {
161                    config: { compilerOptions: { lib: ["es5"] } }
162                });
163
164            assertParseResult(
165                `{
166                    "compilerOptions": {
167                        "lib": ["es5", "es6"]
168                    }
169                }`, {
170                    config: { compilerOptions: { lib: ["es5", "es6"] } }
171                });
172        });
173
174        it("returns error when tsconfig have excludes", () => {
175            assertParseErrorWithExcludesKeyword(
176                `{
177                    "compilerOptions": {
178                        "lib": ["es5"]
179                    },
180                    "excludes": [
181                        "foge.ts"
182                    ]
183                }`);
184        });
185
186        it("ignore dotted files and folders", () => {
187            assertParseFileList(
188                `{}`,
189                "tsconfig.json",
190                "/apath",
191                ["/apath/test.ts", "/apath/.git/a.ts", "/apath/.b.ts", "/apath/..c.ts"],
192                ["/apath/test.ts"]
193            );
194        });
195
196        it("allow dotted files and folders when explicitly requested", () => {
197            assertParseFileList(
198                `{
199                    "files": ["/apath/.git/a.ts", "/apath/.b.ts", "/apath/..c.ts"]
200                }`,
201                "tsconfig.json",
202                "/apath",
203                ["/apath/test.ts", "/apath/.git/a.ts", "/apath/.b.ts", "/apath/..c.ts"],
204                ["/apath/.git/a.ts", "/apath/.b.ts", "/apath/..c.ts"]
205            );
206        });
207
208        it("exclude outDir unless overridden", () => {
209            const tsconfigWithoutExclude =
210            `{
211                "compilerOptions": {
212                    "outDir": "bin"
213                }
214            }`;
215            const tsconfigWithExclude =
216            `{
217                "compilerOptions": {
218                    "outDir": "bin"
219                },
220                "exclude": [ "obj" ]
221            }`;
222            const rootDir = "/";
223            const allFiles = ["/bin/a.ts", "/b.ts"];
224            const expectedFiles = ["/b.ts"];
225            assertParseFileList(tsconfigWithoutExclude, "tsconfig.json", rootDir, allFiles, expectedFiles);
226            assertParseFileList(tsconfigWithExclude, "tsconfig.json", rootDir, allFiles, allFiles);
227        });
228
229        it("exclude declarationDir unless overridden", () => {
230            const tsconfigWithoutExclude =
231            `{
232                "compilerOptions": {
233                    "declarationDir": "declarations"
234                }
235            }`;
236            const tsconfigWithExclude =
237            `{
238                "compilerOptions": {
239                    "declarationDir": "declarations"
240                },
241                "exclude": [ "types" ]
242            }`;
243
244            const rootDir = "/";
245            const allFiles = ["/declarations/a.d.ts", "/a.ts"];
246            const expectedFiles = ["/a.ts"];
247
248            assertParseFileList(tsconfigWithoutExclude, "tsconfig.json", rootDir, allFiles, expectedFiles);
249            assertParseFileList(tsconfigWithExclude, "tsconfig.json", rootDir, allFiles, allFiles);
250        });
251
252        it("implicitly exclude common package folders", () => {
253            assertParseFileList(
254                `{}`,
255                "tsconfig.json",
256                "/",
257                ["/node_modules/a.ts", "/bower_components/b.ts", "/jspm_packages/c.ts", "/d.ts", "/folder/e.ts"],
258                ["/d.ts", "/folder/e.ts"]
259            );
260        });
261
262        it("parse and re-emit tsconfig.json file with diagnostics", () => {
263            const content = `{
264                "compilerOptions": {
265                    "allowJs": true
266                    // Some comments
267                    "outDir": "bin"
268                }
269                "files": ["file1.ts"]
270            }`;
271            const result = parseJsonText("config.json", content);
272            const diagnostics = result.parseDiagnostics;
273            const configJsonObject = convertToObject(result, diagnostics);
274            const expectedResult = {
275                compilerOptions: {
276                    allowJs: true,
277                    outDir: "bin"
278                },
279                files: ["file1.ts"]
280            };
281            assert.isTrue(diagnostics.length === 2);
282            assert.equal(JSON.stringify(configJsonObject), JSON.stringify(expectedResult));
283        });
284
285        it("generates errors for empty files list", () => {
286            const content = `{
287                "files": []
288            }`;
289            assertParseFileDiagnostics(content,
290                "/apath/tsconfig.json",
291                "tests/cases/unittests",
292                ["/apath/a.ts"],
293                Diagnostics.The_files_list_in_config_file_0_is_empty.code);
294        });
295
296        it("generates errors for empty files list when no references are provided", () => {
297            const content = `{
298                "files": [],
299                "references": []
300            }`;
301            assertParseFileDiagnostics(content,
302                "/apath/tsconfig.json",
303                "tests/cases/unittests",
304                ["/apath/a.ts"],
305                Diagnostics.The_files_list_in_config_file_0_is_empty.code);
306        });
307
308        it("does not generate errors for empty files list when one or more references are provided", () => {
309            const content = `{
310                "files": [],
311                "references": [{ "path": "/apath" }]
312            }`;
313            assertParseFileDiagnosticsExclusion(content,
314                "/apath/tsconfig.json",
315                "tests/cases/unittests",
316                ["/apath/a.ts"],
317                Diagnostics.The_files_list_in_config_file_0_is_empty.code);
318        });
319
320        it("generates errors for directory with no .ts files", () => {
321            const content = `{
322            }`;
323            assertParseFileDiagnostics(content,
324                "/apath/tsconfig.json",
325                "tests/cases/unittests",
326                ["/apath/a.js"],
327                Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code,
328                /*noLocation*/ true);
329        });
330
331        it("generates errors for empty directory", () => {
332            const content = `{
333                "compilerOptions": {
334                    "allowJs": true
335                }
336            }`;
337            assertParseFileDiagnostics(content,
338                "/apath/tsconfig.json",
339                "tests/cases/unittests",
340                [],
341                Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code,
342                /*noLocation*/ true);
343        });
344
345        it("generates errors for empty include", () => {
346            const content = `{
347                "include": []
348            }`;
349            assertParseFileDiagnostics(content,
350                "/apath/tsconfig.json",
351                "tests/cases/unittests",
352                ["/apath/a.ts"],
353                Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code,
354                /*noLocation*/ true);
355        });
356
357        it("generates errors for includes with outDir", () => {
358            const content = `{
359                "compilerOptions": {
360                    "outDir": "./"
361                },
362                "include": ["**/*"]
363            }`;
364            assertParseFileDiagnostics(content,
365                "/apath/tsconfig.json",
366                "tests/cases/unittests",
367                ["/apath/a.ts"],
368                Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code,
369                /*noLocation*/ true);
370        });
371
372
373        it("generates errors for when invalid comment type present in tsconfig", () => {
374            const jsonText = `{
375              "compilerOptions": {
376                ## this comment does cause issues
377                "types" : [
378                ]
379              }
380            }`;
381            const parsed = getParsedCommandJsonNode(jsonText, "/apath/tsconfig.json", "tests/cases/unittests", ["/apath/a.ts"]);
382            assert.isTrue(parsed.errors.length >= 0);
383        });
384
385        it("generates errors when files is not string", () => {
386            assertParseFileDiagnostics(
387                JSON.stringify({
388                    files: [{
389                        compilerOptions: {
390                            experimentalDecorators: true,
391                            allowJs: true
392                        }
393                    }]
394                }),
395                "/apath/tsconfig.json",
396                "tests/cases/unittests",
397                ["/apath/a.ts"],
398                Diagnostics.Compiler_option_0_requires_a_value_of_type_1.code,
399                /*noLocation*/ true);
400        });
401
402        it("generates errors when include is not string", () => {
403            assertParseFileDiagnostics(
404                JSON.stringify({
405                    include: [
406                        ["./**/*.ts"]
407                    ]
408                }),
409                "/apath/tsconfig.json",
410                "tests/cases/unittests",
411                ["/apath/a.ts"],
412                Diagnostics.Compiler_option_0_requires_a_value_of_type_1.code,
413                /*noLocation*/ true);
414        });
415
416        it("parses wildcard directories even when parent directories have dots", () => {
417            const parsed = parseConfigFileTextToJson("/foo.bar/tsconfig.json", JSON.stringify({
418                include: ["src"]
419            }));
420
421            const parsedCommand = parseJsonConfigFileContent(parsed.config, sys, "/foo.bar");
422            assert.deepEqual(parsedCommand.wildcardDirectories, { "/foo.bar/src": WatchDirectoryFlags.Recursive });
423        });
424
425        it("correctly parses wild card directories from implicit glob when two keys differ only in directory seperator", () => {
426            const parsed = parseConfigFileTextToJson("/foo.bar/tsconfig.json", JSON.stringify({
427                include: ["./", "./**/*.json"]
428            }));
429
430            const parsedCommand = parseJsonConfigFileContent(parsed.config, sys, "/foo");
431            assert.deepEqual(parsedCommand.wildcardDirectories, { "/foo": WatchDirectoryFlags.Recursive });
432        });
433    });
434}
435