Skip to content

Commit 3da75ab

Browse files
mizdraclaude
andauthored
fix(core): resolve relative dtsOutDir against defining tsconfig directory (#373)
* test: fix wrong test * fix(core): resolve relative dtsOutDir against defining tsconfig directory Previously, `dtsOutDir` defined in a base tsconfig was resolved relative to the entry tsconfig directory. Now relative paths are resolved against the tsconfig file that defines them, matching TypeScript's behavior for options like `outDir`. Use `${configDir}` to opt into consumer-relative resolution. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 1614b39 commit 3da75ab

3 files changed

Lines changed: 25 additions & 14 deletions

File tree

.changeset/forty-eels-unite.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@css-modules-kit/core': patch
3+
---
4+
5+
fix(core): resolve relative dtsOutDir against defining tsconfig directory

packages/core/src/config.test.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ describe('readConfigFile', () => {
210210
const iff = await createIFF({
211211
'node_modules/some-pkg/tsconfig.json': dedent`
212212
{
213-
"cmkOptions": { "dtsOutDir": "generated/cmk" }
213+
"cmkOptions": { "dtsOutDir": "\${configDir}/generated/cmk" }
214214
}
215215
`,
216216
'tsconfig.json': dedent`
@@ -268,8 +268,7 @@ describe('readConfigFile', () => {
268268
}),
269269
);
270270
});
271-
// FIXME
272-
test.fails('resolves relative paths against the defining tsconfig directory', async () => {
271+
test('resolves relative paths against the defining tsconfig directory', async () => {
273272
const iff = await createIFF({
274273
'tsconfig.base.json': dedent`
275274
{

packages/core/src/config.ts

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import ts from 'typescript';
22
import { TsConfigFileNotFoundError } from './error.js';
3-
import { basename, dirname, join, resolve } from './path.js';
3+
import { basename, dirname, join, relative, resolve } from './path.js';
44
import type { Diagnostic } from './type.js';
55

66
// https://github.com/microsoft/TypeScript/blob/caf1aee269d1660b4d2a8b555c2d602c97cb28d7/src/compiler/commandLineParser.ts#L3006
@@ -67,7 +67,7 @@ export interface CMKConfig {
6767

6868
/**
6969
* The config loaded from `ts.ParsedCommandLine['raw']`.
70-
* This is unnormalized. Paths are relative, and some options may be omitted.
70+
* This is unnormalized. Paths are relative from the entry tsconfig file (basePath), and some options may be omitted.
7171
*/
7272
interface UnnormalizedRawConfig {
7373
includes?: string[];
@@ -100,7 +100,7 @@ function isTsConfigFileExists(fileName: string): boolean {
100100
return ts.findConfigFile(dirname(fileName), ts.sys.fileExists.bind(ts.sys), basename(fileName)) !== undefined;
101101
}
102102

103-
function parseRawData(raw: unknown, tsConfigSourceFile: ts.TsConfigSourceFile): ParsedRawData {
103+
function parseRawData(raw: unknown, tsConfigSourceFile: ts.TsConfigSourceFile, basePath: string): ParsedRawData {
104104
const result: ParsedRawData = {
105105
config: {},
106106
diagnostics: [],
@@ -137,7 +137,9 @@ function parseRawData(raw: unknown, tsConfigSourceFile: ts.TsConfigSourceFile):
137137
}
138138
if ('dtsOutDir' in raw.cmkOptions) {
139139
if (typeof raw.cmkOptions.dtsOutDir === 'string') {
140-
result.config.dtsOutDir = raw.cmkOptions.dtsOutDir;
140+
result.config.dtsOutDir = startsWithConfigDirTemplate(raw.cmkOptions.dtsOutDir)
141+
? raw.cmkOptions.dtsOutDir
142+
: relative(basePath, join(dirname(tsConfigSourceFile.fileName), raw.cmkOptions.dtsOutDir));
141143
} else {
142144
result.diagnostics.push({
143145
category: 'error',
@@ -193,7 +195,7 @@ function parseRawData(raw: unknown, tsConfigSourceFile: ts.TsConfigSourceFile):
193195
return result;
194196
}
195197

196-
function parseTsConfigFile(fileName: string) {
198+
function parseTsConfigFile(fileName: string, basePath: string) {
197199
const tsConfigSourceFile = ts.readJsonConfigFile(fileName, ts.sys.readFile.bind(ts.sys));
198200
// MEMO: `tsConfigSourceFile.parseDiagnostics` (Internal API) contains a syntax error for `tsconfig.json`.
199201
// However, it is ignored so that ts-plugin will work even if `tsconfig.json` is somewhat broken.
@@ -216,7 +218,7 @@ function parseTsConfigFile(fileName: string) {
216218
],
217219
);
218220
// Read options from `parsedCommandLine.raw`
219-
const parsedRawData = parseRawData(parsedCommandLine.raw, tsConfigSourceFile);
221+
const parsedRawData = parseRawData(parsedCommandLine.raw, tsConfigSourceFile, basePath);
220222

221223
return {
222224
extendedSourceFiles: tsConfigSourceFile.extendedSourceFiles,
@@ -229,9 +231,15 @@ function parseTsConfigFile(fileName: string) {
229231
};
230232
}
231233

234+
const configDirTemplate = /^\$\{configDir}/i;
235+
// https://github.com/microsoft/TypeScript/blob/7b8cb3bdf82f400642b73173f941335775d6f730/src/compiler/commandLineParser.ts#L3300
236+
function startsWithConfigDirTemplate(path: string) {
237+
return configDirTemplate.test(path);
238+
}
239+
232240
// https://github.com/microsoft/TypeScript/blob/55423abe4d029017f19b6e4c32097591994836b4/src/compiler/commandLineParser.ts#L3299-L3328
233241
function getSubstitutedPath(path: string, basePath: string) {
234-
return join(basePath, path.replace(/^\$\{configDir}/i, './'));
242+
return join(basePath, path.replace(configDirTemplate, './'));
235243
}
236244

237245
/**
@@ -246,22 +254,21 @@ export function readConfigFile(project: string): CMKConfig {
246254
const configFileName = findTsConfigFile(project);
247255
if (!configFileName) throw new TsConfigFileNotFoundError();
248256

249-
const parsedTsConfig = parseTsConfigFile(configFileName);
257+
const basePath = dirname(configFileName);
258+
const parsedTsConfig = parseTsConfigFile(configFileName, basePath);
250259

251260
// The options read from `parsedCommandLine.raw` do not inherit values from the file specified in `extends`.
252261
// So here we read the options from those files and merge them into `parsedRawData`.
253262
if (parsedTsConfig.extendedSourceFiles) {
254263
for (const extendedSourceFile of parsedTsConfig.extendedSourceFiles) {
255264
if (isTsConfigFileExists(extendedSourceFile)) {
256-
const base = parseTsConfigFile(extendedSourceFile);
265+
const base = parseTsConfigFile(extendedSourceFile, basePath);
257266
parsedTsConfig.config = { ...base.config, ...parsedTsConfig.config };
258267
parsedTsConfig.diagnostics = [...base.diagnostics, ...parsedTsConfig.diagnostics];
259268
}
260269
}
261270
}
262271

263-
const basePath = dirname(configFileName);
264-
265272
return {
266273
// If `include` is not specified, fallback to the default include spec。
267274
// ref: https://github.com/microsoft/TypeScript/blob/caf1aee269d1660b4d2a8b555c2d602c97cb28d7/src/compiler/commandLineParser.ts#L3102

0 commit comments

Comments
 (0)