Skip to content

Commit a40b894

Browse files
authored
feat: custom check_js resolver when walking graph (#566)
1 parent 7413a92 commit a40b894

File tree

3 files changed

+169
-45
lines changed

3 files changed

+169
-45
lines changed

src/graph.rs

+154-34
Original file line numberDiff line numberDiff line change
@@ -1299,10 +1299,33 @@ pub enum ModuleEntryRef<'a> {
12991299
Redirect(&'a ModuleSpecifier),
13001300
}
13011301

1302+
pub trait CheckJsResolver: std::fmt::Debug {
1303+
fn resolve(&self, specifier: &ModuleSpecifier) -> bool;
1304+
}
1305+
1306+
#[derive(Debug, Clone, Copy)]
1307+
pub enum CheckJsOption<'a> {
1308+
True,
1309+
False,
1310+
Custom(&'a dyn CheckJsResolver),
1311+
}
1312+
1313+
impl<'a> CheckJsOption<'a> {
1314+
pub fn resolve(&self, specifier: &ModuleSpecifier) -> bool {
1315+
match self {
1316+
CheckJsOption::True => true,
1317+
CheckJsOption::False => false,
1318+
CheckJsOption::Custom(check_js_resolver) => {
1319+
check_js_resolver.resolve(specifier)
1320+
}
1321+
}
1322+
}
1323+
}
1324+
13021325
#[derive(Debug, Clone)]
1303-
pub struct WalkOptions {
1326+
pub struct WalkOptions<'a> {
13041327
/// Whether to walk js modules when `kind` is `GraphKind::TypesOnly`.
1305-
pub check_js: bool,
1328+
pub check_js: CheckJsOption<'a>,
13061329
pub follow_dynamic: bool,
13071330
/// Part of the graph to walk.
13081331
pub kind: GraphKind,
@@ -1324,22 +1347,22 @@ pub struct FillFromLockfileOptions<
13241347
pub package_specifiers: TPackageSpecifiersIter,
13251348
}
13261349

1327-
pub struct ModuleEntryIterator<'a> {
1350+
pub struct ModuleEntryIterator<'a, 'options> {
13281351
graph: &'a ModuleGraph,
13291352
seen: HashSet<&'a ModuleSpecifier>,
13301353
visiting: VecDeque<&'a ModuleSpecifier>,
13311354
follow_dynamic: bool,
13321355
kind: GraphKind,
1333-
check_js: bool,
1356+
check_js: CheckJsOption<'options>,
13341357
prefer_fast_check_graph: bool,
13351358
previous_module: Option<ModuleEntryRef<'a>>,
13361359
}
13371360

1338-
impl<'a> ModuleEntryIterator<'a> {
1361+
impl<'a, 'options> ModuleEntryIterator<'a, 'options> {
13391362
fn new(
13401363
graph: &'a ModuleGraph,
13411364
roots: impl Iterator<Item = &'a ModuleSpecifier>,
1342-
options: WalkOptions,
1365+
options: WalkOptions<'options>,
13431366
) -> Self {
13441367
let mut seen =
13451368
HashSet::<&'a ModuleSpecifier>::with_capacity(graph.specifiers_count());
@@ -1385,7 +1408,7 @@ impl<'a> ModuleEntryIterator<'a> {
13851408
/// An iterator over all the errors found when walking this iterator.
13861409
///
13871410
/// This can be useful in scenarios where you want to filter or ignore an error.
1388-
pub fn errors(self) -> ModuleGraphErrorIterator<'a> {
1411+
pub fn errors(self) -> ModuleGraphErrorIterator<'a, 'options> {
13891412
ModuleGraphErrorIterator::new(self)
13901413
}
13911414

@@ -1405,15 +1428,27 @@ impl<'a> ModuleEntryIterator<'a> {
14051428
}
14061429

14071430
/// Gets if the specified media type can be type checked.
1408-
fn is_checkable(&self, media_type: MediaType) -> bool {
1409-
self.check_js
1410-
|| !matches!(
1411-
media_type,
1412-
MediaType::JavaScript
1413-
| MediaType::Mjs
1414-
| MediaType::Cjs
1415-
| MediaType::Jsx
1416-
)
1431+
fn is_checkable(
1432+
&self,
1433+
specifier: &ModuleSpecifier,
1434+
media_type: MediaType,
1435+
) -> bool {
1436+
match media_type {
1437+
MediaType::TypeScript
1438+
| MediaType::Mts
1439+
| MediaType::Cts
1440+
| MediaType::Dts
1441+
| MediaType::Dmts
1442+
| MediaType::Dcts
1443+
| MediaType::Tsx
1444+
| MediaType::Json
1445+
| MediaType::Wasm => true,
1446+
MediaType::Css | MediaType::SourceMap | MediaType::Unknown => false,
1447+
MediaType::JavaScript
1448+
| MediaType::Jsx
1449+
| MediaType::Mjs
1450+
| MediaType::Cjs => self.check_js.resolve(specifier),
1451+
}
14171452
}
14181453

14191454
fn analyze_module_deps(
@@ -1441,15 +1476,15 @@ impl<'a> ModuleEntryIterator<'a> {
14411476
}
14421477
}
14431478

1444-
impl<'a> Iterator for ModuleEntryIterator<'a> {
1479+
impl<'a, 'options> Iterator for ModuleEntryIterator<'a, 'options> {
14451480
type Item = (&'a ModuleSpecifier, ModuleEntryRef<'a>);
14461481

14471482
fn next(&mut self) -> Option<Self::Item> {
14481483
match self.previous_module.take() {
14491484
Some(ModuleEntryRef::Module(module)) => match module {
14501485
Module::Js(module) => {
1451-
let check_types =
1452-
self.kind.include_types() && self.is_checkable(module.media_type);
1486+
let check_types = self.kind.include_types()
1487+
&& self.is_checkable(&module.specifier, module.media_type);
14531488
let module_deps = if check_types && self.prefer_fast_check_graph {
14541489
module.dependencies_prefer_fast_check()
14551490
} else {
@@ -1497,7 +1532,7 @@ impl<'a> Iterator for ModuleEntryIterator<'a> {
14971532
continue; // skip visiting the code module
14981533
}
14991534
} else if self.kind == GraphKind::TypesOnly
1500-
&& !self.is_checkable(module.media_type)
1535+
&& !self.is_checkable(&module.specifier, module.media_type)
15011536
{
15021537
continue; // skip visiting
15031538
}
@@ -1526,13 +1561,13 @@ impl<'a> Iterator for ModuleEntryIterator<'a> {
15261561
}
15271562
}
15281563

1529-
pub struct ModuleGraphErrorIterator<'a> {
1530-
iterator: ModuleEntryIterator<'a>,
1564+
pub struct ModuleGraphErrorIterator<'a, 'options> {
1565+
iterator: ModuleEntryIterator<'a, 'options>,
15311566
next_errors: Vec<ModuleGraphError>,
15321567
}
15331568

1534-
impl<'a> ModuleGraphErrorIterator<'a> {
1535-
pub fn new(iterator: ModuleEntryIterator<'a>) -> Self {
1569+
impl<'a, 'options> ModuleGraphErrorIterator<'a, 'options> {
1570+
pub fn new(iterator: ModuleEntryIterator<'a, 'options>) -> Self {
15361571
Self {
15371572
iterator,
15381573
next_errors: Default::default(),
@@ -1607,7 +1642,7 @@ impl<'a> ModuleGraphErrorIterator<'a> {
16071642
}
16081643
}
16091644

1610-
impl<'a> Iterator for ModuleGraphErrorIterator<'a> {
1645+
impl<'a, 'options> Iterator for ModuleGraphErrorIterator<'a, 'options> {
16111646
type Item = ModuleGraphError;
16121647

16131648
fn next(&mut self) -> Option<Self::Item> {
@@ -1634,7 +1669,9 @@ impl<'a> Iterator for ModuleGraphErrorIterator<'a> {
16341669
}
16351670

16361671
let check_types = kind.include_types()
1637-
&& self.iterator.is_checkable(module.media_type);
1672+
&& self
1673+
.iterator
1674+
.is_checkable(&module.specifier, module.media_type);
16381675
let module_deps = if check_types && prefer_fast_check_graph {
16391676
module.dependencies_prefer_fast_check()
16401677
} else {
@@ -1886,7 +1923,7 @@ impl ModuleGraph {
18861923
WalkOptions {
18871924
follow_dynamic: true,
18881925
kind: self.graph_kind,
1889-
check_js: true,
1926+
check_js: CheckJsOption::True,
18901927
prefer_fast_check_graph: false,
18911928
},
18921929
);
@@ -1921,11 +1958,11 @@ impl ModuleGraph {
19211958
}
19221959

19231960
/// Iterates over all the module entries in the module graph searching from the provided roots.
1924-
pub fn walk<'a>(
1961+
pub fn walk<'a, 'options>(
19251962
&'a self,
19261963
roots: impl Iterator<Item = &'a ModuleSpecifier>,
1927-
options: WalkOptions,
1928-
) -> ModuleEntryIterator<'a> {
1964+
options: WalkOptions<'options>,
1965+
) -> ModuleEntryIterator<'a, 'options> {
19291966
ModuleEntryIterator::new(self, roots, options)
19301967
}
19311968

@@ -2159,7 +2196,7 @@ impl ModuleGraph {
21592196
.walk(
21602197
self.roots.iter(),
21612198
WalkOptions {
2162-
check_js: true,
2199+
check_js: CheckJsOption::True,
21632200
kind: GraphKind::CodeOnly,
21642201
follow_dynamic: false,
21652202
prefer_fast_check_graph: false,
@@ -5915,7 +5952,7 @@ mod tests {
59155952
WalkOptions {
59165953
follow_dynamic: false,
59175954
kind: GraphKind::All,
5918-
check_js: true,
5955+
check_js: CheckJsOption::True,
59195956
prefer_fast_check_graph: false,
59205957
},
59215958
)
@@ -5930,7 +5967,7 @@ mod tests {
59305967
WalkOptions {
59315968
follow_dynamic: true,
59325969
kind: GraphKind::All,
5933-
check_js: true,
5970+
check_js: CheckJsOption::True,
59345971
prefer_fast_check_graph: false,
59355972
},
59365973
)
@@ -6066,7 +6103,7 @@ mod tests {
60666103
.walk(
60676104
roots.iter(),
60686105
WalkOptions {
6069-
check_js: true,
6106+
check_js: CheckJsOption::True,
60706107
follow_dynamic: false,
60716108
kind: GraphKind::All,
60726109
prefer_fast_check_graph: false,
@@ -6789,4 +6826,87 @@ mod tests {
67896826
);
67906827
}
67916828
}
6829+
6830+
#[tokio::test]
6831+
async fn check_js_option_custom() {
6832+
#[derive(Debug)]
6833+
struct CustomResolver;
6834+
6835+
impl CheckJsResolver for CustomResolver {
6836+
fn resolve(&self, specifier: &ModuleSpecifier) -> bool {
6837+
specifier.as_str() == "file:///true.js"
6838+
}
6839+
}
6840+
6841+
struct TestLoader;
6842+
impl Loader for TestLoader {
6843+
fn load(
6844+
&self,
6845+
specifier: &ModuleSpecifier,
6846+
_options: LoadOptions,
6847+
) -> LoadFuture {
6848+
let specifier = specifier.clone();
6849+
match specifier.as_str() {
6850+
"file:///valid.js" => Box::pin(async move {
6851+
Ok(Some(LoadResponse::Module {
6852+
specifier: specifier.clone(),
6853+
maybe_headers: None,
6854+
content: b"export {}".to_vec().into(),
6855+
}))
6856+
}),
6857+
"file:///true.js" => Box::pin(async move {
6858+
Ok(Some(LoadResponse::Module {
6859+
specifier: specifier.clone(),
6860+
maybe_headers: None,
6861+
content: b"// @ts-types='invalid'\nimport {} from './valid.js';"
6862+
.to_vec()
6863+
.into(),
6864+
}))
6865+
}),
6866+
"file:///false.js" => Box::pin(async move {
6867+
Ok(Some(LoadResponse::Module {
6868+
specifier: specifier.clone(),
6869+
maybe_headers: None,
6870+
// the 'invalid' shouldn't be visited here
6871+
content: b"// @ts-types='invalid'\nimport {} from './valid.js';"
6872+
.to_vec()
6873+
.into(),
6874+
}))
6875+
}),
6876+
"file:///main.ts" => Box::pin(async move {
6877+
Ok(Some(LoadResponse::Module {
6878+
specifier: specifier.clone(),
6879+
maybe_headers: None,
6880+
content: b"import './true.js'; import './false.js'"
6881+
.to_vec()
6882+
.into(),
6883+
}))
6884+
}),
6885+
_ => unreachable!(),
6886+
}
6887+
}
6888+
}
6889+
let loader = TestLoader;
6890+
let mut graph = ModuleGraph::new(GraphKind::All);
6891+
let roots = vec![Url::parse("file:///main.ts").unwrap()];
6892+
graph
6893+
.build(roots.clone(), &loader, Default::default())
6894+
.await;
6895+
assert_eq!(graph.specifiers_count(), 4);
6896+
let errors = graph
6897+
.walk(
6898+
roots.iter(),
6899+
WalkOptions {
6900+
check_js: CheckJsOption::Custom(&CustomResolver),
6901+
follow_dynamic: false,
6902+
kind: GraphKind::All,
6903+
prefer_fast_check_graph: false,
6904+
},
6905+
)
6906+
.errors()
6907+
.collect::<Vec<_>>();
6908+
6909+
// should only be 1 for true.js and not false.js
6910+
assert_eq!(errors.len(), 1);
6911+
}
67926912
}

0 commit comments

Comments
 (0)