Latest Results
fix(tsconfig): do not let a reference without paths shadow the parent (#1424)
## Summary
Fixes #1423. With `references: "auto"`, a project reference that lives
in the same directory as its parent and has no `paths` of its own
swallowed the parent's `paths`, so aliased imports stopped resolving:
```jsonc
// tsconfig.json
{
"compilerOptions": { "paths": { "$/*": ["./src/*"] } },
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}
// tsconfig.node.json ā composite, include: ["vite.config.ts"], no paths
```
```js
resolver.sync("<root>/src", "$/utils/helper") // main: NotFound; this PR: <root>/src/utils/helper.ts
resolver.sync("<root>/src", "./utils/helper") // both: <root>/src/utils/helper.ts
```
## Cause
`TsConfig::resolve_references_then_self_paths` (`src/tsconfig.rs`)
returns the first reference whose `paths_base` covers the importer:
```rust
for tsconfig in &self.references_resolved {
if path.starts_with(&tsconfig.compiler_options.paths_base) {
return tsconfig.resolve_path_alias(specifier);
}
}
```
A sibling reference shares the parent's directory, so its `paths_base`
covers every file. Its alias list is empty, it is returned anyway, and
the parent's `paths` are never consulted. `5b1ae7f3` (#860) replaced the
previous `[ref_paths, self_paths].concat()` with this early `return`,
which is the regression.
## Fix
Only accept a reference's alias list when it is non-empty; otherwise
keep looking and fall back to the parent's `paths`:
```rust
if path.starts_with(&tsconfig.compiler_options.paths_base) {
let paths = tsconfig.resolve_path_alias(specifier);
if !paths.is_empty() {
return paths;
}
}
```
Reference priority is preserved: a reference that does map the specifier
still wins over the parent (#1086 / #1151). For `resolve()` the importer
is a directory, so `paths_base` cannot establish file ownership
(`is_file_included_in_tsconfig` is only available on the
`resolve_file()` path, which is unchanged). An empty mapping therefore
falls through, matching the pre-#860 `concat` semantics.
**Note:** `resolve_file()` remains the exact path ā it can use
`include`/`files` to pick the owning project, and
`TsconfigDiscovery::Auto` requires it. This change only restores the
`resolve()` + `Manual` + `references: auto` behaviour that regressed; it
does not make the directory heuristic authoritative.
## Tests
- `sibling_reference_without_paths_does_not_shadow_parent` ā new fixture
`fixtures/tsconfig/cases/project-references-sibling-paths`; covers both
`resolve()` and `resolve_file()`.
- `directory_fallback_to_parent_paths_for_unmapped_reference` ā locks
the directory-level fallback (`project-c` ā parent mapping) and the
`resolve_file()` `NotFound`, so the widened fallback cannot be mistaken
for a bug later.
- Existing project-reference tests (`auto`,
`referenced_paths_win_over_root_with_no_paths`, walk-up/allowJs cases)
unchanged.
- `cargo test`, `cargo test --all-features`, `cargo clippy
--all-features --all-targets -- -D warnings`, `cargo fmt --check` clean;
verified against the reproduction repo with a freshly built binary. Latest Branches
0%
-7%
-2%
release-plz-2026-09-06T16-57-58Z Ā© 2026 CodSpeed Technology