diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 0fb10346440a2..f44eae9455f64 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -2617,14 +2617,17 @@ namespace ts { if (ownConfig.extendedConfigPath) { // copy the resolution stack so it is never reused between branches in potential diamond-problem scenarios. resolutionStack = resolutionStack.concat([resolvedPath]); - const extendedConfig = getExtendedConfig(sourceFile, ownConfig.extendedConfigPath, host, basePath, resolutionStack, errors, extendedConfigCache); + const extendedConfig = getExtendedConfig(sourceFile, ownConfig.extendedConfigPath, host, resolutionStack, errors, extendedConfigCache); if (extendedConfig && isSuccessfulParsedTsconfig(extendedConfig)) { const baseRaw = extendedConfig.raw; const raw = ownConfig.raw; + let relativeDifference: string | undefined ; const setPropertyInRawIfNotUndefined = (propertyName: string) => { - const value = raw[propertyName] || baseRaw[propertyName]; - if (value) { - raw[propertyName] = value; + if (!raw[propertyName] && baseRaw[propertyName]) { + raw[propertyName] = map(baseRaw[propertyName], (path: string) => isRootedDiskPath(path) ? path : combinePaths( + relativeDifference ||= convertToRelativePath(getDirectoryPath(ownConfig.extendedConfigPath!), basePath, createGetCanonicalFileName(host.useCaseSensitiveFileNames)), + path + )); } }; setPropertyInRawIfNotUndefined("include"); @@ -2786,7 +2789,6 @@ namespace ts { sourceFile: TsConfigSourceFile | undefined, extendedConfigPath: string, host: ParseConfigHost, - basePath: string, resolutionStack: string[], errors: Push, extendedConfigCache?: ESMap @@ -2801,25 +2803,8 @@ namespace ts { else { extendedResult = readJsonConfigFile(extendedConfigPath, path => host.readFile(path)); if (!extendedResult.parseDiagnostics.length) { - const extendedDirname = getDirectoryPath(extendedConfigPath); - extendedConfig = parseConfig(/*json*/ undefined, extendedResult, host, extendedDirname, + extendedConfig = parseConfig(/*json*/ undefined, extendedResult, host, getDirectoryPath(extendedConfigPath), getBaseFileName(extendedConfigPath), resolutionStack, errors, extendedConfigCache); - - if (isSuccessfulParsedTsconfig(extendedConfig)) { - // Update the paths to reflect base path - const relativeDifference = convertToRelativePath(extendedDirname, basePath, identity); - const updatePath = (path: string) => isRootedDiskPath(path) ? path : combinePaths(relativeDifference, path); - const mapPropertiesInRawIfNotUndefined = (propertyName: string) => { - if (raw[propertyName]) { - raw[propertyName] = map(raw[propertyName], updatePath); - } - }; - - const { raw } = extendedConfig; - mapPropertiesInRawIfNotUndefined("include"); - mapPropertiesInRawIfNotUndefined("exclude"); - mapPropertiesInRawIfNotUndefined("files"); - } } if (extendedConfigCache) { extendedConfigCache.set(path, { extendedResult, extendedConfig }); diff --git a/src/testRunner/tsconfig.json b/src/testRunner/tsconfig.json index 1d389e21f0140..d18df87b6cdf1 100644 --- a/src/testRunner/tsconfig.json +++ b/src/testRunner/tsconfig.json @@ -112,6 +112,7 @@ "unittests/services/transpile.ts", "unittests/tsbuild/amdModulesWithOut.ts", "unittests/tsbuild/configFileErrors.ts", + "unittests/tsbuild/configFileExtends.ts", "unittests/tsbuild/containerOnlyReferenced.ts", "unittests/tsbuild/declarationEmit.ts", "unittests/tsbuild/demo.ts", diff --git a/src/testRunner/unittests/tsbuild/configFileExtends.ts b/src/testRunner/unittests/tsbuild/configFileExtends.ts new file mode 100644 index 0000000000000..b00a213b9d3fc --- /dev/null +++ b/src/testRunner/unittests/tsbuild/configFileExtends.ts @@ -0,0 +1,52 @@ +namespace ts { + describe("unittests:: tsbuild:: configFileExtends:: when tsconfig extends another config", () => { + function getConfigExtendsWithIncludeFs() { + return loadProjectFromFiles({ + "/src/tsconfig.json": JSON.stringify({ + references: [ + { path: "./shared/tsconfig.json" }, + { path: "./webpack/tsconfig.json" } + ], + files: [] + }), + "/src/shared/tsconfig-base.json": JSON.stringify({ + include: ["./typings-base/"] + }), + "/src/shared/typings-base/globals.d.ts": `type Unrestricted = any;`, + "/src/shared/tsconfig.json": JSON.stringify({ + extends: "./tsconfig-base.json", + compilerOptions: { + composite: true, + outDir: "../target-tsc-build/", + rootDir: ".." + }, + files: ["./index.ts"] + }), + "/src/shared/index.ts": `export const a: Unrestricted = 1;`, + "/src/webpack/tsconfig.json": JSON.stringify({ + extends: "../shared/tsconfig-base.json", + compilerOptions: { + composite: true, + outDir: "../target-tsc-build/", + rootDir: ".." + }, + files: ["./index.ts"], + references: [{ path: "../shared/tsconfig.json" }] + }), + "/src/webpack/index.ts": `export const b: Unrestricted = 1;`, + }); + } + verifyTsc({ + scenario: "configFileExtends", + subScenario: "when building solution with projects extends config with include", + fs: getConfigExtendsWithIncludeFs, + commandLineArgs: ["--b", "/src/tsconfig.json", "--v", "--listFiles"], + }); + verifyTsc({ + scenario: "configFileExtends", + subScenario: "when building project uses reference and both extend config with include", + fs: getConfigExtendsWithIncludeFs, + commandLineArgs: ["--b", "/src/webpack/tsconfig.json", "--v", "--listFiles"], + }); + }); +} \ No newline at end of file diff --git a/tests/baselines/reference/tsbuild/configFileExtends/initial-build/when-building-project-uses-reference-and-both-extend-config-with-include.js b/tests/baselines/reference/tsbuild/configFileExtends/initial-build/when-building-project-uses-reference-and-both-extend-config-with-include.js new file mode 100644 index 0000000000000..f8c8fa523a4a0 --- /dev/null +++ b/tests/baselines/reference/tsbuild/configFileExtends/initial-build/when-building-project-uses-reference-and-both-extend-config-with-include.js @@ -0,0 +1,160 @@ +Input:: +//// [/lib/lib.d.ts] +/// +interface Boolean {} +interface Function {} +interface CallableFunction {} +interface NewableFunction {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +interface RegExp {} +interface String { charAt: any; } +interface Array { length: number; [n: number]: T; } +interface ReadonlyArray {} +declare const console: { log(msg: any): void; }; + +//// [/src/shared/index.ts] +export const a: Unrestricted = 1; + +//// [/src/shared/tsconfig-base.json] +{"include":["./typings-base/"]} + +//// [/src/shared/tsconfig.json] +{"extends":"./tsconfig-base.json","compilerOptions":{"composite":true,"outDir":"../target-tsc-build/","rootDir":".."},"files":["./index.ts"]} + +//// [/src/shared/typings-base/globals.d.ts] +type Unrestricted = any; + +//// [/src/tsconfig.json] + + +//// [/src/webpack/index.ts] +export const b: Unrestricted = 1; + +//// [/src/webpack/tsconfig.json] +{"extends":"../shared/tsconfig-base.json","compilerOptions":{"composite":true,"outDir":"../target-tsc-build/","rootDir":".."},"files":["./index.ts"],"references":[{"path":"../shared/tsconfig.json"}]} + + + +Output:: +/lib/tsc --b /src/webpack/tsconfig.json --v --listFiles +[12:00:00 AM] Projects in this build: + * src/shared/tsconfig.json + * src/webpack/tsconfig.json + +[12:00:00 AM] Project 'src/shared/tsconfig.json' is out of date because output file 'src/target-tsc-build/shared/index.js' does not exist + +[12:00:00 AM] Building project '/src/shared/tsconfig.json'... + +/lib/lib.d.ts +/src/shared/index.ts +/src/shared/typings-base/globals.d.ts +[12:00:00 AM] Project 'src/webpack/tsconfig.json' is out of date because output file 'src/target-tsc-build/webpack/index.js' does not exist + +[12:00:00 AM] Building project '/src/webpack/tsconfig.json'... + +/lib/lib.d.ts +/src/webpack/index.ts +/src/shared/typings-base/globals.d.ts +exitCode:: ExitStatus.Success + + +//// [/src/target-tsc-build/shared/index.d.ts] +export declare const a: Unrestricted; + + +//// [/src/target-tsc-build/shared/index.js] +"use strict"; +exports.__esModule = true; +exports.a = void 0; +exports.a = 1; + + +//// [/src/target-tsc-build/shared/tsconfig.tsbuildinfo] +{ + "program": { + "fileInfos": { + "../../../lib/lib.d.ts": { + "version": "3858781397-/// \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; }\ninterface ReadonlyArray {}\ndeclare const console: { log(msg: any): void; };", + "signature": "3858781397-/// \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; }\ninterface ReadonlyArray {}\ndeclare const console: { log(msg: any): void; };", + "affectsGlobalScope": true + }, + "../../shared/index.ts": { + "version": "-22125360210-export const a: Unrestricted = 1;", + "signature": "-478734393-export declare const a: Unrestricted;\r\n", + "affectsGlobalScope": false + }, + "../../shared/typings-base/globals.d.ts": { + "version": "4725476611-type Unrestricted = any;", + "signature": "4725476611-type Unrestricted = any;", + "affectsGlobalScope": true + } + }, + "options": { + "composite": true, + "outDir": "..", + "rootDir": "../..", + "listFiles": true, + "configFilePath": "../../shared/tsconfig.json" + }, + "referencedMap": {}, + "exportedModulesMap": {}, + "semanticDiagnosticsPerFile": [ + "../../../lib/lib.d.ts", + "../../shared/index.ts", + "../../shared/typings-base/globals.d.ts" + ] + }, + "version": "FakeTSVersion" +} + +//// [/src/target-tsc-build/webpack/index.d.ts] +export declare const b: Unrestricted; + + +//// [/src/target-tsc-build/webpack/index.js] +"use strict"; +exports.__esModule = true; +exports.b = void 0; +exports.b = 1; + + +//// [/src/target-tsc-build/webpack/tsconfig.tsbuildinfo] +{ + "program": { + "fileInfos": { + "../../../lib/lib.d.ts": { + "version": "3858781397-/// \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; }\ninterface ReadonlyArray {}\ndeclare const console: { log(msg: any): void; };", + "signature": "3858781397-/// \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; }\ninterface ReadonlyArray {}\ndeclare const console: { log(msg: any): void; };", + "affectsGlobalScope": true + }, + "../../webpack/index.ts": { + "version": "-14405273073-export const b: Unrestricted = 1;", + "signature": "-5074241048-export declare const b: Unrestricted;\r\n", + "affectsGlobalScope": false + }, + "../../shared/typings-base/globals.d.ts": { + "version": "4725476611-type Unrestricted = any;", + "signature": "4725476611-type Unrestricted = any;", + "affectsGlobalScope": true + } + }, + "options": { + "composite": true, + "outDir": "..", + "rootDir": "../..", + "listFiles": true, + "configFilePath": "../../webpack/tsconfig.json" + }, + "referencedMap": {}, + "exportedModulesMap": {}, + "semanticDiagnosticsPerFile": [ + "../../../lib/lib.d.ts", + "../../shared/typings-base/globals.d.ts", + "../../webpack/index.ts" + ] + }, + "version": "FakeTSVersion" +} + diff --git a/tests/baselines/reference/tsbuild/configFileExtends/initial-build/when-building-solution-with-projects-extends-config-with-include.js b/tests/baselines/reference/tsbuild/configFileExtends/initial-build/when-building-solution-with-projects-extends-config-with-include.js new file mode 100644 index 0000000000000..7d42e37a02d1c --- /dev/null +++ b/tests/baselines/reference/tsbuild/configFileExtends/initial-build/when-building-solution-with-projects-extends-config-with-include.js @@ -0,0 +1,161 @@ +Input:: +//// [/lib/lib.d.ts] +/// +interface Boolean {} +interface Function {} +interface CallableFunction {} +interface NewableFunction {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +interface RegExp {} +interface String { charAt: any; } +interface Array { length: number; [n: number]: T; } +interface ReadonlyArray {} +declare const console: { log(msg: any): void; }; + +//// [/src/shared/index.ts] +export const a: Unrestricted = 1; + +//// [/src/shared/tsconfig-base.json] +{"include":["./typings-base/"]} + +//// [/src/shared/tsconfig.json] +{"extends":"./tsconfig-base.json","compilerOptions":{"composite":true,"outDir":"../target-tsc-build/","rootDir":".."},"files":["./index.ts"]} + +//// [/src/shared/typings-base/globals.d.ts] +type Unrestricted = any; + +//// [/src/tsconfig.json] +{"references":[{"path":"./shared/tsconfig.json"},{"path":"./webpack/tsconfig.json"}],"files":[]} + +//// [/src/webpack/index.ts] +export const b: Unrestricted = 1; + +//// [/src/webpack/tsconfig.json] +{"extends":"../shared/tsconfig-base.json","compilerOptions":{"composite":true,"outDir":"../target-tsc-build/","rootDir":".."},"files":["./index.ts"],"references":[{"path":"../shared/tsconfig.json"}]} + + + +Output:: +/lib/tsc --b /src/tsconfig.json --v --listFiles +[12:00:00 AM] Projects in this build: + * src/shared/tsconfig.json + * src/webpack/tsconfig.json + * src/tsconfig.json + +[12:00:00 AM] Project 'src/shared/tsconfig.json' is out of date because output file 'src/target-tsc-build/shared/index.js' does not exist + +[12:00:00 AM] Building project '/src/shared/tsconfig.json'... + +/lib/lib.d.ts +/src/shared/index.ts +/src/shared/typings-base/globals.d.ts +[12:00:00 AM] Project 'src/webpack/tsconfig.json' is out of date because output file 'src/target-tsc-build/webpack/index.js' does not exist + +[12:00:00 AM] Building project '/src/webpack/tsconfig.json'... + +/lib/lib.d.ts +/src/webpack/index.ts +/src/shared/typings-base/globals.d.ts +exitCode:: ExitStatus.Success + + +//// [/src/target-tsc-build/shared/index.d.ts] +export declare const a: Unrestricted; + + +//// [/src/target-tsc-build/shared/index.js] +"use strict"; +exports.__esModule = true; +exports.a = void 0; +exports.a = 1; + + +//// [/src/target-tsc-build/shared/tsconfig.tsbuildinfo] +{ + "program": { + "fileInfos": { + "../../../lib/lib.d.ts": { + "version": "3858781397-/// \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; }\ninterface ReadonlyArray {}\ndeclare const console: { log(msg: any): void; };", + "signature": "3858781397-/// \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; }\ninterface ReadonlyArray {}\ndeclare const console: { log(msg: any): void; };", + "affectsGlobalScope": true + }, + "../../shared/index.ts": { + "version": "-22125360210-export const a: Unrestricted = 1;", + "signature": "-478734393-export declare const a: Unrestricted;\r\n", + "affectsGlobalScope": false + }, + "../../shared/typings-base/globals.d.ts": { + "version": "4725476611-type Unrestricted = any;", + "signature": "4725476611-type Unrestricted = any;", + "affectsGlobalScope": true + } + }, + "options": { + "composite": true, + "outDir": "..", + "rootDir": "../..", + "listFiles": true, + "configFilePath": "../../shared/tsconfig.json" + }, + "referencedMap": {}, + "exportedModulesMap": {}, + "semanticDiagnosticsPerFile": [ + "../../../lib/lib.d.ts", + "../../shared/index.ts", + "../../shared/typings-base/globals.d.ts" + ] + }, + "version": "FakeTSVersion" +} + +//// [/src/target-tsc-build/webpack/index.d.ts] +export declare const b: Unrestricted; + + +//// [/src/target-tsc-build/webpack/index.js] +"use strict"; +exports.__esModule = true; +exports.b = void 0; +exports.b = 1; + + +//// [/src/target-tsc-build/webpack/tsconfig.tsbuildinfo] +{ + "program": { + "fileInfos": { + "../../../lib/lib.d.ts": { + "version": "3858781397-/// \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; }\ninterface ReadonlyArray {}\ndeclare const console: { log(msg: any): void; };", + "signature": "3858781397-/// \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; }\ninterface ReadonlyArray {}\ndeclare const console: { log(msg: any): void; };", + "affectsGlobalScope": true + }, + "../../webpack/index.ts": { + "version": "-14405273073-export const b: Unrestricted = 1;", + "signature": "-5074241048-export declare const b: Unrestricted;\r\n", + "affectsGlobalScope": false + }, + "../../shared/typings-base/globals.d.ts": { + "version": "4725476611-type Unrestricted = any;", + "signature": "4725476611-type Unrestricted = any;", + "affectsGlobalScope": true + } + }, + "options": { + "composite": true, + "outDir": "..", + "rootDir": "../..", + "listFiles": true, + "configFilePath": "../../webpack/tsconfig.json" + }, + "referencedMap": {}, + "exportedModulesMap": {}, + "semanticDiagnosticsPerFile": [ + "../../../lib/lib.d.ts", + "../../shared/typings-base/globals.d.ts", + "../../webpack/index.ts" + ] + }, + "version": "FakeTSVersion" +} +