diff --git a/src/tsconfig-loader.ts b/src/tsconfig-loader.ts index 82db01c..6e32afb 100644 --- a/src/tsconfig-loader.ts +++ b/src/tsconfig-loader.ts @@ -108,45 +108,51 @@ export function loadTsconfig( return undefined; } - const configString = readFileSync(configFilePath); - const cleanedJson = StripBom(configString); - const config: Tsconfig = JSON5.parse(cleanedJson); - let extendedConfig = config.extends; - - if (extendedConfig) { + // Compose a config object by looping through all tsconfig files for + // as long as 'extends' is set + let extensionPath: string | null = configFilePath; + let extendsDir: string = "."; + let config: Tsconfig = {}; + while (extensionPath !== null) { if ( - typeof extendedConfig === "string" && - extendedConfig.indexOf(".json") === -1 + typeof extensionPath === "string" && + extensionPath.indexOf(".json") === -1 ) { - extendedConfig += ".json"; + extensionPath += ".json"; } - const currentDir = path.dirname(configFilePath); - const base = - loadTsconfig( - path.join(currentDir, extendedConfig), - existsSync, - readFileSync - ) || {}; - - // baseUrl should be interpreted as relative to the base tsconfig, - // but we need to update it so it is relative to the original tsconfig being loaded - if (base && base.compilerOptions && base.compilerOptions.baseUrl) { - const extendsDir = path.dirname(extendedConfig); - base.compilerOptions.baseUrl = path.join( - extendsDir, - base.compilerOptions.baseUrl - ); - } + const configString = readFileSync(extensionPath); + const cleanedJson = StripBom(configString); + const newConfig: Tsconfig = JSON5.parse(cleanedJson); - return { - ...base, + // Merge new and previous config. Since the loop is walking 'backwards' + // we need to make sure the 'old' config is prioritized over the 'new' one. + config = { + ...newConfig, ...config, compilerOptions: { - ...base.compilerOptions, + ...newConfig.compilerOptions, ...config.compilerOptions } }; + + // baseUrl should be interpreted as relative to the base tsconfig, + // but we need to update it so it is relative to the original tsconfig being loaded + // we therefore need to compose the 'extendsDir' path + extendsDir = newConfig.extends + ? path.join(extendsDir, path.dirname(newConfig.extends)) + : extendsDir; + extensionPath = newConfig.extends + ? path.resolve(path.dirname(extensionPath), newConfig.extends) + : null; } + + if (config.compilerOptions && config.compilerOptions.baseUrl) { + config.compilerOptions.baseUrl = path.join( + extendsDir, + config.compilerOptions.baseUrl + ); + } + return config; } diff --git a/test/tsconfig-loader-tests.ts b/test/tsconfig-loader-tests.ts index 002bcb4..29f5a64 100644 --- a/test/tsconfig-loader-tests.ts +++ b/test/tsconfig-loader-tests.ts @@ -108,7 +108,7 @@ describe("loadConfig", () => { path => path === "/root/dir1/tsconfig.json", _ => `{ // my comment - "compilerOptions": { + "compilerOptions": { "baseUrl": "hej" } }` @@ -122,7 +122,7 @@ describe("loadConfig", () => { "/root/dir1/tsconfig.json", path => path === "/root/dir1/tsconfig.json", _ => `{ - "compilerOptions": { + "compilerOptions": { "baseUrl": "hej", }, }` @@ -161,7 +161,64 @@ describe("loadConfig", () => { assert.deepEqual(res, { extends: "../base-config.json", compilerOptions: { - baseUrl: "kalle", + baseUrl: "../kalle", + paths: { foo: ["bar2"] }, + strict: true + } + }); + }); + + it("It should correctly handle mutilple tsconfig 'extends'", () => { + const secondConfig = { + extends: "../tsconfig.json", + compilerOptions: { + strict: true, + baseUrl: "deut" + } + }; + const secondConfigPath = join( + "/root", + "dir1", + "dir2", + "tsconfig.second.json" + ); + const firstConfig = { + extends: "../base-config.json", + compilerOptions: { baseUrl: "k alle", paths: { foo: ["bar2"] } } + }; + const firstConfigPath = join("/root", "dir1", "tsconfig.json"); + const baseConfig = { + compilerOptions: { + baseUrl: "olle", + strict: false, + paths: { foo: ["bar1"] } + } + }; + const baseConfigPath = join("/root", "base-config.json"); + const res = loadTsconfig( + join("/root", "dir1", "dir2", "tsconfig.second.json"), + path => + path === firstConfigPath || + path === secondConfigPath || + path === baseConfigPath, + path => { + if (path === firstConfigPath) { + return JSON.stringify(firstConfig); + } + if (path === secondConfigPath) { + return JSON.stringify(secondConfig); + } + if (path === baseConfigPath) { + return JSON.stringify(baseConfig); + } + return ""; + } + ); + + assert.deepEqual(res, { + extends: "../tsconfig.json", + compilerOptions: { + baseUrl: "../../deut", paths: { foo: ["bar2"] }, strict: true }