fix(tsconfig): resolve baseUrl / paths against the canonical tsconfig path#1148
Conversation
…nfig path When `extends` traverses a symlink (e.g. a pnpm workspace package), `baseUrl` and `paths` were anchored at the symlink's parent instead of the real tsconfig directory. `Cache::get_tsconfig` now canonicalizes the tsconfig file path and passes it to `TsConfig::parse` as a separate `canonical_path` parameter. `paths_base` is computed from the canonical directory while `tsconfig.path` (cache identity, error reporting, circular-extend detection) keeps the requested path unchanged.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #1148 +/- ##
==========================================
+ Coverage 93.11% 93.15% +0.03%
==========================================
Files 22 22
Lines 4141 4150 +9
==========================================
+ Hits 3856 3866 +10
+ Misses 285 284 -1 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Merging this PR will degrade performance by 4.65%
Warning Please fix the performance issues or acknowledge them on CodSpeed. Performance Changes
Tip Investigate this regression by commenting Comparing Footnotes
|
…em (#1150) ## Summary Closes #1075. `rootDirs` entries inherited via `extends` were being normalized against the caller tsconfig's directory at `build()` time, instead of against the directory of the config that actually declared them. This broke the SvelteKit pattern where `tsconfig.json` extends `.svelte-kit/tsconfig.json` and the latter declares `rootDirs: ["..", "./types"]` relative to itself. ## Fix Mirror the established `paths_base` pattern: normalize non-template `rootDirs` entries at parse time against each config's own canonical directory (`canonical_path.parent()`). The `build()` step keeps `${configDir}` template substitution only, so templates still resolve against the root caller's directory as before. After this change, `extend_tsconfig`'s existing clone-from-parent is correct without further work — the parent's entries are already absolute by the time the child inherits them. ## Verification Cross-checked against TypeScript 6.0.3 via `tsc --traceResolution`: - **SvelteKit case (no symlinks)** — TS resolves `rootDirs: ["..", "./types"]` declared in `.svelte-kit/tsconfig.json` against `.svelte-kit/`, exactly matching the fixed behavior. - **Symlinked `extends`** (`node_modules/shared-config` → real-configs) — TS anchors inherited `rootDirs` at the **canonical** path of the parent config, matching the `paths_base` precedent landed in #1148. Verified against typescript-go source (`internal/tsoptions/tsconfigparsing.go`): `rootDirs` is declared `IsFilePath: true` and `normalizeNonListOptionValue` calls `GetNormalizedAbsolutePath(value, basePath)` at parse time per config; `handleOptionConfigDirTemplateSubstitution` runs once afterward against the root caller's directory. New fixture `fixtures/tsconfig/cases/root-dirs-extends/` mirrors the SvelteKit reproduction; new test `test_root_dirs_via_extends` fails on `main` (`NotFound("./$types")`) and passes after the fix.
## 🤖 New release * `oxc_resolver`: 11.19.1 -> 11.19.2 * `oxc_resolver_napi`: 11.19.1 -> 11.19.2 <details><summary><i><b>Changelog</b></i></summary><p> ## `oxc_resolver` <blockquote> ## [11.19.2](v11.19.1...v11.19.2) - 2026-05-25 ### <!-- 1 -->🐛 Bug Fixes - *(tsconfig)* apply later-wins semantics for extends array ([#1156](#1156)) (by @Boshen) - *(tsconfig)* walk past a tsconfig that doesn't claim the file ([#1154](#1154)) (by @Boshen) - *(tsconfig)* let project references take priority over their parent ([#1151](#1151)) (by @Boshen) - *(tsconfig)* resolve `rootDirs` against the config that declared them ([#1150](#1150)) (by @Boshen) - *(tsconfig)* resolve `baseUrl` / `paths` against the canonical tsconfig path ([#1148](#1148)) (by @Boshen) - strip query fragments when calling `find_tsconfig` ([#1147](#1147)) (by @Boshen) - avoid panic in resolve_file for parentless paths ([#1053](#1053)) (by @Boshen) - *(dts)* strip ./ prefix from package entry when matching typesVersions ([#1051](#1051)) (by @Boshen) - *(dts)* expand Declaration to TypeScript|Declaration for package entry resolution ([#1050](#1050)) (by @Boshen) - *(dts)* prefer declaration extensions over JS in exports-resolved paths ([#1047](#1047)) (by @Boshen) - avoid wasm/wasi dead-code lint in NodePath ([#1043](#1043)) (by @Boshen) - *(napi)* replace panics with error returns to prevent WASM traps ([#1055](#1055)) (by @Boshen) ### <!-- 2 -->🚜 Refactor - remove clear_cache test that dynamically creates fixtures (by @Boshen) - move resolve and misc fixtures into fixtures/integration (by @Boshen) - replace ignored doctest with link to example (by @Boshen) - consolidate fixture directories for better test file mapping (by @Boshen) - replace `url` crate with `percent-encoding` ([#1065](#1065)) (by @Boshen) ### <!-- 4 -->⚡ Performance - *(cache)* pack CachedPathImpl::meta into a CachedMeta byte ([#1144](#1144)) (by @Boshen) - *(cache)* store canonical path as Box<Path> instead of PathBuf ([#1143](#1143)) (by @Boshen) - *(alias)* fast-reject alias entries by cached first byte ([#1142](#1142)) (by @Boshen) ### <!-- 6 -->🧪 Testing - *(tsconfig)* port lookup scenarios from typescript-go ([#1155](#1155)) (by @Boshen) - add 28 tests to improve coverage (92% → 93%) ([#1082](#1082)) (by @Boshen) ### Contributors * @Boshen * @renovate[bot] </blockquote> </p></details> --- This PR was generated with [release-plz](https://github.com/release-plz/release-plz/). Co-authored-by: oxc-guard[bot] <276638029+oxc-guard[bot]@users.noreply.github.com>
closes #1080