use oxc::ast::AstKind;
use oxc::ast::ast::{Expression, Program};
use oxc::ast_visit::Visit;
use oxc::cfg::graph::adj::NodeIndex;
use oxc::cfg::graph::visit::{Control, DfsEvent, EdgeRef};
use oxc::cfg::visit::set_depth_first_search;
use oxc::cfg::{EdgeType, InstructionKind};
use oxc::semantic::Semantic;
use oxc::span::SourceType;
use rolldown_ecmascript::{EcmaAst, EcmaCompiler};
#[cfg(test)]
mod test;
pub fn filterable(source: &str) -> bool {
let ast = EcmaCompiler::parse("<Noop>", source, SourceType::mjs()).unwrap();
let semantic = EcmaAst::make_semantic(ast.program(), true);
let mut analyzer = FilterableAnalyzer::new(&semantic);
analyzer.visit_program(ast.program());
analyzer.ret
}
struct FilterableAnalyzer<'a> {
ast_ext: &'a Semantic<'a>,
ret: bool,
}
impl<'a> FilterableAnalyzer<'a> {
pub fn new(ast_ext: &'a Semantic<'a>) -> Self {
Self { ast_ext, ret: false }
}
}
impl<'a> Visit<'a> for FilterableAnalyzer<'a> {
fn visit_program(&mut self, _it: &Program<'a>) {
let Some(cfg) = self.ast_ext.cfg() else {
return;
};
let g = cfg.graph();
let mut function_index = None;
let mut caller_index = NodeIndex::from(0);
let ret = set_depth_first_search(g, Some(NodeIndex::from(1)), |e| {
match e {
DfsEvent::Discover(_, _) => Control::<bool>::Continue,
DfsEvent::TreeEdge(s, e) => {
if function_index.is_some() {
for b in cfg.basic_block(e).instructions() {
if matches!(b.kind, InstructionKind::Unreachable) {
return Control::Prune;
}
if matches!(b.kind, InstructionKind::ImplicitReturn) {
return Control::Break(true);
}
let node = b.node_id.map(|id| self.ast_ext.nodes().get_node(id).kind());
match node {
Some(AstKind::ReturnStatement(stmt)) => match stmt.argument {
Some(Expression::Identifier(ref id)) if id.name == "undefined" => {
return Control::Break(true);
}
None => {
return Control::Break(true);
}
_ => {
return Control::Prune;
}
},
Some(AstKind::IfStatement(_) | AstKind::BlockStatement(_)) => {
}
Some(_) => {
if matches!(b.kind, InstructionKind::Condition) {
continue;
}
return Control::Prune;
}
None => {
return Control::Continue;
}
}
}
} else {
for e in g.edges_connecting(s, e) {
if matches!(g.edge_weight(e.id()), Some(EdgeType::NewFunction)) {
function_index = Some(e.target());
caller_index = s;
}
}
}
Control::Continue
}
DfsEvent::BackEdge(..) | DfsEvent::CrossForwardEdge(..) | DfsEvent::Finish(..) => {
Control::Continue
}
}
});
self.ret = ret.break_value().unwrap_or_default();
}
}