use crate::ast::{Expr, Prop, Stmt, StmtKind, SwitchCase};
pub fn stmt_captures(s: &Stmt) -> bool {
match &s.kind {
StmtKind::FuncDecl { .. } | StmtKind::ClassDecl(_) => true,
StmtKind::Expr(e) | StmtKind::Throw(e) => expr_captures(e),
StmtKind::Return(e) => e.as_ref().is_some_and(expr_captures),
StmtKind::Decl { decls, .. } => decls
.iter()
.any(|d| expr_captures(&d.target) || d.init.as_ref().is_some_and(expr_captures)),
StmtKind::Block(body) => body.iter().any(stmt_captures),
StmtKind::If { test, cons, alt } => {
expr_captures(test) || stmt_captures(cons) || alt.as_deref().is_some_and(stmt_captures)
}
StmtKind::While { test, body } | StmtKind::DoWhile { body, test } => {
expr_captures(test) || stmt_captures(body)
}
StmtKind::For {
init,
test,
update,
body,
} => {
init.as_deref().is_some_and(stmt_captures)
|| test.as_ref().is_some_and(expr_captures)
|| update.as_ref().is_some_and(expr_captures)
|| stmt_captures(body)
}
StmtKind::ForOf {
target, iter, body, ..
} => expr_captures(target) || expr_captures(iter) || stmt_captures(body),
StmtKind::ForIn {
target,
object,
body,
..
} => expr_captures(target) || expr_captures(object) || stmt_captures(body),
StmtKind::Switch { disc, cases } => expr_captures(disc) || cases.iter().any(case_captures),
StmtKind::Labeled { body, .. } => stmt_captures(body),
StmtKind::Try {
block,
handler,
finalizer,
} => {
block.iter().any(stmt_captures)
|| handler.as_ref().is_some_and(|(param, body)| {
param.as_ref().is_some_and(expr_captures) || body.iter().any(stmt_captures)
})
|| finalizer
.as_ref()
.is_some_and(|body| body.iter().any(stmt_captures))
}
StmtKind::Break(_) | StmtKind::Continue(_) | StmtKind::Empty => false,
}
}
fn case_captures(c: &SwitchCase) -> bool {
c.test.as_ref().is_some_and(expr_captures) || c.body.iter().any(stmt_captures)
}
pub fn expr_captures(e: &Expr) -> bool {
match e {
Expr::Function { .. } | Expr::Class(_) => true,
Expr::Ident(n) => n == "eval",
Expr::Null
| Expr::Undefined
| Expr::Hole
| Expr::True
| Expr::False
| Expr::Number(_)
| Expr::BigInt(_)
| Expr::Regex(_, _)
| Expr::Str(_)
| Expr::This
| Expr::Super
| Expr::NewTarget => false,
Expr::Template { exprs, .. } => exprs.iter().any(expr_captures),
Expr::TaggedTemplate { tag, exprs, .. } => {
expr_captures(tag) || exprs.iter().any(expr_captures)
}
Expr::Yield { arg, .. } => arg.as_deref().is_some_and(expr_captures),
Expr::Await(inner) | Expr::Spread(inner) | Expr::Unary(_, inner) => expr_captures(inner),
Expr::Array(items) | Expr::Sequence(items) => items.iter().any(expr_captures),
Expr::Object(props) => props.iter().any(prop_captures),
Expr::Logical(_, l, r) | Expr::Binary(_, l, r) => expr_captures(l) || expr_captures(r),
Expr::Conditional { test, cons, alt } => {
expr_captures(test) || expr_captures(cons) || expr_captures(alt)
}
Expr::Assign { target, value, .. } => expr_captures(target) || expr_captures(value),
Expr::Update { target, .. } => expr_captures(target),
Expr::Call { func, args, .. } | Expr::New { callee: func, args } => {
expr_captures(func) || args.iter().any(expr_captures)
}
Expr::Member { object, .. } => expr_captures(object),
Expr::Index { object, index, .. } => expr_captures(object) || expr_captures(index),
}
}
fn prop_captures(p: &Prop) -> bool {
match p {
Prop::KeyValue { key, value, .. } => expr_captures(key) || expr_captures(value),
Prop::Spread(e) => expr_captures(e),
Prop::Accessor { key, func, .. } => expr_captures(key) || expr_captures(func),
}
}
pub fn block_needs_scope(body: &[Stmt]) -> bool {
body.iter().any(|s| match &s.kind {
StmtKind::Decl { kind, .. } => !matches!(kind, crate::ast::DeclKind::Var),
StmtKind::FuncDecl { .. } | StmtKind::ClassDecl(_) => true,
StmtKind::Expr(e) => mentions_eval(e),
_ => false,
})
}
fn mentions_eval(e: &Expr) -> bool {
match e {
Expr::Ident(n) => n == "eval",
Expr::Call { func, args, .. } | Expr::New { callee: func, args } => {
mentions_eval(func) || args.iter().any(mentions_eval)
}
Expr::Assign { target, value, .. } => mentions_eval(target) || mentions_eval(value),
Expr::Sequence(items) => items.iter().any(mentions_eval),
Expr::Logical(_, l, r) | Expr::Binary(_, l, r) => mentions_eval(l) || mentions_eval(r),
Expr::Conditional { test, cons, alt } => {
mentions_eval(test) || mentions_eval(cons) || mentions_eval(alt)
}
Expr::Unary(_, inner) | Expr::Await(inner) | Expr::Spread(inner) => mentions_eval(inner),
_ => false,
}
}