From 167d318a1375a467877bc6d6672a0015e3e684bd Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Mon, 25 Jul 2016 14:48:41 -0700 Subject: [PATCH 1/2] Draft of configuration inheritance --- Jakefile.js | 1 + src/compiler/commandLineParser.ts | 81 +++++- src/compiler/core.ts | 30 ++- src/compiler/diagnosticMessages.json | 9 + src/compiler/types.ts | 2 + src/harness/harness.ts | 3 +- src/harness/projectsRunner.ts | 5 + src/harness/rwcRunner.ts | 1 + src/harness/tsconfig.json | 1 + .../unittests/configurationExtension.ts | 230 ++++++++++++++++++ src/harness/virtualFileSystem.ts | 25 +- 11 files changed, 374 insertions(+), 14 deletions(-) create mode 100644 src/harness/unittests/configurationExtension.ts diff --git a/Jakefile.js b/Jakefile.js index f63275284d..d6a2f253ac 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -155,6 +155,7 @@ var harnessSources = harnessCoreSources.concat([ "moduleResolution.ts", "tsconfigParsing.ts", "commandLineParsing.ts", + "configurationExtension.ts", "convertCompilerOptionsFromJson.ts", "convertTypingOptionsFromJson.ts", "tsserverProjectSystem.ts", diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 7e2c6eb8d3..41a126f234 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -700,12 +700,54 @@ namespace ts { * @param basePath A root directory to resolve relative path entries in the config * file to. e.g. outDir */ - export function parseJsonConfigFileContent(json: any, host: ParseConfigHost, basePath: string, existingOptions: CompilerOptions = {}, configFileName?: string): ParsedCommandLine { + export function parseJsonConfigFileContent(json: any, host: ParseConfigHost, basePath: string, existingOptions: CompilerOptions = {}, configFileName?: string, resolutionStack: Path[] = []): ParsedCommandLine { const errors: Diagnostic[] = []; - const compilerOptions: CompilerOptions = convertCompilerOptionsFromJsonWorker(json["compilerOptions"], basePath, errors, configFileName); - const options = extend(existingOptions, compilerOptions); + const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames); + const resolvedPath = toPath(configFileName || "", basePath, getCanonicalFileName); + if (resolutionStack.indexOf(resolvedPath) >= 0) { + return { + options: {}, + fileNames: [], + typingOptions: {}, + raw: json, + errors: [createCompilerDiagnostic(Diagnostics.Circularity_detected_while_resolving_configuration_Colon_0, [...resolutionStack, resolvedPath].join(" -> "))], + wildcardDirectories: {} + }; + } + + let options: CompilerOptions = convertCompilerOptionsFromJsonWorker(json["compilerOptions"], basePath, errors, configFileName); const typingOptions: TypingOptions = convertTypingOptionsFromJsonWorker(json["typingOptions"], basePath, errors, configFileName); + if (json["extends"]) { + let [include, exclude, files, baseOptions]: [string[], string[], string[], CompilerOptions] = [undefined, undefined, undefined, {}]; + if (typeof json["extends"] === "string") { + [include, exclude, files, baseOptions] = (tryExtendsName(json["extends"]) || [include, exclude, files, baseOptions]); + } + else if (typeof json["extends"] === "object" && json["extends"].length) { + for (const name of json["extends"]) { + const [tempinclude, tempexclude, tempfiles, tempBase]: [string[], string[], string[], CompilerOptions] = (tryExtendsName(name) || [include, exclude, files, baseOptions]); + include = tempinclude || include; + exclude = tempexclude || exclude; + files = tempfiles || files; + baseOptions = assign({}, baseOptions, tempBase); + } + } + else { + errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "extends", "string or string[]")); + } + if (include && !json["include"]) { + json["include"] = include; + } + if (exclude && !json["exclude"]) { + json["exclude"] = exclude; + } + if (files && !json["files"]) { + json["files"] = files; + } + options = assign({}, baseOptions, options); + } + + options = extend(existingOptions, options); options.configFilePath = configFileName; const { fileNames, wildcardDirectories } = getFileNames(errors); @@ -719,6 +761,39 @@ namespace ts { wildcardDirectories }; + function tryExtendsName(extendedConfig: string): [string[], string[], string[], CompilerOptions] { + // If the path isn't a rooted or relative path, don't try to resolve it (we reserve the right to special case module-id like paths in the future) + if (!(isRootedDiskPath(extendedConfig) || startsWith(normalizeSlashes(extendedConfig), "./") || startsWith(normalizeSlashes(extendedConfig), "../"))) { + errors.push(createCompilerDiagnostic(Diagnostics.The_path_in_an_extends_options_must_be_relative_or_rooted)); + return; + } + let extendedConfigPath = toPath(extendedConfig, basePath, getCanonicalFileName); + if (!host.fileExists(extendedConfigPath) && !endsWith(extendedConfigPath, ".json")) { + extendedConfigPath = `${extendedConfigPath}.json` as Path; + if (!host.fileExists(extendedConfigPath)) { + errors.push(createCompilerDiagnostic(Diagnostics.File_0_does_not_exist, extendedConfig)); + return; + } + } + const extendedResult = readConfigFile(extendedConfigPath, path => host.readFile(path)); + if (extendedResult.error) { + errors.push(extendedResult.error); + return; + } + const extendedDirname = getDirectoryPath(extendedConfigPath); + const relativeDifference = convertToRelativePath(extendedDirname, basePath, getCanonicalFileName); + const updatePath: (path: string) => string = path => isRootedDiskPath(path) ? path : combinePaths(relativeDifference, path); + // Merge configs (copy the resolution stack so it is never reused between branches in potential diamond-problem scenarios) + const result = parseJsonConfigFileContent(extendedResult.config, host, extendedDirname, /*existingOptions*/undefined, getBaseFileName(extendedConfigPath), resolutionStack.concat([resolvedPath])); + errors.push(...result.errors); + const [include, exclude, files] = map(["include", "exclude", "files"], key => { + if (!json[key] && extendedResult.config[key]) { + return map(extendedResult.config[key], updatePath); + } + }); + return [include, exclude, files, result.options]; + } + function getFileNames(errors: Diagnostic[]): ExpandResult { let fileNames: string[]; if (hasProperty(json, "files")) { diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 709a331e02..089ed96751 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -171,6 +171,20 @@ namespace ts { return result; } + export function mapObject(object: Map, f: (key: string, x: T) => [string, U]): Map { + let result: Map = {}; + if (object) { + result = {}; + for (const v of getKeys(object)) { + const [key, value]: [string, U] = f(v, object[v]) || [undefined, undefined]; + if (key !== undefined) { + result[key] = value; + } + } + } + return result; + } + export function concatenate(array1: T[], array2: T[]): T[] { if (!array2 || !array2.length) return array1; if (!array1 || !array1.length) return array2; @@ -357,6 +371,20 @@ namespace ts { return result; } + export function assign, T2, T3>(t: T1, arg1: T2, arg2: T3): T1 & T2 & T3; + export function assign, T2>(t: T1, arg1: T2): T1 & T2; + export function assign>(t: T1, ...args: any[]): any; + export function assign>(t: T1, ...args: any[]) { + for (const arg of args) { + for (const p of getKeys(arg)) { + if (hasProperty(arg, p)) { + t[p] = arg[p]; + } + } + } + return t; + } + export function forEachValue(map: Map, callback: (value: T) => U): U { let result: U; for (const id in map) { @@ -941,7 +969,7 @@ namespace ts { * [^./] # matches everything up to the first . character (excluding directory seperators) * (\\.(?!min\\.js$))? # matches . characters but not if they are part of the .min.js file extension */ - const singleAsteriskRegexFragmentFiles = "([^./]|(\\.(?!min\\.js$))?)*"; + const singleAsteriskRegexFragmentFiles = "([^./]|(\\.(?!min\\.js$))?)*"; const singleAsteriskRegexFragmentOther = "[^/]*"; export function getRegularExpressionForWildcard(specs: string[], basePath: string, usage: "files" | "directories" | "exclude") { diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 8126d5c605..fedaf02b4a 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -3031,5 +3031,14 @@ "Unknown typing option '{0}'.": { "category": "Error", "code": 17010 + }, + + "Circularity detected while resolving configuration: {0}": { + "category": "Error", + "code": 18000 + }, + "The path in an 'extends' options must be relative or rooted.": { + "category": "Error", + "code": 18001 } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 072209aa49..95e2e358e2 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1693,6 +1693,8 @@ namespace ts { * @param path The path to test. */ fileExists(path: string): boolean; + + readFile(path: string): string; } export interface WriteFileCallback { diff --git a/src/harness/harness.ts b/src/harness/harness.ts index f27e7e1c17..6649463edb 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -1540,7 +1540,8 @@ namespace Harness { const parseConfigHost: ts.ParseConfigHost = { useCaseSensitiveFileNames: false, readDirectory: (name) => [], - fileExists: (name) => true + fileExists: (name) => true, + readFile: (name) => ts.forEach(testUnitData, data => data.name.toLowerCase() === name.toLowerCase() ? data.content : undefined) }; // check if project has tsconfig.json in the list of files diff --git a/src/harness/projectsRunner.ts b/src/harness/projectsRunner.ts index 038b2dbe35..64ed9ff79a 100644 --- a/src/harness/projectsRunner.ts +++ b/src/harness/projectsRunner.ts @@ -222,6 +222,7 @@ class ProjectRunner extends RunnerBase { useCaseSensitiveFileNames: Harness.IO.useCaseSensitiveFileNames(), fileExists, readDirectory, + readFile }; const configParseResult = ts.parseJsonConfigFileContent(configObject, configParseHost, ts.getDirectoryPath(configFileName), compilerOptions); if (configParseResult.errors.length > 0) { @@ -295,6 +296,10 @@ class ProjectRunner extends RunnerBase { return Harness.IO.fileExists(getFileNameInTheProjectTest(fileName)); } + function readFile(fileName: string): string { + return Harness.IO.readFile(getFileNameInTheProjectTest(fileName)); + } + function getSourceFileText(fileName: string): string { let text: string = undefined; try { diff --git a/src/harness/rwcRunner.ts b/src/harness/rwcRunner.ts index 1266ffa5d3..57e6705318 100644 --- a/src/harness/rwcRunner.ts +++ b/src/harness/rwcRunner.ts @@ -79,6 +79,7 @@ namespace RWC { useCaseSensitiveFileNames: Harness.IO.useCaseSensitiveFileNames(), fileExists: Harness.IO.fileExists, readDirectory: Harness.IO.readDirectory, + readFile: Harness.IO.readFile }; const configParseResult = ts.parseJsonConfigFileContent(parsedTsconfigFileContents.config, configParseHost, ts.getDirectoryPath(tsconfigFile.path)); fileNames = configParseResult.fileNames; diff --git a/src/harness/tsconfig.json b/src/harness/tsconfig.json index 3b9025c27c..11824e16d9 100644 --- a/src/harness/tsconfig.json +++ b/src/harness/tsconfig.json @@ -86,6 +86,7 @@ "./unittests/moduleResolution.ts", "./unittests/tsconfigParsing.ts", "./unittests/commandLineParsing.ts", + "./unittests/configurationExtension.ts", "./unittests/convertCompilerOptionsFromJson.ts", "./unittests/convertTypingOptionsFromJson.ts", "./unittests/tsserverProjectSystem.ts", diff --git a/src/harness/unittests/configurationExtension.ts b/src/harness/unittests/configurationExtension.ts new file mode 100644 index 0000000000..21584d4b67 --- /dev/null +++ b/src/harness/unittests/configurationExtension.ts @@ -0,0 +1,230 @@ +/// +/// + +namespace ts { + const testContents = { + "/dev/tsconfig.json": `{ + "extends": "./configs/base", + "files": [ + "main.ts", + "supplemental.ts" + ] +}`, + "/dev/tsconfig.nostrictnull.json": `{ + "extends": "./tsconfig", + "compilerOptions": { + "strictNullChecks": false + } +}`, + "/dev/tsconfig.tests.json": `{ + "extends": ["./configs/tests", "./tsconfig"], + "compilerOptions": { + "module": "commonjs" + } +}`, + "/dev/tsconfig.tests.browser.json": `{ + "extends": ["./configs/tests", "./tsconfig"], + "compilerOptions": { + "module": "amd" + } +}`, + "/dev/configs/base.json": `{ + "compilerOptions": { + "allowJs": true, + "noImplicitAny": true, + "strictNullChecks": true + } +}`, + "/dev/configs/tests.json": `{ + "compilerOptions": { + "preserveConstEnums": true, + "removeComments": false, + "sourceMap": true + }, + "exclude": [ + "../tests/baselines", + "../tests/scenarios" + ], + "include": [ + "../tests/**/*.ts" + ] +}`, + "/dev/circular.json": `{ + "extends": "./circular2", + "compilerOptions": { + "module": "amd" + } +}`, + "/dev/circular2.json": `{ + "extends": "./circular", + "compilerOptions": { + "module": "commonjs" + } +}`, + "/dev/missing.json": `{ + "extends": "./missing2", + "compilerOptions": { + "types": [] + } +}`, + "/dev/failure.json": `{ + "extends": "./failure2.json", + "compilerOptions": { + "typeRoots": [] + } +}`, + "/dev/failure2.json": `{ + "excludes": ["*.js"] +}`, + "/dev/multi.json": `{ + "extends": ["./configs/first", "./configs/second"], + "compilerOptions": { + "allowJs": false + } +}`, + "/dev/configs/first.json": `{ + "extends": "./base", + "compilerOptions": { + "module": "commonjs" + }, + "files": ["../main.ts"] +}`, + "/dev/configs/second.json": `{ + "extends": "./base", + "compilerOptions": { + "module": "amd" + }, + "include": ["../supplemental.*"] +}`, + "/dev/extends.json": `{ "extends": 42 }`, + "/dev/extends2.json": `{ "extends": "configs/base" }`, + "/dev/main.ts": "", + "/dev/supplemental.ts": "", + "/dev/tests/unit/spec.ts": "", + "/dev/tests/utils.ts": "", + "/dev/tests/scenarios/first.json": "", + "/dev/tests/baselines/first/output.ts": "" + }; + + const caseInsensitiveBasePath = "c:/dev/"; + const caseInsensitiveHost = new Utils.MockParseConfigHost(caseInsensitiveBasePath, /*useCaseSensitiveFileNames*/ false, mapObject(testContents, (key, content) => [`c:${key}`, content])); + + const caseSensitiveBasePath = "/dev/"; + const caseSensitiveHost = new Utils.MockParseConfigHost(caseSensitiveBasePath, /*useCaseSensitiveFileNames*/ true, testContents); + + function verifyDiagnostics(actual: Diagnostic[], expected: {code: number, category: DiagnosticCategory, messageText: string}[]) { + assert.isTrue(expected.length === actual.length, `Expected error: ${JSON.stringify(expected)}. Actual error: ${JSON.stringify(actual)}.`); + for (let i = 0; i < actual.length; i++) { + const actualError = actual[i]; + const expectedError = expected[i]; + assert.equal(actualError.code, expectedError.code, "Error code mismatch"); + assert.equal(actualError.category, expectedError.category, "Category mismatch"); + assert.equal(flattenDiagnosticMessageText(actualError.messageText, "\n"), expectedError.messageText); + } + } + + describe("Configuration Extension", () => { + forEach<[string, string, Utils.MockParseConfigHost], void>([ + ["under a case insensitive host", caseInsensitiveBasePath, caseInsensitiveHost], + ["under a case sensitive host", caseSensitiveBasePath, caseSensitiveHost] + ], ([testName, basePath, host]) => { + function testSuccess(name: string, entry: string, expected: CompilerOptions, expectedFiles: string[]) { + it(name, () => { + const {config, error} = ts.readConfigFile(entry, name => host.readFile(name)); + assert(config && !error, flattenDiagnosticMessageText(error && error.messageText, "\n")); + const parsed = ts.parseJsonConfigFileContent(config, host, basePath, {}, entry); + assert(!parsed.errors.length, flattenDiagnosticMessageText(parsed.errors[0] && parsed.errors[0].messageText, "\n")); + expected.configFilePath = entry; + assert.deepEqual(parsed.options, expected); + assert.deepEqual(parsed.fileNames, expectedFiles); + }); + } + + function testFailure(name: string, entry: string, expectedDiagnostics: {code: number, category: DiagnosticCategory, messageText: string}[]) { + it(name, () => { + const {config, error} = ts.readConfigFile(entry, name => host.readFile(name)); + assert(config && !error, flattenDiagnosticMessageText(error && error.messageText, "\n")); + const parsed = ts.parseJsonConfigFileContent(config, host, basePath, {}, entry); + verifyDiagnostics(parsed.errors, expectedDiagnostics); + }); + } + + describe(testName, () => { + testSuccess("can resolve an extension with a base extension", "tsconfig.json", { + allowJs: true, + noImplicitAny: true, + strictNullChecks: true, + }, [ + combinePaths(basePath, "main.ts"), + combinePaths(basePath, "supplemental.ts"), + ]); + + testSuccess("can resolve an extension with a base extension that overrides options", "tsconfig.nostrictnull.json", { + allowJs: true, + noImplicitAny: true, + strictNullChecks: false, + }, [ + combinePaths(basePath, "main.ts"), + combinePaths(basePath, "supplemental.ts"), + ]); + + testSuccess("can resolve an extension with a multiple base extensions that overrides options", "tsconfig.tests.json", { + allowJs: true, + noImplicitAny: true, + strictNullChecks: true, + preserveConstEnums: true, + removeComments: false, + sourceMap: true, + module: ts.ModuleKind.CommonJS, + }, [ + combinePaths(basePath, "main.ts"), + combinePaths(basePath, "supplemental.ts"), + combinePaths(basePath, "tests/unit/spec.ts"), + combinePaths(basePath, "tests/utils.ts"), + ]); + + testSuccess("can resolve a diamond dependency graph", "multi.json", { + allowJs: false, + noImplicitAny: true, + strictNullChecks: true, + module: ts.ModuleKind.AMD, + }, [ + combinePaths(basePath, "configs/../main.ts"), // Probably should consider resolving these kinds of paths when they appear + combinePaths(basePath, "supplemental.ts"), + ]); + + testFailure("can report errors on circular imports", "circular.json", [ + { + code: 18000, + category: DiagnosticCategory.Error, + messageText: `Circularity detected while resolving configuration: ${[combinePaths(basePath, "circular.json"), combinePaths(basePath, "circular2.json"), combinePaths(basePath, "circular.json")].join(" -> ")}` + } + ]); + + testFailure("can report missing configurations", "missing.json", [{ + code: 6096, + category: DiagnosticCategory.Message, + messageText: `File './missing2' does not exist.` + }]); + + testFailure("can report errors in extended configs", "failure.json", [{ + code: 6114, + category: DiagnosticCategory.Error, + messageText: `Unknown option 'excludes'. Did you mean 'exclude'?` + }]); + + testFailure("can error when 'extends' is neither a string nor a string[]", "extends.json", [{ + code: 5024, + category: DiagnosticCategory.Error, + messageText: `Compiler option 'extends' requires a value of type string or string[].` + }]); + + testFailure("can error when 'extends' is neither relative nor rooted.", "extends2.json", [{ + code: 18001, + category: DiagnosticCategory.Error, + messageText: `The path in an 'extends' options must be relative or rooted.` + }]); + }); + }); + }); +} \ No newline at end of file diff --git a/src/harness/virtualFileSystem.ts b/src/harness/virtualFileSystem.ts index 30192b8b8e..36b25cfd29 100644 --- a/src/harness/virtualFileSystem.ts +++ b/src/harness/virtualFileSystem.ts @@ -10,9 +10,9 @@ namespace Utils { this.name = name; } - isDirectory() { return false; } - isFile() { return false; } - isFileSystem() { return false; } + isDirectory(): this is VirtualDirectory { return false; } + isFile(): this is VirtualFile { return false; } + isFileSystem(): this is VirtualFileSystem { return false; } } export class VirtualFile extends VirtualFileSystemEntry { @@ -82,9 +82,8 @@ namespace Utils { return file; } else if (entry.isFile()) { - const file = entry; - file.content = content; - return file; + entry.content = content; + return entry; } else { return undefined; @@ -160,10 +159,18 @@ namespace Utils { } export class MockParseConfigHost extends VirtualFileSystem implements ts.ParseConfigHost { - constructor(currentDirectory: string, ignoreCase: boolean, files: string[]) { + constructor(currentDirectory: string, ignoreCase: boolean, files: ts.Map | string[]) { super(currentDirectory, ignoreCase); - for (const file of files) { - this.addFile(file); + const fileNames = (files instanceof Array) ? files : ts.getKeys(files); + for (const file of fileNames) { + this.addFile(file, (files as any)[file]); + } + } + + readFile(path: string): string { + const value = this.traversePath(path); + if (value && value.isFile()) { + return value.content; } } From d8ff546512f3c27384a70239d73fc6be3d5dccbc Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Mon, 29 Aug 2016 17:18:04 -0700 Subject: [PATCH 2/2] Remove handling for multiple inheritance for config files --- src/compiler/commandLineParser.ts | 11 +---- .../unittests/configurationExtension.ts | 47 +------------------ 2 files changed, 3 insertions(+), 55 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 559ec8da2f..7acf6eb901 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -822,17 +822,8 @@ namespace ts { if (typeof json["extends"] === "string") { [include, exclude, files, baseOptions] = (tryExtendsName(json["extends"]) || [include, exclude, files, baseOptions]); } - else if (typeof json["extends"] === "object" && json["extends"].length) { - for (const name of json["extends"]) { - const [tempinclude, tempexclude, tempfiles, tempBase]: [string[], string[], string[], CompilerOptions] = (tryExtendsName(name) || [include, exclude, files, baseOptions]); - include = tempinclude || include; - exclude = tempexclude || exclude; - files = tempfiles || files; - baseOptions = assign({}, baseOptions, tempBase); - } - } else { - errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "extends", "string or string[]")); + errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "extends", "string")); } if (include && !json["include"]) { json["include"] = include; diff --git a/src/harness/unittests/configurationExtension.ts b/src/harness/unittests/configurationExtension.ts index 21584d4b67..4537dc7757 100644 --- a/src/harness/unittests/configurationExtension.ts +++ b/src/harness/unittests/configurationExtension.ts @@ -15,18 +15,6 @@ namespace ts { "compilerOptions": { "strictNullChecks": false } -}`, - "/dev/tsconfig.tests.json": `{ - "extends": ["./configs/tests", "./tsconfig"], - "compilerOptions": { - "module": "commonjs" - } -}`, - "/dev/tsconfig.tests.browser.json": `{ - "extends": ["./configs/tests", "./tsconfig"], - "compilerOptions": { - "module": "amd" - } }`, "/dev/configs/base.json": `{ "compilerOptions": { @@ -75,12 +63,6 @@ namespace ts { }`, "/dev/failure2.json": `{ "excludes": ["*.js"] -}`, - "/dev/multi.json": `{ - "extends": ["./configs/first", "./configs/second"], - "compilerOptions": { - "allowJs": false - } }`, "/dev/configs/first.json": `{ "extends": "./base", @@ -168,31 +150,6 @@ namespace ts { combinePaths(basePath, "supplemental.ts"), ]); - testSuccess("can resolve an extension with a multiple base extensions that overrides options", "tsconfig.tests.json", { - allowJs: true, - noImplicitAny: true, - strictNullChecks: true, - preserveConstEnums: true, - removeComments: false, - sourceMap: true, - module: ts.ModuleKind.CommonJS, - }, [ - combinePaths(basePath, "main.ts"), - combinePaths(basePath, "supplemental.ts"), - combinePaths(basePath, "tests/unit/spec.ts"), - combinePaths(basePath, "tests/utils.ts"), - ]); - - testSuccess("can resolve a diamond dependency graph", "multi.json", { - allowJs: false, - noImplicitAny: true, - strictNullChecks: true, - module: ts.ModuleKind.AMD, - }, [ - combinePaths(basePath, "configs/../main.ts"), // Probably should consider resolving these kinds of paths when they appear - combinePaths(basePath, "supplemental.ts"), - ]); - testFailure("can report errors on circular imports", "circular.json", [ { code: 18000, @@ -213,10 +170,10 @@ namespace ts { messageText: `Unknown option 'excludes'. Did you mean 'exclude'?` }]); - testFailure("can error when 'extends' is neither a string nor a string[]", "extends.json", [{ + testFailure("can error when 'extends' is not a string", "extends.json", [{ code: 5024, category: DiagnosticCategory.Error, - messageText: `Compiler option 'extends' requires a value of type string or string[].` + messageText: `Compiler option 'extends' requires a value of type string.` }]); testFailure("can error when 'extends' is neither relative nor rooted.", "extends2.json", [{