diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 05d80d97cc..f5d5a24cb0 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -2852,6 +2852,7 @@ namespace ts { let typeAcquisition: TypeAcquisition | undefined, typingOptionstypeAcquisition: TypeAcquisition | undefined; let watchOptions: WatchOptions | undefined; let extendedConfigPath: string | undefined; + let rootCompilerOptions: PropertyName[] | undefined; const optionsIterator: JsonConversionNotifier = { onSetValidOptionKeyValueInParent(parentOption: string, option: CommandLineOption, value: CompilerOptionsValue) { @@ -2894,6 +2895,9 @@ namespace ts { if (key === "excludes") { errors.push(createDiagnosticForNodeInSourceFile(sourceFile, keyNode, Diagnostics.Unknown_option_excludes_Did_you_mean_exclude)); } + if (find(commandOptionsWithoutBuild, (opt) => opt.name === key)) { + rootCompilerOptions = append(rootCompilerOptions, keyNode); + } } }; const json = convertConfigFileToObject(sourceFile, errors, /*reportOptionsErrors*/ true, optionsIterator); @@ -2913,6 +2917,10 @@ namespace ts { } } + if (rootCompilerOptions && json && json.compilerOptions === undefined) { + errors.push(createDiagnosticForNodeInSourceFile(sourceFile, rootCompilerOptions[0], Diagnostics._0_should_be_set_inside_the_compilerOptions_object_of_the_config_json_file, getTextOfPropertyName(rootCompilerOptions[0]) as string)); + } + return { raw: json, options, watchOptions, typeAcquisition, extendedConfigPath }; } diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index d1a03c6a31..6f9c127f2d 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -4834,6 +4834,10 @@ "category": "Message", "code": 6257 }, + "'{0}' should be set inside the 'compilerOptions' object of the config json file": { + "category": "Error", + "code": 6258 + }, "Enable project compilation": { "category": "Message", diff --git a/src/testRunner/unittests/config/convertCompilerOptionsFromJson.ts b/src/testRunner/unittests/config/convertCompilerOptionsFromJson.ts index 71c826351d..f388f7e2b5 100644 --- a/src/testRunner/unittests/config/convertCompilerOptionsFromJson.ts +++ b/src/testRunner/unittests/config/convertCompilerOptionsFromJson.ts @@ -663,7 +663,36 @@ namespace ts { }); }); - it("Don't crash when root expression is not objecty at all", () => { + it("raises an error if you've set a compiler flag in the root without including 'compilerOptions'", () => { + assertCompilerOptionsWithJsonText(`{ + "module": "esnext", + }`, "tsconfig.json", { + compilerOptions: {}, + errors: [{ + ...Diagnostics._0_should_be_set_inside_the_compilerOptions_object_of_the_config_json_file, + messageText: "'module' should be set inside the 'compilerOptions' object of the config json file.", + file: undefined, + start: 0, + length: 0 + }] + }); + }); + + it("does not raise an error if you've set a compiler flag in the root when you have included 'compilerOptions'", () => { + assertCompilerOptionsWithJsonText(`{ + "target": "esnext", + "compilerOptions": { + "module": "esnext" + } + }`, "tsconfig.json", { + compilerOptions: { + module: ModuleKind.ESNext + }, + errors: [] + }); + }); + + it("Don't crash when root expression is not object at all", () => { assertCompilerOptionsWithJsonText(`42`, "tsconfig.json", { compilerOptions: {}, errors: [{ diff --git a/src/testRunner/unittests/tsbuildWatch/programUpdates.ts b/src/testRunner/unittests/tsbuildWatch/programUpdates.ts index 011bebf2e6..5d63b9b47e 100644 --- a/src/testRunner/unittests/tsbuildWatch/programUpdates.ts +++ b/src/testRunner/unittests/tsbuildWatch/programUpdates.ts @@ -718,7 +718,9 @@ export function someFn() { }`), const alphaExtendedConfigFile: File = { path: "/a/b/alpha.tsconfig.json", content: JSON.stringify({ - strict: true + compilerOptions: { + strict: true + } }) }; const project1Config: File = { @@ -734,7 +736,9 @@ export function someFn() { }`), const bravoExtendedConfigFile: File = { path: "/a/b/bravo.tsconfig.json", content: JSON.stringify({ - strict: true + compilerOptions: { + strict: true + } }) }; const otherFile: File = { diff --git a/tests/baselines/reference/tsbuild/watchMode/programUpdates/works-correctly-when-project-with-extended-config-is-removed.js b/tests/baselines/reference/tsbuild/watchMode/programUpdates/works-correctly-when-project-with-extended-config-is-removed.js index 16d987b8ad..b389fa1e55 100644 --- a/tests/baselines/reference/tsbuild/watchMode/programUpdates/works-correctly-when-project-with-extended-config-is-removed.js +++ b/tests/baselines/reference/tsbuild/watchMode/programUpdates/works-correctly-when-project-with-extended-config-is-removed.js @@ -16,7 +16,7 @@ interface Array { length: number; [n: number]: T; } {"references":[{"path":"./project1.tsconfig.json"},{"path":"./project2.tsconfig.json"}],"files":[]} //// [/a/b/alpha.tsconfig.json] -{"strict":true} +{"compilerOptions":{"strict":true}} //// [/a/b/project1.tsconfig.json] {"extends":"./alpha.tsconfig.json","compilerOptions":{"composite":true},"files":["/a/b/commonFile1.ts","/a/b/commonFile2.ts"]} @@ -28,7 +28,7 @@ let x = 1 let y = 1 //// [/a/b/bravo.tsconfig.json] -{"strict":true} +{"compilerOptions":{"strict":true}} //// [/a/b/project2.tsconfig.json] {"extends":"./bravo.tsconfig.json","compilerOptions":{"composite":true},"files":["/a/b/other.ts"]} @@ -60,7 +60,7 @@ Output:: Program root files: ["/a/b/commonFile1.ts","/a/b/commonFile2.ts"] -Program options: {"composite":true,"watch":true,"configFilePath":"/a/b/project1.tsconfig.json"} +Program options: {"strict":true,"composite":true,"watch":true,"configFilePath":"/a/b/project1.tsconfig.json"} Program structureReused: Not Program files:: /a/lib/lib.d.ts @@ -78,7 +78,7 @@ Shape signatures in builder refreshed for:: /a/b/commonfile2.ts (used version) Program root files: ["/a/b/other.ts"] -Program options: {"composite":true,"watch":true,"configFilePath":"/a/b/project2.tsconfig.json"} +Program options: {"strict":true,"composite":true,"watch":true,"configFilePath":"/a/b/project2.tsconfig.json"} Program structureReused: Not Program files:: /a/lib/lib.d.ts @@ -117,6 +117,7 @@ FsWatchesRecursive:: exitCode:: ExitStatus.undefined //// [/a/b/commonFile1.js] +"use strict"; var x = 1; @@ -125,6 +126,7 @@ declare let x: number; //// [/a/b/commonFile2.js] +"use strict"; var y = 1; @@ -133,7 +135,7 @@ declare let y: number; //// [/a/b/project1.tsconfig.tsbuildinfo] -{"program":{"fileNames":["../lib/lib.d.ts","./commonfile1.ts","./commonfile2.ts"],"fileInfos":[{"version":"-7698705165-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }","affectsGlobalScope":true},{"version":"2167136208-let x = 1","affectsGlobalScope":true},{"version":"2168322129-let y = 1","affectsGlobalScope":true}],"options":{"composite":true},"referencedMap":[],"exportedModulesMap":[],"semanticDiagnosticsPerFile":[2,3,1]},"version":"FakeTSVersion"} +{"program":{"fileNames":["../lib/lib.d.ts","./commonfile1.ts","./commonfile2.ts"],"fileInfos":[{"version":"-7698705165-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }","affectsGlobalScope":true},{"version":"2167136208-let x = 1","affectsGlobalScope":true},{"version":"2168322129-let y = 1","affectsGlobalScope":true}],"options":{"composite":true,"strict":true},"referencedMap":[],"exportedModulesMap":[],"semanticDiagnosticsPerFile":[2,3,1]},"version":"FakeTSVersion"} //// [/a/b/project1.tsconfig.tsbuildinfo.readable.baseline.txt] { @@ -161,7 +163,8 @@ declare let y: number; } }, "options": { - "composite": true + "composite": true, + "strict": true }, "referencedMap": {}, "exportedModulesMap": {}, @@ -172,10 +175,11 @@ declare let y: number; ] }, "version": "FakeTSVersion", - "size": 753 + "size": 767 } //// [/a/b/other.js] +"use strict"; var z = 0; @@ -184,7 +188,7 @@ declare let z: number; //// [/a/b/project2.tsconfig.tsbuildinfo] -{"program":{"fileNames":["../lib/lib.d.ts","./other.ts"],"fileInfos":[{"version":"-7698705165-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }","affectsGlobalScope":true},{"version":"2874288940-let z = 0;","affectsGlobalScope":true}],"options":{"composite":true},"referencedMap":[],"exportedModulesMap":[],"semanticDiagnosticsPerFile":[2,1]},"version":"FakeTSVersion"} +{"program":{"fileNames":["../lib/lib.d.ts","./other.ts"],"fileInfos":[{"version":"-7698705165-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }","affectsGlobalScope":true},{"version":"2874288940-let z = 0;","affectsGlobalScope":true}],"options":{"composite":true,"strict":true},"referencedMap":[],"exportedModulesMap":[],"semanticDiagnosticsPerFile":[2,1]},"version":"FakeTSVersion"} //// [/a/b/project2.tsconfig.tsbuildinfo.readable.baseline.txt] { @@ -206,7 +210,8 @@ declare let z: number; } }, "options": { - "composite": true + "composite": true, + "strict": true }, "referencedMap": {}, "exportedModulesMap": {}, @@ -216,7 +221,7 @@ declare let z: number; ] }, "version": "FakeTSVersion", - "size": 666 + "size": 680 }