Skip to content

Commit d8610fa

Browse files
committed
fix(tsconfig): dedupe project reference dependencies
1 parent a94f6ea commit d8610fa

3 files changed

Lines changed: 28 additions & 48 deletions

File tree

src/lib.rs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1578,6 +1578,7 @@ impl<Fs: FileSystem + Send + Sync> ResolverGeneric<Fs> {
15781578
}
15791579
let directory = tsconfig.directory().to_path_buf();
15801580
let current_path = tsconfig.path.clone();
1581+
let mut flattened_reference_paths = FxHashSet::default();
15811582
visited.insert(current_path.clone());
15821583
for reference in &mut tsconfig.references {
15831584
let reference_tsconfig_path = directory.normalize_with(&reference.path);
@@ -1618,10 +1619,19 @@ impl<Fs: FileSystem + Send + Sync> ResolverGeneric<Fs> {
16181619
},
16191620
)
16201621
.await?;
1621-
TsConfig::extend_file_dependencies(
1622-
&mut tsconfig.file_dependencies,
1623-
&reference_tsconfig.file_dependencies,
1624-
);
1622+
tsconfig
1623+
.file_dependencies
1624+
.extend(reference_tsconfig.file_dependencies.iter().cloned());
1625+
for nested in &reference_tsconfig.flattened_references {
1626+
if flattened_reference_paths.insert(nested.path.clone()) {
1627+
tsconfig.flattened_references.push(Arc::clone(nested));
1628+
}
1629+
}
1630+
if flattened_reference_paths.insert(reference_tsconfig.path.clone()) {
1631+
tsconfig
1632+
.flattened_references
1633+
.push(Arc::clone(&reference_tsconfig));
1634+
}
16251635
reference.tsconfig.replace(reference_tsconfig);
16261636
}
16271637
Ok(())

src/tests/tsconfig_project_references.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
33
use crate::{
44
tsconfig::FileDependencies, ResolveContext, ResolveError, ResolveOptions, Resolver, ResolverPath,
5-
TsConfig, TsconfigOptions, TsconfigReferences,
5+
TsconfigOptions, TsconfigReferences,
66
};
77

88
#[test]
@@ -11,15 +11,15 @@ fn file_dependencies_are_deduplicated() {
1111
file_dependencies.insert("/repo/app/tsconfig.json".into());
1212
file_dependencies.insert("/repo/base.json".into());
1313

14-
let dependencies = [
14+
let dependencies: FileDependencies = [
1515
"/repo/base.json".into(),
1616
"/repo/shared/tsconfig.json".into(),
1717
"/repo/shared/tsconfig.json".into(),
1818
]
1919
.into_iter()
2020
.collect();
2121

22-
TsConfig::extend_file_dependencies(&mut file_dependencies, &dependencies);
22+
file_dependencies.extend(dependencies.iter().cloned());
2323

2424
assert_eq!(
2525
file_dependencies

src/tsconfig.rs

Lines changed: 11 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::{hash::BuildHasherDefault, sync::Arc};
22

33
use camino::{Utf8Path, Utf8PathBuf};
44
use indexmap::{IndexMap, IndexSet};
5-
use rustc_hash::{FxHashSet, FxHasher};
5+
use rustc_hash::FxHasher;
66
use serde::Deserialize;
77

88
use crate::path::PathUtil;
@@ -43,6 +43,10 @@ pub struct TsConfig {
4343
/// Bubbled up project references with a reference to their tsconfig.
4444
#[serde(default)]
4545
pub references: Vec<ProjectReference>,
46+
47+
/// Flattened transitive project references in resolution order.
48+
#[serde(skip)]
49+
pub(crate) flattened_references: Vec<Arc<TsConfig>>,
4650
}
4751

4852
/// Compiler Options
@@ -158,52 +162,18 @@ impl TsConfig {
158162
.base_url
159163
.clone_from(&other_config.compiler_options.base_url);
160164
}
161-
Self::extend_file_dependencies(&mut self.file_dependencies, &other_config.file_dependencies);
162-
}
163-
164-
pub(crate) fn extend_file_dependencies(
165-
file_dependencies: &mut FileDependencies,
166-
dependencies: &FileDependencies,
167-
) {
168-
file_dependencies.extend(dependencies.iter().cloned());
165+
self
166+
.file_dependencies
167+
.extend(other_config.file_dependencies.iter().cloned());
169168
}
170169

171170
pub fn resolve(&self, path: &Utf8Path, specifier: &str) -> Vec<Utf8PathBuf> {
172-
let mut visited = FxHashSet::default();
173-
if let Some(matched) = self.find_reference_paths(path, specifier, &mut visited) {
174-
return matched;
175-
}
176-
self.resolve_path_alias(specifier)
177-
}
178-
179-
// Walks `references` recursively, returning the nearest reference whose
180-
// `base_path` contains `path`. Used to honor transitive project references
181-
// (A → B → C): a file inside C should resolve via C's own `paths` even
182-
// when the entry tsconfig is A and only B is listed directly in A's
183-
// references. Matches `tsc`'s "nearest tsconfig wins" semantics.
184-
fn find_reference_paths<'a>(
185-
&'a self,
186-
path: &Utf8Path,
187-
specifier: &str,
188-
visited: &mut FxHashSet<&'a Utf8Path>,
189-
) -> Option<Vec<Utf8PathBuf>> {
190-
if !visited.insert(self.path.as_path()) {
191-
return None;
192-
}
193-
194-
for tsconfig in self
195-
.references
196-
.iter()
197-
.filter_map(|reference| reference.tsconfig.as_ref())
198-
{
199-
if let Some(nested) = tsconfig.find_reference_paths(path, specifier, visited) {
200-
return Some(nested);
201-
}
171+
for tsconfig in &self.flattened_references {
202172
if path.starts_with(tsconfig.base_path()) {
203-
return Some(tsconfig.resolve_path_alias(specifier));
173+
return tsconfig.resolve_path_alias(specifier);
204174
}
205175
}
206-
None
176+
self.resolve_path_alias(specifier)
207177
}
208178

209179
// Copied from parcel

0 commit comments

Comments
 (0)