mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 04:08:41 +00:00
feat(syntax): module graph visitor. (#3062)
I've tried too hard to make it into a full-fledged depth first iterator, But had no success with it; It is the next best thing that I could've thought of. Provides a way to visit the module graph from a ModuleRecord as its entry point.
This commit is contained in:
parent
f4b8f0b533
commit
e6d11c6190
2 changed files with 237 additions and 0 deletions
|
|
@ -3,6 +3,7 @@
|
|||
pub mod class;
|
||||
pub mod identifier;
|
||||
pub mod keyword;
|
||||
pub mod module_graph_visitor;
|
||||
pub mod module_record;
|
||||
pub mod node;
|
||||
pub mod operator;
|
||||
|
|
|
|||
236
crates/oxc_syntax/src/module_graph_visitor.rs
Normal file
236
crates/oxc_syntax/src/module_graph_visitor.rs
Normal file
|
|
@ -0,0 +1,236 @@
|
|||
use std::{collections::HashSet, marker::PhantomData, path::PathBuf, sync::Arc};
|
||||
|
||||
use oxc_span::CompactStr;
|
||||
|
||||
use crate::module_record::ModuleRecord;
|
||||
|
||||
type ModulePair<'a> = (&'a CompactStr, &'a Arc<ModuleRecord>);
|
||||
|
||||
type FilterFn<'a> = dyn Fn(ModulePair, &ModuleRecord) -> bool + 'a;
|
||||
type EventFn<'a> = dyn FnMut(ModuleGraphVisitorEvent, ModulePair, &ModuleRecord) + 'a;
|
||||
type EnterFn<'a> = dyn FnMut(ModulePair, &ModuleRecord) + 'a;
|
||||
type LeaveFn<'a> = dyn FnMut(ModulePair, &ModuleRecord) + 'a;
|
||||
|
||||
/// A builder for creating visitors that walk through the module graph
|
||||
pub struct ModuleGraphVisitorBuilder<'a, T> {
|
||||
max_depth: u32,
|
||||
filter: Option<Box<FilterFn<'a>>>,
|
||||
event: Option<Box<EventFn<'a>>>,
|
||||
enter: Option<Box<EnterFn<'a>>>,
|
||||
leave: Option<Box<LeaveFn<'a>>>,
|
||||
_marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
/// Returning value of `visit_fold` closures, It will stop on returning a `Stop` variant,
|
||||
/// Otherwise it would continue with the iteration.
|
||||
pub enum VisitFoldWhile<T> {
|
||||
Stop(T),
|
||||
Next(T),
|
||||
}
|
||||
|
||||
/// Indicates the type of event for `EventFn` closure.
|
||||
pub enum ModuleGraphVisitorEvent {
|
||||
Enter,
|
||||
Leave,
|
||||
}
|
||||
|
||||
impl<T> VisitFoldWhile<T> {
|
||||
pub fn is_done(&self) -> bool {
|
||||
matches!(self, Self::Stop(_))
|
||||
}
|
||||
|
||||
pub fn into_inner(self) -> T {
|
||||
match self {
|
||||
Self::Stop(inner) | Self::Next(inner) => inner,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> ModuleGraphVisitorBuilder<'a, T> {
|
||||
/// Sets the `self.max_depth`.
|
||||
#[must_use]
|
||||
pub fn max_depth(mut self, max_depth: u32) -> Self {
|
||||
self.max_depth = max_depth;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the filter closure.
|
||||
#[must_use]
|
||||
pub fn filter<F: (Fn(ModulePair, &ModuleRecord) -> bool) + 'a>(mut self, filter: F) -> Self {
|
||||
self.filter = Some(Box::new(filter));
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the generic event closure.
|
||||
#[must_use]
|
||||
pub fn event<F: FnMut(ModuleGraphVisitorEvent, ModulePair, &ModuleRecord) + 'a>(
|
||||
mut self,
|
||||
event: F,
|
||||
) -> Self {
|
||||
self.event = Some(Box::new(event));
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the enter module event closure.
|
||||
#[must_use]
|
||||
pub fn enter<F: FnMut(ModulePair, &ModuleRecord) + 'a>(mut self, enter: F) -> Self {
|
||||
self.enter = Some(Box::new(enter));
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the leave module event closure.
|
||||
#[must_use]
|
||||
pub fn leave<F: FnMut(ModulePair, &ModuleRecord) + 'a>(mut self, leave: F) -> Self {
|
||||
self.leave = Some(Box::new(leave));
|
||||
self
|
||||
}
|
||||
|
||||
/// Behaves similar to a flat fold_while iteration.
|
||||
pub fn visit_fold<V: Fn(T, ModulePair, &ModuleRecord) -> VisitFoldWhile<T>>(
|
||||
self,
|
||||
initial_value: T,
|
||||
module: &ModuleRecord,
|
||||
visit: V,
|
||||
) -> ModuleGraphVisitResult<T> {
|
||||
let mut visitor =
|
||||
ModuleGraphVisitor { traversed: HashSet::new(), depth: 0, max_depth: self.max_depth };
|
||||
let filter = self.filter.unwrap_or_else(|| Box::new(|_, _| true));
|
||||
let event = self.event.unwrap_or_else(|| Box::new(|_, _, _| {}));
|
||||
let enter = self.enter.unwrap_or_else(|| Box::new(|_, _| {}));
|
||||
let leave = self.leave.unwrap_or_else(|| Box::new(|_, _| {}));
|
||||
let result =
|
||||
visitor.filter_fold_while(initial_value, module, filter, visit, event, enter, leave);
|
||||
|
||||
ModuleGraphVisitResult::with_result(result, visitor)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Default for ModuleGraphVisitorBuilder<'a, T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
max_depth: u32::MAX,
|
||||
filter: None,
|
||||
event: None,
|
||||
enter: None,
|
||||
leave: None,
|
||||
_marker: std::marker::PhantomData {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ModuleGraphVisitResult<T> {
|
||||
pub result: T,
|
||||
pub traversed: HashSet<PathBuf>,
|
||||
pub max_depth: u32,
|
||||
}
|
||||
|
||||
impl<T> ModuleGraphVisitResult<T> {
|
||||
fn with_result(result: T, visitor: ModuleGraphVisitor) -> Self {
|
||||
Self { result, traversed: visitor.traversed, max_depth: visitor.max_depth }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ModuleGraphVisitor {
|
||||
traversed: HashSet<PathBuf>,
|
||||
depth: u32,
|
||||
max_depth: u32,
|
||||
}
|
||||
|
||||
impl ModuleGraphVisitor {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn filter_fold_while<
|
||||
T,
|
||||
Filter: Fn(ModulePair, &ModuleRecord) -> bool,
|
||||
Fold: FnMut(T, ModulePair, &ModuleRecord) -> VisitFoldWhile<T>,
|
||||
EventMod: FnMut(ModuleGraphVisitorEvent, ModulePair, &ModuleRecord),
|
||||
EnterMod: FnMut(ModulePair, &ModuleRecord),
|
||||
LeaveMod: FnMut(ModulePair, &ModuleRecord),
|
||||
>(
|
||||
&mut self,
|
||||
initial_value: T,
|
||||
module_record: &ModuleRecord,
|
||||
filter: Filter,
|
||||
mut fold: Fold,
|
||||
mut event: EventMod,
|
||||
mut enter: EnterMod,
|
||||
mut leave: LeaveMod,
|
||||
) -> T {
|
||||
self.filter_fold_recursive(
|
||||
VisitFoldWhile::Next(initial_value),
|
||||
module_record,
|
||||
&filter,
|
||||
&mut fold,
|
||||
&mut event,
|
||||
&mut enter,
|
||||
&mut leave,
|
||||
)
|
||||
.into_inner()
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn filter_fold_recursive<
|
||||
T,
|
||||
Filter: Fn(ModulePair, &ModuleRecord) -> bool,
|
||||
Fold: FnMut(T, ModulePair, &ModuleRecord) -> VisitFoldWhile<T>,
|
||||
EventMod: FnMut(ModuleGraphVisitorEvent, ModulePair, &ModuleRecord),
|
||||
EnterMod: FnMut(ModulePair, &ModuleRecord),
|
||||
LeaveMod: FnMut(ModulePair, &ModuleRecord),
|
||||
>(
|
||||
&mut self,
|
||||
mut accumulator: VisitFoldWhile<T>,
|
||||
module_record: &ModuleRecord,
|
||||
filter: &Filter,
|
||||
fold: &mut Fold,
|
||||
event: &mut EventMod,
|
||||
enter: &mut EnterMod,
|
||||
leave: &mut LeaveMod,
|
||||
) -> VisitFoldWhile<T> {
|
||||
macro_rules! accumulate {
|
||||
($acc:expr) => {
|
||||
accumulator = $acc;
|
||||
|
||||
if accumulator.is_done() {
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
for module_record_ref in &module_record.loaded_modules {
|
||||
if self.depth > self.max_depth {
|
||||
return VisitFoldWhile::Stop(accumulator.into_inner());
|
||||
}
|
||||
|
||||
let path = &module_record_ref.resolved_absolute_path;
|
||||
if !self.traversed.insert(path.clone()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if !filter(module_record_ref.pair(), module_record) {
|
||||
continue;
|
||||
}
|
||||
|
||||
self.depth += 1;
|
||||
|
||||
event(ModuleGraphVisitorEvent::Enter, module_record_ref.pair(), module_record);
|
||||
enter(module_record_ref.pair(), module_record);
|
||||
|
||||
accumulate!(fold(accumulator.into_inner(), module_record_ref.pair(), module_record));
|
||||
accumulate!(self.filter_fold_recursive(
|
||||
accumulator,
|
||||
module_record_ref.value(),
|
||||
filter,
|
||||
fold,
|
||||
event,
|
||||
enter,
|
||||
leave
|
||||
));
|
||||
|
||||
event(ModuleGraphVisitorEvent::Leave, module_record_ref.pair(), module_record);
|
||||
leave(module_record_ref.pair(), module_record);
|
||||
|
||||
self.depth -= 1;
|
||||
}
|
||||
|
||||
accumulator
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue