• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1namespace ts {
2    function createFileSystem(ignoreCase: boolean, cwd: string, root: string) {
3        return new vfs.FileSystem(ignoreCase, {
4            cwd,
5            files: {
6                [root]: {
7                    "dev/node_modules/config-box/package.json": JSON.stringify({
8                        name: "config-box",
9                        version: "1.0.0",
10                        tsconfig: "./strict.json"
11                    }),
12                    "dev/node_modules/config-box/strict.json": JSON.stringify({
13                        compilerOptions: {
14                            strict: true,
15                        }
16                    }),
17                    "dev/node_modules/config-box/unstrict.json": JSON.stringify({
18                        compilerOptions: {
19                            strict: false,
20                        }
21                    }),
22                    "dev/tsconfig.extendsBox.json": JSON.stringify({
23                        extends: "config-box",
24                        files: [
25                            "main.ts",
26                        ]
27                    }),
28                    "dev/tsconfig.extendsStrict.json": JSON.stringify({
29                        extends: "config-box/strict",
30                        files: [
31                            "main.ts",
32                        ]
33                    }),
34                    "dev/tsconfig.extendsUnStrict.json": JSON.stringify({
35                        extends: "config-box/unstrict",
36                        files: [
37                            "main.ts",
38                        ]
39                    }),
40                    "dev/tsconfig.extendsStrictExtension.json": JSON.stringify({
41                        extends: "config-box/strict.json",
42                        files: [
43                            "main.ts",
44                        ]
45                    }),
46                    "dev/node_modules/config-box-implied/package.json": JSON.stringify({
47                        name: "config-box-implied",
48                        version: "1.0.0",
49                    }),
50                    "dev/node_modules/config-box-implied/tsconfig.json": JSON.stringify({
51                        compilerOptions: {
52                            strict: true,
53                        }
54                    }),
55                    "dev/node_modules/config-box-implied/unstrict/tsconfig.json": JSON.stringify({
56                        compilerOptions: {
57                            strict: false,
58                        }
59                    }),
60                    "dev/tsconfig.extendsBoxImplied.json": JSON.stringify({
61                        extends: "config-box-implied",
62                        files: [
63                            "main.ts",
64                        ]
65                    }),
66                    "dev/tsconfig.extendsBoxImpliedUnstrict.json": JSON.stringify({
67                        extends: "config-box-implied/unstrict",
68                        files: [
69                            "main.ts",
70                        ]
71                    }),
72                    "dev/tsconfig.extendsBoxImpliedUnstrictExtension.json": JSON.stringify({
73                        extends: "config-box-implied/unstrict/tsconfig",
74                        files: [
75                            "main.ts",
76                        ]
77                    }),
78                    "dev/tsconfig.extendsBoxImpliedPath.json": JSON.stringify({
79                        extends: "config-box-implied/tsconfig.json",
80                        files: [
81                            "main.ts",
82                        ]
83                    }),
84                    "dev/tsconfig.json": JSON.stringify({
85                        extends: "./configs/base",
86                        files: [
87                            "main.ts",
88                            "supplemental.ts"
89                        ]
90                    }),
91                    "dev/tsconfig.nostrictnull.json": JSON.stringify({
92                        extends: "./tsconfig",
93                        compilerOptions: {
94                            strictNullChecks: false
95                        }
96                    }),
97                    "dev/configs/base.json": JSON.stringify({
98                        compilerOptions: {
99                            allowJs: true,
100                            noImplicitAny: true,
101                            strictNullChecks: true
102                        }
103                    }),
104                    "dev/configs/tests.json": JSON.stringify({
105                        compilerOptions: {
106                            preserveConstEnums: true,
107                            removeComments: false,
108                            sourceMap: true
109                        },
110                        exclude: [
111                            "../tests/baselines",
112                            "../tests/scenarios"
113                        ],
114                        include: [
115                            "../tests/**/*.ts"
116                        ]
117                    }),
118                    "dev/circular.json": JSON.stringify({
119                        extends: "./circular2",
120                        compilerOptions: {
121                            module: "amd"
122                        }
123                    }),
124                    "dev/circular2.json": JSON.stringify({
125                        extends: "./circular",
126                        compilerOptions: {
127                            module: "commonjs"
128                        }
129                    }),
130                    "dev/missing.json": JSON.stringify({
131                        extends: "./missing2",
132                        compilerOptions: {
133                            types: []
134                        }
135                    }),
136                    "dev/failure.json": JSON.stringify({
137                        extends: "./failure2.json",
138                        compilerOptions: {
139                            typeRoots: []
140                        }
141                    }),
142                    "dev/failure2.json": JSON.stringify({
143                        excludes: ["*.js"]
144                    }),
145                    "dev/configs/first.json": JSON.stringify({
146                        extends: "./base",
147                        compilerOptions: {
148                            module: "commonjs"
149                        },
150                        files: ["../main.ts"]
151                    }),
152                    "dev/configs/second.json": JSON.stringify({
153                        extends: "./base",
154                        compilerOptions: {
155                            module: "amd"
156                        },
157                        include: ["../supplemental.*"]
158                    }),
159                    "dev/configs/third.json": JSON.stringify({
160                        extends: "./second",
161                        compilerOptions: {
162                            module: null // eslint-disable-line no-null/no-null
163                        },
164                        include: ["../supplemental.*"]
165                    }),
166                    "dev/configs/fourth.json": JSON.stringify({
167                        extends: "./third",
168                        compilerOptions: {
169                            module: "system"
170                        },
171                        include: null, // eslint-disable-line no-null/no-null
172                        files: ["../main.ts"]
173                    }),
174                    "dev/configs/fifth.json": JSON.stringify({
175                        extends: "./fourth",
176                        include: ["../tests/utils.ts"],
177                        files: []
178                    }),
179                    "dev/extends.json": JSON.stringify({ extends: 42 }),
180                    "dev/extends2.json": JSON.stringify({ extends: "configs/base" }),
181                    "dev/main.ts": "",
182                    "dev/supplemental.ts": "",
183                    "dev/tests/unit/spec.ts": "",
184                    "dev/tests/utils.ts": "",
185                    "dev/tests/scenarios/first.json": "",
186                    "dev/tests/baselines/first/output.ts": ""
187                }
188            }
189        });
190    }
191
192    const caseInsensitiveBasePath = "c:/dev/";
193    const caseInsensitiveHost = new fakes.ParseConfigHost(createFileSystem(/*ignoreCase*/ true, caseInsensitiveBasePath, "c:/"));
194
195    const caseSensitiveBasePath = "/dev/";
196    const caseSensitiveHost = new fakes.ParseConfigHost(createFileSystem(/*ignoreCase*/ false, caseSensitiveBasePath, "/"));
197
198    function verifyDiagnostics(actual: Diagnostic[], expected: { code: number; messageText: string; }[]) {
199        assert.isTrue(expected.length === actual.length, `Expected error: ${JSON.stringify(expected)}. Actual error: ${JSON.stringify(actual)}.`);
200        for (let i = 0; i < actual.length; i++) {
201            const actualError = actual[i];
202            const expectedError = expected[i];
203            assert.equal(actualError.code, expectedError.code, "Error code mismatch");
204            assert.equal(actualError.category, DiagnosticCategory.Error, "Category mismatch"); // Should always be error
205            assert.equal(flattenDiagnosticMessageText(actualError.messageText, "\n"), expectedError.messageText);
206        }
207    }
208
209    describe("unittests:: config:: configurationExtension", () => {
210        forEach<[string, string, fakes.ParseConfigHost], void>([
211            ["under a case insensitive host", caseInsensitiveBasePath, caseInsensitiveHost],
212            ["under a case sensitive host", caseSensitiveBasePath, caseSensitiveHost]
213        ], ([testName, basePath, host]) => {
214            function getParseCommandLine(entry: string) {
215                const {config, error} = readConfigFile(entry, name => host.readFile(name));
216                assert(config && !error, flattenDiagnosticMessageText(error && error.messageText, "\n"));
217                return parseJsonConfigFileContent(config, host, basePath, {}, entry);
218            }
219
220            function getParseCommandLineJsonSourceFile(entry: string) {
221                const jsonSourceFile = readJsonConfigFile(entry, name => host.readFile(name));
222                assert(jsonSourceFile.endOfFileToken && !jsonSourceFile.parseDiagnostics.length, flattenDiagnosticMessageText(jsonSourceFile.parseDiagnostics[0] && jsonSourceFile.parseDiagnostics[0].messageText, "\n"));
223                return {
224                    jsonSourceFile,
225                    parsed: parseJsonSourceFileConfigFileContent(jsonSourceFile, host, basePath, {}, entry)
226                };
227            }
228
229            function testSuccess(name: string, entry: string, expected: CompilerOptions, expectedFiles: string[]) {
230                expected.configFilePath = entry;
231                it(name, () => {
232                    const parsed = getParseCommandLine(entry);
233                    assert(!parsed.errors.length, flattenDiagnosticMessageText(parsed.errors[0] && parsed.errors[0].messageText, "\n"));
234                    assert.deepEqual(parsed.options, expected);
235                    assert.deepEqual(parsed.fileNames, expectedFiles);
236                });
237
238                it(name + " with jsonSourceFile", () => {
239                    const { parsed, jsonSourceFile } = getParseCommandLineJsonSourceFile(entry);
240                    assert(!parsed.errors.length, flattenDiagnosticMessageText(parsed.errors[0] && parsed.errors[0].messageText, "\n"));
241                    assert.deepEqual(parsed.options, expected);
242                    assert.equal(parsed.options.configFile, jsonSourceFile);
243                    assert.deepEqual(parsed.fileNames, expectedFiles);
244                });
245            }
246
247            function testFailure(name: string, entry: string, expectedDiagnostics: { code: number; messageText: string; }[]) {
248                it(name, () => {
249                    const parsed = getParseCommandLine(entry);
250                    verifyDiagnostics(parsed.errors, expectedDiagnostics);
251                });
252
253                it(name + " with jsonSourceFile", () => {
254                    const { parsed } = getParseCommandLineJsonSourceFile(entry);
255                    verifyDiagnostics(parsed.errors, expectedDiagnostics);
256                });
257            }
258
259            describe(testName, () => {
260                testSuccess("can resolve an extension with a base extension", "tsconfig.json", {
261                    allowJs: true,
262                    noImplicitAny: true,
263                    strictNullChecks: true,
264                }, [
265                    combinePaths(basePath, "main.ts"),
266                    combinePaths(basePath, "supplemental.ts"),
267                ]);
268
269                testSuccess("can resolve an extension with a base extension that overrides options", "tsconfig.nostrictnull.json", {
270                    allowJs: true,
271                    noImplicitAny: true,
272                    strictNullChecks: false,
273                }, [
274                    combinePaths(basePath, "main.ts"),
275                    combinePaths(basePath, "supplemental.ts"),
276                ]);
277
278                testFailure("can report errors on circular imports", "circular.json", [
279                    {
280                        code: 18000,
281                        messageText: `Circularity detected while resolving configuration: ${[combinePaths(basePath, "circular.json"), combinePaths(basePath, "circular2.json"), combinePaths(basePath, "circular.json")].join(" -> ")}`
282                    }
283                ]);
284
285                testFailure("can report missing configurations", "missing.json", [{
286                    code: 6053,
287                    messageText: `File './missing2' not found.`
288                }]);
289
290                testFailure("can report errors in extended configs", "failure.json", [{
291                    code: 6114,
292                    messageText: `Unknown option 'excludes'. Did you mean 'exclude'?`
293                }]);
294
295                testFailure("can error when 'extends' is not a string", "extends.json", [{
296                    code: 5024,
297                    messageText: `Compiler option 'extends' requires a value of type string.`
298                }]);
299
300                testSuccess("can overwrite compiler options using extended 'null'", "configs/third.json", {
301                    allowJs: true,
302                    noImplicitAny: true,
303                    strictNullChecks: true,
304                    module: undefined // Technically, this is distinct from the key never being set; but within the compiler we don't make the distinction
305                }, [
306                    combinePaths(basePath, "supplemental.ts")
307                ]);
308
309                testSuccess("can overwrite top-level options using extended 'null'", "configs/fourth.json", {
310                    allowJs: true,
311                    noImplicitAny: true,
312                    strictNullChecks: true,
313                    module: ModuleKind.System
314                }, [
315                    combinePaths(basePath, "main.ts")
316                ]);
317
318                testSuccess("can overwrite top-level files using extended []", "configs/fifth.json", {
319                    allowJs: true,
320                    noImplicitAny: true,
321                    strictNullChecks: true,
322                    module: ModuleKind.System
323                }, [
324                    combinePaths(basePath, "tests/utils.ts")
325                ]);
326
327                describe("finding extended configs from node_modules", () => {
328                    testSuccess("can lookup via tsconfig field", "tsconfig.extendsBox.json", { strict: true }, [combinePaths(basePath, "main.ts")]);
329                    testSuccess("can lookup via package-relative path", "tsconfig.extendsStrict.json", { strict: true }, [combinePaths(basePath, "main.ts")]);
330                    testSuccess("can lookup via non-redirected-to package-relative path", "tsconfig.extendsUnStrict.json", { strict: false }, [combinePaths(basePath, "main.ts")]);
331                    testSuccess("can lookup via package-relative path with extension", "tsconfig.extendsStrictExtension.json", { strict: true }, [combinePaths(basePath, "main.ts")]);
332                    testSuccess("can lookup via an implicit tsconfig", "tsconfig.extendsBoxImplied.json", { strict: true }, [combinePaths(basePath, "main.ts")]);
333                    testSuccess("can lookup via an implicit tsconfig in a package-relative directory", "tsconfig.extendsBoxImpliedUnstrict.json", { strict: false }, [combinePaths(basePath, "main.ts")]);
334                    testSuccess("can lookup via an implicit tsconfig in a package-relative directory with name", "tsconfig.extendsBoxImpliedUnstrictExtension.json", { strict: false }, [combinePaths(basePath, "main.ts")]);
335                    testSuccess("can lookup via an implicit tsconfig in a package-relative directory with extension", "tsconfig.extendsBoxImpliedPath.json", { strict: true }, [combinePaths(basePath, "main.ts")]);
336                });
337
338                it("adds extendedSourceFiles only once", () => {
339                    const sourceFile = readJsonConfigFile("configs/fourth.json", (path) => host.readFile(path));
340                    const dir = combinePaths(basePath, "configs");
341                    const expected = [
342                        combinePaths(dir, "third.json"),
343                        combinePaths(dir, "second.json"),
344                        combinePaths(dir, "base.json"),
345                    ];
346                    parseJsonSourceFileConfigFileContent(sourceFile, host, dir, {}, "fourth.json");
347                    assert.deepEqual(sourceFile.extendedSourceFiles, expected);
348                    parseJsonSourceFileConfigFileContent(sourceFile, host, dir, {}, "fourth.json");
349                    assert.deepEqual(sourceFile.extendedSourceFiles, expected);
350                });
351            });
352        });
353    });
354}
355