Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
3 changes: 3 additions & 0 deletions fixtures/tsconfig/cases/extends-paths-outside/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "../../tsconfig.json"
}
Empty file.
4 changes: 4 additions & 0 deletions fixtures/tsconfig/cases/references-extend/tsconfig.a.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"extends": "./tsconfig.json",
"include": ["**/*.ts"]
}
10 changes: 10 additions & 0 deletions fixtures/tsconfig/cases/references-extend/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"extends": "../../tsconfig.json",
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.a.json"
}
]
}
16 changes: 11 additions & 5 deletions src/tests/tsconfig_paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub fn tsconfig_resolve_impl(tsconfig_discovery: bool) {
let pass = [
(f.clone(), None, "ts-path", f.join("src/foo.js")),
(f.join("nested"), None, "ts-path", f.join("nested/test.js")),
(f.join("cases/extends-paths-outside"), Some("src/index.js"), "ts-path", f.join("src/foo.js")),
(f.join("cases/index"), None, "foo", f.join("node_modules/tsconfig-index/foo.js")),
// This requires reading package.json.tsconfig field
// (f.join("cases/field"), "foo", f.join("node_modules/tsconfig-field/foo.js"))
Expand All @@ -26,6 +27,7 @@ pub fn tsconfig_resolve_impl(tsconfig_discovery: bool) {
(f.join("cases/extends-paths"), Some("src"), "@/index", f.join("cases/extends-paths/src/index.js")),
(f.join("cases/extends-multiple"), None, "foo", f.join("cases/extends-multiple/foo.js")),
(f.join("cases/absolute-alias"), None, "/images/foo.js", f.join("cases/absolute-alias/public/images/foo.ts")),
(f.join("cases/references-extend"), Some("src/index.ts"), "ts-path", f.join("src/foo.js")),
];

for (dir, subdir, request, expected) in pass {
Expand All @@ -43,7 +45,7 @@ pub fn tsconfig_resolve_impl(tsconfig_discovery: bool) {
});
let path = subdir.map_or_else(|| dir.clone(), |subdir| dir.join(subdir));
let resolved_path = resolver.resolve_file(&path, request).map(|f| f.full_path());
assert_eq!(resolved_path, Ok(expected), "{request} {path:?}");
assert_eq!(resolved_path, Ok(expected), "{request} {path:?} {tsconfig_discovery}");
}

let data = [
Expand Down Expand Up @@ -174,7 +176,7 @@ fn empty() {
// <https://github.com/parcel-bundler/parcel/blob/c8f5c97a01f643b4d5c333c02d019ef2618b44a5/packages/utils/node-resolver-rs/src/tsconfig.rs#L193C12-L193C12>
#[test]
fn test_paths() {
let path = Path::new("/foo/tsconfig.json");
let path = Path::new("/foo");
let tsconfig_json = serde_json::json!({
"compilerOptions": {
"paths": {
Expand All @@ -188,7 +190,8 @@ fn test_paths() {
}
})
.to_string();
let tsconfig = TsConfig::parse(true, path, tsconfig_json).unwrap().build();
let tsconfig =
TsConfig::parse(true, &path.join("tsconfig.json"), tsconfig_json).unwrap().build();

let data = [
("jquery", vec!["/foo/node_modules/jquery/dist/jquery"]),
Expand All @@ -197,13 +200,16 @@ fn test_paths() {
("bar/hi", vec!["/foo/test/hi"]),
("bar/baz/hi", vec!["/foo/baz/hi", "/foo/yo/hi"]),
("@/components/button", vec!["/foo/components/button"]),
("./jquery", vec![]),
("url", vec!["/foo/node_modules/my-url"]),
];

for (specifier, expected) in data {
let paths = tsconfig.resolve_path_alias(specifier);
let expected = expected.into_iter().map(PathBuf::from).collect::<Vec<_>>();
let expected = expected
.into_iter()
.map(PathBuf::from)
.chain(std::iter::once(path.join(specifier)))
.collect::<Vec<_>>();
assert_eq!(paths, expected, "{specifier}");
}
}
Expand Down
104 changes: 41 additions & 63 deletions src/tsconfig.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const TEMPLATE_VARIABLE: &str = "${configDir}";

const GLOB_ALL_PATTERN: &str = "**/*";

pub type CompilerOptionsPathsMap = IndexMap<String, Vec<String>, BuildHasherDefault<FxHasher>>;
pub type CompilerOptionsPathsMap = IndexMap<String, Vec<PathBuf>, BuildHasherDefault<FxHasher>>;

/// Project Reference
///
Expand Down Expand Up @@ -88,6 +88,17 @@ impl TsConfig {
};
tsconfig.root = root;
tsconfig.path = path.to_path_buf();
tsconfig.compiler_options.paths_base =
tsconfig.compiler_options.base_url.as_ref().map_or_else(
|| tsconfig.directory().to_path_buf(),
|base_url| {
if base_url.to_string_lossy().starts_with(TEMPLATE_VARIABLE) {
base_url.clone()
} else {
tsconfig.directory().normalize_with(base_url)
}
},
);
Ok(tsconfig)
}

Expand Down Expand Up @@ -149,16 +160,6 @@ impl TsConfig {
!self.references.is_empty()
}

/// Returns the base path from which to resolve aliases.
///
/// The base path can be configured by the user as part of the
/// [CompilerOptions]. If not configured, it returns the directory in which
/// the tsconfig itself is found.
#[must_use]
pub(crate) fn base_path(&self) -> &Path {
self.compiler_options.base_url.as_ref().map_or_else(|| self.directory(), |p| p.as_path())
}

/// Inherits settings from the given tsconfig into `self`.
#[allow(clippy::cognitive_complexity, clippy::too_many_lines)]
pub(crate) fn extend_tsconfig(&mut self, tsconfig: &Self) {
Expand All @@ -180,31 +181,18 @@ impl TsConfig {
self.exclude = Some(exclude.clone());
}

let tsconfig_dir = tsconfig.directory();
let compiler_options = &mut self.compiler_options;

if compiler_options.base_url.is_none()
&& let Some(base_url) = &tsconfig.compiler_options.base_url
{
compiler_options.base_url = Some(if base_url.starts_with(TEMPLATE_VARIABLE) {
base_url.clone()
} else {
tsconfig_dir.join(base_url).normalize()
});
if compiler_options.base_url.is_none() {
compiler_options.base_url.clone_from(&tsconfig.compiler_options.base_url);
if tsconfig.compiler_options.base_url.is_some() {
compiler_options.paths_base.clone_from(&tsconfig.compiler_options.paths_base);
}
}

if compiler_options.paths.is_none() {
let paths_base = compiler_options.base_url.as_ref().map_or_else(
|| tsconfig_dir.to_path_buf(),
|path| {
if path.starts_with(TEMPLATE_VARIABLE) {
path.clone()
} else {
tsconfig_dir.join(path).normalize()
}
},
);
compiler_options.paths_base = paths_base;
if compiler_options.base_url.is_none() && tsconfig.compiler_options.base_url.is_none() {
compiler_options.paths_base.clone_from(&tsconfig.compiler_options.paths_base);
}
compiler_options.paths.clone_from(&tsconfig.compiler_options.paths);
}

Expand Down Expand Up @@ -328,30 +316,27 @@ impl TsConfig {
}

if let Some(base_url) = &self.compiler_options.base_url {
let base_url = self.adjust_path(base_url.clone());
self.compiler_options.base_url = Some(base_url);
self.compiler_options.base_url = Some(self.adjust_path(base_url.clone()));
}

if self.compiler_options.paths.is_some() {
// `paths_base` should use config dir if it is not resolved with base url nor extended
// with another tsconfig.
if let Some(base_url) = self.compiler_options.base_url.clone() {
self.compiler_options.paths_base = base_url;
}

if self.compiler_options.paths_base.as_os_str().is_empty() {
self.compiler_options.paths_base.clone_from(&config_dir);
}
if let Some(stripped_path) =
self.compiler_options.paths_base.to_string_lossy().strip_prefix(TEMPLATE_VARIABLE)
{
self.compiler_options.paths_base =
config_dir.join(stripped_path.trim_start_matches('/'));
}

if self.compiler_options.paths.is_some() {
// Substitute template variable in `tsconfig.compilerOptions.paths`.
for paths in self.compiler_options.paths.as_mut().unwrap().values_mut() {
for path in paths {
if let Some(stripped_path) = path.strip_prefix(TEMPLATE_VARIABLE) {
*path = config_dir
.join(stripped_path.trim_start_matches('/'))
.to_string_lossy()
.to_string();
}
*path = if let Some(stripped_path) =
path.to_string_lossy().strip_prefix(TEMPLATE_VARIABLE)
{
config_dir.join(stripped_path.trim_start_matches('/'))
} else {
self.compiler_options.paths_base.normalize_with(&path)
};
}
}
}
Expand All @@ -378,7 +363,7 @@ impl TsConfig {
specifier: &str,
) -> Vec<PathBuf> {
for tsconfig in &self.references_resolved {
if path.starts_with(tsconfig.base_path()) {
if path.starts_with(&tsconfig.compiler_options.paths_base) {
return tsconfig.resolve_path_alias(specifier);
}
}
Expand All @@ -398,10 +383,7 @@ impl TsConfig {
}

let compiler_options = &self.compiler_options;
let base_url_iter = compiler_options
.base_url
.as_ref()
.map_or_else(Vec::new, |base_url| vec![base_url.normalize_with(specifier)]);
let base_url_iter = vec![compiler_options.paths_base.normalize_with(specifier)];

let Some(paths_map) = &compiler_options.paths else {
return base_url_iter;
Expand Down Expand Up @@ -429,23 +411,19 @@ impl TsConfig {
paths
.iter()
.map(|path| {
path.replace(
PathBuf::from(path.to_string_lossy().replace(
'*',
&specifier[longest_prefix_length
..specifier.len() - longest_suffix_length],
)
))
})
.collect::<Vec<_>>()
})
},
Clone::clone,
);

paths
.into_iter()
.map(|p| compiler_options.paths_base.normalize_with(p))
.chain(base_url_iter)
.collect()
paths.into_iter().chain(base_url_iter).collect()
}
}

Expand All @@ -460,7 +438,7 @@ pub struct CompilerOptions {
/// Path aliases.
pub paths: Option<CompilerOptionsPathsMap>,

/// The actual base from where path aliases are resolved.
/// The "base_url" at which this tsconfig is defined.
#[serde(skip)]
pub(crate) paths_base: PathBuf,

Expand Down
Loading