Skip to content

Commit 0ba0db9

Browse files
authored
fix: fix resolution when resolving non-relative specifier on tsconfig baseUrl (#903)
This is the 3rd case in `https://github.com/microsoft/TypeScript/issues/62207` This PR forbids resolution of `foo.js` if `tsconfig.conf` does not have a `baseUrl` and allows resolution of `foo.js` if `tsconfig.conf` has a `baseUrl`
1 parent 79b5660 commit 0ba0db9

File tree

7 files changed

+52
-121
lines changed

7 files changed

+52
-121
lines changed

fixtures/tsconfig/cases/base-url/index.ts

Whitespace-only changes.

fixtures/tsconfig/cases/base-url/src/foo.js

Whitespace-only changes.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"compilerOptions": {
3+
"baseUrl": "./src"
4+
}
5+
}

src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,9 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
370370
ctx: &mut Ctx,
371371
) -> Result<CachedPath, ResolveError> {
372372
// tsconfig-paths
373-
if let Some(path) = self.load_tsconfig_paths(cached_path, specifier, tsconfig, ctx)? {
373+
if let Some(path) =
374+
self.resolve_tsconfig_compiler_options(cached_path, specifier, tsconfig, ctx)?
375+
{
374376
return Ok(path);
375377
}
376378

src/tests/tsconfig_paths.rs

Lines changed: 11 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,9 @@
22
//!
33
//! Fixtures copied from <https://github.com/parcel-bundler/parcel/tree/v2/packages/utils/node-resolver-core/test/fixture/tsconfig>.
44
5-
use std::path::{Path, PathBuf};
6-
75
use crate::{
8-
JSONError, ResolveError, ResolveOptions, Resolver, TsConfig, TsconfigDiscovery,
9-
TsconfigOptions, TsconfigReferences,
6+
JSONError, ResolveError, ResolveOptions, Resolver, TsconfigDiscovery, TsconfigOptions,
7+
TsconfigReferences,
108
};
119

1210
// <https://github.com/parcel-bundler/parcel/blob/b6224fd519f95e68d8b93ba90376fd94c8b76e69/packages/utils/node-resolver-rs/src/lib.rs#L2303>
@@ -28,6 +26,8 @@ pub fn tsconfig_resolve_impl(tsconfig_discovery: bool) {
2826
(f.join("cases/extends-multiple"), None, "foo", f.join("cases/extends-multiple/foo.js")),
2927
(f.join("cases/absolute-alias"), None, "/images/foo.js", f.join("cases/absolute-alias/public/images/foo.ts")),
3028
(f.join("cases/references-extend"), Some("src/index.ts"), "ts-path", f.join("src/foo.js")),
29+
// Support `base_url` 3rd case <https://github.com/microsoft/TypeScript/issues/62207>
30+
(f.join("cases/base-url"), Some("src/index.ts"), "foo.js", f.join("cases/base-url/src/foo.js")),
3131
];
3232

3333
for (dir, subdir, request, expected) in pass {
@@ -63,6 +63,13 @@ pub fn tsconfig_resolve_impl(tsconfig_discovery: bool) {
6363
f.join("cases").join("extends-not-found").join("not-found"),
6464
)),
6565
),
66+
// no `base_url` <https://github.com/microsoft/TypeScript/issues/62207>
67+
(
68+
f.clone(),
69+
"src/foo.js",
70+
f.join("tsconfig.json"),
71+
Err(ResolveError::NotFound("src/foo.js".to_string())),
72+
),
6673
];
6774

6875
for (path, request, tsconfig, expected) in data {
@@ -173,106 +180,6 @@ fn empty() {
173180
assert_eq!(resolved_path, Ok(f.join("index.js")));
174181
}
175182

176-
// <https://github.com/parcel-bundler/parcel/blob/c8f5c97a01f643b4d5c333c02d019ef2618b44a5/packages/utils/node-resolver-rs/src/tsconfig.rs#L193C12-L193C12>
177-
#[test]
178-
fn test_paths() {
179-
let path = Path::new("/foo");
180-
let tsconfig_json = serde_json::json!({
181-
"compilerOptions": {
182-
"paths": {
183-
"jquery": ["node_modules/jquery/dist/jquery"],
184-
"*": ["generated/*"],
185-
"bar/*": ["test/*"],
186-
"bar/baz/*": ["baz/*", "yo/*"],
187-
"@/components/*": ["components/*"],
188-
"url": ["node_modules/my-url"],
189-
}
190-
}
191-
})
192-
.to_string();
193-
let tsconfig =
194-
TsConfig::parse(true, &path.join("tsconfig.json"), tsconfig_json).unwrap().build();
195-
196-
let data = [
197-
("jquery", vec!["/foo/node_modules/jquery/dist/jquery"]),
198-
("test", vec!["/foo/generated/test"]),
199-
("test/hello", vec!["/foo/generated/test/hello"]),
200-
("bar/hi", vec!["/foo/test/hi"]),
201-
("bar/baz/hi", vec!["/foo/baz/hi", "/foo/yo/hi"]),
202-
("@/components/button", vec!["/foo/components/button"]),
203-
("url", vec!["/foo/node_modules/my-url"]),
204-
];
205-
206-
for (specifier, expected) in data {
207-
let paths = tsconfig.resolve_path_alias(specifier);
208-
let expected = expected
209-
.into_iter()
210-
.map(PathBuf::from)
211-
.chain(std::iter::once(path.join(specifier)))
212-
.collect::<Vec<_>>();
213-
assert_eq!(paths, expected, "{specifier}");
214-
}
215-
}
216-
217-
// <https://github.com/parcel-bundler/parcel/blob/c8f5c97a01f643b4d5c333c02d019ef2618b44a5/packages/utils/node-resolver-rs/src/tsconfig.rs#L233C6-L233C19>
218-
#[test]
219-
fn test_base_url() {
220-
let path = Path::new("/foo/tsconfig.json");
221-
let tsconfig_json = serde_json::json!({
222-
"compilerOptions": {
223-
"baseUrl": "./src"
224-
}
225-
})
226-
.to_string();
227-
let tsconfig = TsConfig::parse(true, path, tsconfig_json).unwrap().build();
228-
229-
let data = [
230-
("foo", vec!["/foo/src/foo"]),
231-
("components/button", vec!["/foo/src/components/button"]),
232-
("./jquery", vec![]),
233-
];
234-
235-
for (specifier, expected) in data {
236-
let paths = tsconfig.resolve_path_alias(specifier);
237-
let expected = expected.into_iter().map(PathBuf::from).collect::<Vec<_>>();
238-
assert_eq!(paths, expected, "{specifier}");
239-
}
240-
}
241-
242-
// <https://github.com/parcel-bundler/parcel/blob/c8f5c97a01f643b4d5c333c02d019ef2618b44a5/packages/utils/node-resolver-rs/src/tsconfig.rs#L252>
243-
#[test]
244-
fn test_paths_and_base_url() {
245-
let path = Path::new("/foo/tsconfig.json");
246-
let tsconfig_json = serde_json::json!({
247-
"compilerOptions": {
248-
"baseUrl": "./src",
249-
"paths": {
250-
"*": ["generated/*"],
251-
"bar/*": ["test/*"],
252-
"bar/baz/*": ["baz/*", "yo/*"],
253-
"@/components/*": ["components/*"]
254-
}
255-
}
256-
})
257-
.to_string();
258-
let tsconfig = TsConfig::parse(true, path, tsconfig_json).unwrap().build();
259-
260-
let data = [
261-
("test", vec!["/foo/src/generated/test", "/foo/src/test"]),
262-
("test/hello", vec!["/foo/src/generated/test/hello", "/foo/src/test/hello"]),
263-
("bar/hi", vec!["/foo/src/test/hi", "/foo/src/bar/hi"]),
264-
("bar/baz/hi", vec!["/foo/src/baz/hi", "/foo/src/yo/hi", "/foo/src/bar/baz/hi"]),
265-
("@/components/button", vec!["/foo/src/components/button", "/foo/src/@/components/button"]),
266-
("./jquery", vec![]),
267-
];
268-
269-
for (specifier, expected) in data {
270-
let paths = tsconfig.resolve_path_alias(specifier);
271-
let expected = expected.into_iter().map(PathBuf::from).collect::<Vec<_>>();
272-
assert_eq!(paths, expected, "{specifier}");
273-
}
274-
}
275-
276183
#[test]
277184
fn test_merge_tsconfig() {
278185
let resolver = Resolver::default();

src/tsconfig.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -390,13 +390,15 @@ impl TsConfig {
390390
}
391391

392392
let compiler_options = &self.compiler_options;
393-
let base_url_iter = vec![compiler_options.paths_base.normalize_with(specifier)];
393+
394+
// if compiler_options
395+
// let base_url_iter = vec![compiler_options.paths_base.normalize_with(specifier)];
394396

395397
let Some(paths_map) = &compiler_options.paths else {
396-
return base_url_iter;
398+
return vec![];
397399
};
398400

399-
let paths = paths_map.get(specifier).map_or_else(
401+
paths_map.get(specifier).map_or_else(
400402
|| {
401403
let mut longest_prefix_length = 0;
402404
let mut longest_suffix_length = 0;
@@ -428,9 +430,14 @@ impl TsConfig {
428430
})
429431
},
430432
Clone::clone,
431-
);
433+
)
434+
}
432435

433-
paths.into_iter().chain(base_url_iter).collect()
436+
pub(crate) fn resolve_base_url(&self, specifier: &str) -> Option<PathBuf> {
437+
self.compiler_options
438+
.base_url
439+
.is_some()
440+
.then(|| self.compiler_options.paths_base.normalize_with(specifier))
434441
}
435442
}
436443

src/tsconfig_resolver.rs

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,12 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
255255
Ok(())
256256
}
257257

258-
pub(crate) fn load_tsconfig_paths(
258+
/// Resolves
259+
/// * `compilerOptions.paths`
260+
/// * `compilerOptions.rootDirs` (if specifier starts with `.`)
261+
/// * `compilerOptions.baseUrl` (if specifier does not start with `.`)
262+
// <https://github.com/microsoft/TypeScript/blob/v5.9.3/src/compiler/moduleNameResolver.ts#L1550>
263+
pub(crate) fn resolve_tsconfig_compiler_options(
259264
&self,
260265
cached_path: &CachedPath,
261266
specifier: &str,
@@ -285,20 +290,25 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
285290
};
286291
for path in paths {
287292
let resolved_path = self.cache.value(&path);
288-
if let Some(resolution) = self.load_as_file_or_directory(
289-
&resolved_path,
290-
".",
291-
Some(tsconfig),
292-
&mut Ctx::default(),
293-
)? {
293+
if let Some(resolution) =
294+
self.load_as_file_or_directory(&resolved_path, ".", Some(tsconfig), ctx)?
295+
{
294296
return Ok(Some(resolution));
295297
}
296298
}
297-
if specifier.starts_with('.')
298-
&& let Some(path) =
299+
if specifier.starts_with('.') {
300+
if let Some(path) =
299301
self.load_tsconfig_root_dirs(cached_path, specifier, tsconfig, ctx)?
300-
{
301-
return Ok(Some(path));
302+
{
303+
return Ok(Some(path));
304+
}
305+
} else if let Some(path) = tsconfig.resolve_base_url(specifier) {
306+
let resolved_path = self.cache.value(&path);
307+
if let Some(resolution) =
308+
self.load_as_file_or_directory(&resolved_path, ".", Some(tsconfig), ctx)?
309+
{
310+
return Ok(Some(resolution));
311+
}
302312
}
303313
Ok(None)
304314
}

0 commit comments

Comments
 (0)