From 03f5234be51b63dfc8e726f8fa18046f83d712c8 Mon Sep 17 00:00:00 2001 From: trikadin <trikadin+github@pm.me> Date: Wed, 11 Sep 2024 13:42:07 +0400 Subject: [PATCH 1/6] Support ${configDir} interpolation for all the fields --- .gitignore | 3 ++ src/parse-tsconfig/index.ts | 60 ++++++++++++++++++++++++++++--------- 2 files changed, 49 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index 1fca471..7b3fe23 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,9 @@ yarn-debug.log* yarn-error.log* lerna-debug.log* +# JetBrains (WebStorm) +.idea + # Dependency directories node_modules/ diff --git a/src/parse-tsconfig/index.ts b/src/parse-tsconfig/index.ts index d8d6879..8b3fcaf 100644 --- a/src/parse-tsconfig/index.ts +++ b/src/parse-tsconfig/index.ts @@ -37,7 +37,7 @@ const resolveExtends = ( const { compilerOptions } = extendsConfig; if (compilerOptions) { const { baseUrl } = compilerOptions; - if (baseUrl) { + if (baseUrl != null && !baseUrl.startsWith(configDirPlaceholder)) { compilerOptions.baseUrl = slash( path.relative( fromDirectoryPath, @@ -166,7 +166,7 @@ const _parseTsconfig = ( for (const property of normalizedPaths) { const unresolvedPath = compilerOptions[property]; - if (unresolvedPath) { + if (unresolvedPath != null && !unresolvedPath.startsWith(configDirPlaceholder)) { const resolvedBaseUrl = path.resolve(directoryPath, unresolvedPath); const relativeBaseUrl = normalizeRelativePath(path.relative( directoryPath, @@ -222,14 +222,26 @@ const _parseTsconfig = ( return config; }; -const interpolateConfigDir = ( - filePath: string, +function interpolateConfigDir<T extends string | string[]> ( + filePaths: T, configDir: string, -) => { - if (filePath.startsWith(configDirPlaceholder)) { - return slash(path.join(configDir, filePath.slice(configDirPlaceholder.length))); + postProcess?: (input: string) => string +): T extends string ? string : string[]; +function interpolateConfigDir ( + filePaths: string | string[], + configDir: string, + postProcess: (input: string) => string = (value) => value +): string | string[] { + if (Array.isArray(filePaths)) { + return filePaths.map((filePath) => interpolateConfigDir(filePath, configDir, postProcess)); + } + + if (filePaths.startsWith(configDirPlaceholder)) { + return postProcess(slash(path.join(configDir, filePaths.slice(configDirPlaceholder.length)))); } -}; + + return filePaths; +} export const parseTsconfig = ( tsconfigPath: string, @@ -238,14 +250,34 @@ export const parseTsconfig = ( const resolvedTsconfigPath = path.resolve(tsconfigPath); const config = _parseTsconfig(resolvedTsconfigPath, cache); + /** + * @see https://github.com/microsoft/TypeScript/issues/57485#issuecomment-2027787456 + * exclude paths, as it requires custom processing + */ + const compilerFieldsWithConfigDir = [ + 'outDir', + 'declarationDir', + 'outFile', + 'rootDir', + 'baseUrl', + 'tsBuildInfoFile', + 'rootDirs', + 'typeRoots', + ] as const satisfies Array<keyof NonNullable<TsConfigJson['compilerOptions']>>; + const configDir = path.dirname(resolvedTsconfigPath); if (config.compilerOptions) { - let { outDir } = config.compilerOptions; - if (outDir) { - const interpolated = interpolateConfigDir(outDir, configDir); - if (interpolated) { - outDir = normalizeRelativePath(path.relative(configDir, interpolated)); - config.compilerOptions.outDir = outDir; + for (const field of compilerFieldsWithConfigDir) { + const value = config.compilerOptions[field]; + + if (value != null) { + /** + * I used Object.assign instead of the direct assignment to work around TS bug (it fails to infer types correctly). + * @see https://github.com/microsoft/TypeScript/issues/33912 + */ + Object.assign(config.compilerOptions, { + [field]: interpolateConfigDir(value, configDir, (interpolated) => normalizeRelativePath(path.relative(configDir, interpolated))) + }); } } From 774fc2ff4076a49754eba31da2d7e948ea3cd2ea Mon Sep 17 00:00:00 2001 From: trikadin <trikadin+github@pm.me> Date: Wed, 11 Sep 2024 17:07:38 +0400 Subject: [PATCH 2/6] fix linting --- src/parse-tsconfig/index.ts | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/parse-tsconfig/index.ts b/src/parse-tsconfig/index.ts index 8b3fcaf..a2f7373 100644 --- a/src/parse-tsconfig/index.ts +++ b/src/parse-tsconfig/index.ts @@ -37,7 +37,7 @@ const resolveExtends = ( const { compilerOptions } = extendsConfig; if (compilerOptions) { const { baseUrl } = compilerOptions; - if (baseUrl != null && !baseUrl.startsWith(configDirPlaceholder)) { + if (baseUrl && !baseUrl.startsWith(configDirPlaceholder)) { compilerOptions.baseUrl = slash( path.relative( fromDirectoryPath, @@ -166,7 +166,7 @@ const _parseTsconfig = ( for (const property of normalizedPaths) { const unresolvedPath = compilerOptions[property]; - if (unresolvedPath != null && !unresolvedPath.startsWith(configDirPlaceholder)) { + if (unresolvedPath && !unresolvedPath.startsWith(configDirPlaceholder)) { const resolvedBaseUrl = path.resolve(directoryPath, unresolvedPath); const relativeBaseUrl = normalizeRelativePath(path.relative( directoryPath, @@ -222,18 +222,19 @@ const _parseTsconfig = ( return config; }; -function interpolateConfigDir<T extends string | string[]> ( +function interpolateConfigDir<T extends string | string[]>( filePaths: T, configDir: string, postProcess?: (input: string) => string ): T extends string ? string : string[]; -function interpolateConfigDir ( +// eslint-disable-next-line pvtnbr/prefer-arrow-functions +function interpolateConfigDir( filePaths: string | string[], configDir: string, - postProcess: (input: string) => string = (value) => value + postProcess: (input: string) => string = value => value, ): string | string[] { if (Array.isArray(filePaths)) { - return filePaths.map((filePath) => interpolateConfigDir(filePath, configDir, postProcess)); + return filePaths.map(filePath => interpolateConfigDir(filePath, configDir, postProcess)); } if (filePaths.startsWith(configDirPlaceholder)) { @@ -270,13 +271,18 @@ export const parseTsconfig = ( for (const field of compilerFieldsWithConfigDir) { const value = config.compilerOptions[field]; - if (value != null) { + if (value) { /** - * I used Object.assign instead of the direct assignment to work around TS bug (it fails to infer types correctly). + * I used Object.assign instead of the direct assignment to work around TS bug + * (it fails to infer types correctly). * @see https://github.com/microsoft/TypeScript/issues/33912 */ Object.assign(config.compilerOptions, { - [field]: interpolateConfigDir(value, configDir, (interpolated) => normalizeRelativePath(path.relative(configDir, interpolated))) + [field]: interpolateConfigDir( + value, + configDir, + interpolated => normalizeRelativePath(path.relative(configDir, interpolated)), + ), }); } } From 29381de7d98fb25b2186bb6daa5c4af543f48e1b Mon Sep 17 00:00:00 2001 From: trikadin <trikadin+github@pm.me> Date: Wed, 11 Sep 2024 17:28:42 +0400 Subject: [PATCH 3/6] added tests + fixed missing declarationDir in excludes --- src/parse-tsconfig/index.ts | 31 ++++++++++++------- .../parse-tsconfig/extends/merges.spec.ts | 7 +++++ 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/parse-tsconfig/index.ts b/src/parse-tsconfig/index.ts index a2f7373..ce4829c 100644 --- a/src/parse-tsconfig/index.ts +++ b/src/parse-tsconfig/index.ts @@ -176,20 +176,29 @@ const _parseTsconfig = ( } } - let { outDir } = compilerOptions; - if (outDir) { - if (!Array.isArray(config.exclude)) { - config.exclude = []; - } + const outputFields = [ + 'outDir', + 'declarationDir' + ] as const satisfies Array<keyof NonNullable<TsConfigJson['compilerOptions']>>; - if (!config.exclude.includes(outDir)) { - config.exclude.push(outDir); - } + for (const outputField of outputFields) { + let outputPath = compilerOptions[outputField]; - if (!outDir.startsWith(configDirPlaceholder)) { - outDir = normalizeRelativePath(outDir); + if (outputPath) { + if (!Array.isArray(config.exclude)) { + config.exclude = []; + } + + if (!config.exclude.includes(outputPath)) { + config.exclude.push(outputPath); + } + + if (!outputPath.startsWith(configDirPlaceholder)) { + outputPath = normalizeRelativePath(outputPath); + } + + compilerOptions[outputField] = outputPath; } - compilerOptions.outDir = outDir; } } else { config.compilerOptions = {}; diff --git a/tests/specs/parse-tsconfig/extends/merges.spec.ts b/tests/specs/parse-tsconfig/extends/merges.spec.ts index c9008bf..6e71fd2 100644 --- a/tests/specs/parse-tsconfig/extends/merges.spec.ts +++ b/tests/specs/parse-tsconfig/extends/merges.spec.ts @@ -492,6 +492,13 @@ export default testSuite(({ describe }) => { 'tsconfig.json': createTsconfigJson({ compilerOptions: { outDir: '${configDir}-asdf/dist', + declarationDir: '${configDir}/dist/declaration', + outFile: '${configDir}/dist/outfile.js', + rootDir: '${configDir}/dist/src', + baseUrl: '${configDir}/dist/src', + tsBuildInfoFile: '${configDir}/dist/dist.tsbuildinfo', + rootDirs: ['${configDir}/src', '${configDir}/static'], + typeRoots: ['${configDir}/src/type', '${configDir}/types'], paths: { a: ['${configDir}_a/*'], b: ['ignores/${configDir}/*'], From 73c3ab236f5731f1bb661f65564789c85fc0b898 Mon Sep 17 00:00:00 2001 From: trikadin <trikadin+github@pm.me> Date: Wed, 11 Sep 2024 17:31:55 +0400 Subject: [PATCH 4/6] fix linting --- src/parse-tsconfig/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parse-tsconfig/index.ts b/src/parse-tsconfig/index.ts index ce4829c..aabc7f1 100644 --- a/src/parse-tsconfig/index.ts +++ b/src/parse-tsconfig/index.ts @@ -178,7 +178,7 @@ const _parseTsconfig = ( const outputFields = [ 'outDir', - 'declarationDir' + 'declarationDir', ] as const satisfies Array<keyof NonNullable<TsConfigJson['compilerOptions']>>; for (const outputField of outputFields) { From 0125a34df213d859d06defb98d3062e4521b9676 Mon Sep 17 00:00:00 2001 From: Hiroki Osame <hiroki.osame@gmail.com> Date: Thu, 12 Sep 2024 16:21:26 +0900 Subject: [PATCH 5/6] wip --- src/parse-tsconfig/index.ts | 111 ++++++++++++++++-------------------- 1 file changed, 50 insertions(+), 61 deletions(-) diff --git a/src/parse-tsconfig/index.ts b/src/parse-tsconfig/index.ts index aabc7f1..da90231 100644 --- a/src/parse-tsconfig/index.ts +++ b/src/parse-tsconfig/index.ts @@ -6,7 +6,13 @@ import { readJsonc } from '../utils/read-jsonc.js'; import { implicitBaseUrlSymbol, configDirPlaceholder } from '../utils/constants.js'; import { resolveExtendsPath } from './resolve-extends-path.js'; -const filesProperties = ['files', 'include', 'exclude'] as const; +const pathRelative = (from: string, to: string) => normalizeRelativePath(path.relative(from, to)); + +const filesProperties = [ + 'files', + 'include', + 'exclude', +] as const; const resolveExtends = ( extendsPath: string, @@ -80,6 +86,11 @@ const resolveExtends = ( return extendsConfig; }; +const outputFields = [ + 'outDir', + 'declarationDir', +] as const; + const _parseTsconfig = ( tsconfigPath: string, cache?: Cache<string>, @@ -168,19 +179,11 @@ const _parseTsconfig = ( const unresolvedPath = compilerOptions[property]; if (unresolvedPath && !unresolvedPath.startsWith(configDirPlaceholder)) { const resolvedBaseUrl = path.resolve(directoryPath, unresolvedPath); - const relativeBaseUrl = normalizeRelativePath(path.relative( - directoryPath, - resolvedBaseUrl, - )); + const relativeBaseUrl = pathRelative(directoryPath, resolvedBaseUrl); compilerOptions[property] = relativeBaseUrl; } } - const outputFields = [ - 'outDir', - 'declarationDir', - ] as const satisfies Array<keyof NonNullable<TsConfigJson['compilerOptions']>>; - for (const outputField of outputFields) { let outputPath = compilerOptions[outputField]; @@ -231,27 +234,27 @@ const _parseTsconfig = ( return config; }; -function interpolateConfigDir<T extends string | string[]>( - filePaths: T, +const interpolateConfigDir = ( + filePath: string, configDir: string, - postProcess?: (input: string) => string -): T extends string ? string : string[]; -// eslint-disable-next-line pvtnbr/prefer-arrow-functions -function interpolateConfigDir( - filePaths: string | string[], - configDir: string, - postProcess: (input: string) => string = value => value, -): string | string[] { - if (Array.isArray(filePaths)) { - return filePaths.map(filePath => interpolateConfigDir(filePath, configDir, postProcess)); - } - - if (filePaths.startsWith(configDirPlaceholder)) { - return postProcess(slash(path.join(configDir, filePaths.slice(configDirPlaceholder.length)))); +) => { + if (filePath.startsWith(configDirPlaceholder)) { + return slash(path.join(configDir, filePath.slice(configDirPlaceholder.length))); } +}; - return filePaths; -} +/** + * @see https://github.com/microsoft/TypeScript/issues/57485#issuecomment-2027787456 + * exclude paths, as it requires custom processing + */ +const compilerFieldsWithConfigDir = [ + 'outDir', + 'declarationDir', + 'outFile', + 'rootDir', + 'baseUrl', + 'tsBuildInfoFile', +] as const; export const parseTsconfig = ( tsconfigPath: string, @@ -259,44 +262,29 @@ export const parseTsconfig = ( ): TsConfigJsonResolved => { const resolvedTsconfigPath = path.resolve(tsconfigPath); const config = _parseTsconfig(resolvedTsconfigPath, cache); - - /** - * @see https://github.com/microsoft/TypeScript/issues/57485#issuecomment-2027787456 - * exclude paths, as it requires custom processing - */ - const compilerFieldsWithConfigDir = [ - 'outDir', - 'declarationDir', - 'outFile', - 'rootDir', - 'baseUrl', - 'tsBuildInfoFile', - 'rootDirs', - 'typeRoots', - ] as const satisfies Array<keyof NonNullable<TsConfigJson['compilerOptions']>>; - const configDir = path.dirname(resolvedTsconfigPath); - if (config.compilerOptions) { - for (const field of compilerFieldsWithConfigDir) { - const value = config.compilerOptions[field]; + const { compilerOptions } = config; + if (compilerOptions) { + for (const property of compilerFieldsWithConfigDir) { + const value = compilerOptions[property]; if (value) { - /** - * I used Object.assign instead of the direct assignment to work around TS bug - * (it fails to infer types correctly). - * @see https://github.com/microsoft/TypeScript/issues/33912 - */ - Object.assign(config.compilerOptions, { - [field]: interpolateConfigDir( - value, - configDir, - interpolated => normalizeRelativePath(path.relative(configDir, interpolated)), - ), + const resolvedPath = interpolateConfigDir(value, configDir); + compilerOptions[property] = resolvedPath ? pathRelative(configDir, resolvedPath) : value; + } + } + + for (const property of ['rootDirs', 'typeRoots'] as const) { + const value = compilerOptions[property]; + if (value) { + compilerOptions[property] = value.map((v) => { + const resolvedPath = interpolateConfigDir(v, configDir); + return resolvedPath ? pathRelative(configDir, resolvedPath) : v; }); } } - const { paths } = config.compilerOptions; + const { paths } = compilerOptions; if (paths) { for (const name of Object.keys(paths)) { paths[name] = paths[name].map( @@ -307,8 +295,9 @@ export const parseTsconfig = ( } for (const property of filesProperties) { - if (config[property]) { - config[property] = config[property].map( + const value = config[property]; + if (value) { + config[property] = value.map( filePath => interpolateConfigDir(filePath, configDir) ?? filePath, ); } From 2b5b72181949505f81529b6dc035e4b8c15589b6 Mon Sep 17 00:00:00 2001 From: Hiroki Osame <hiroki.osame@gmail.com> Date: Thu, 12 Sep 2024 16:27:36 +0900 Subject: [PATCH 6/6] wip --- tests/specs/parse-tsconfig/parses.spec.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/specs/parse-tsconfig/parses.spec.ts b/tests/specs/parse-tsconfig/parses.spec.ts index 8f1f649..4398405 100644 --- a/tests/specs/parse-tsconfig/parses.spec.ts +++ b/tests/specs/parse-tsconfig/parses.spec.ts @@ -73,6 +73,7 @@ export default testSuite(({ describe }) => { esModuleInterop: true, declaration: true, outDir: 'dist', + declarationDir: 'dist-declaration', strict: true, target: 'esnext', rootDir: 'root-dir',