Rework the walk module a bit (#4707)
* start a rework of the walk module I'd like to have the code explicitly recurse in the visitor rather than implicitly. This allows the calling code to build up an idea of the depth of each node, or skip parts of the AST entirely. i'm going to rebuild this enough to get the old API to work before looking to merge this. I've also discovered a number of new AST types that were just papered over and/or excluded entirely -- I'm going to have to go through and make sure that both Digest and the walker are still current, or if we silently added types or, more likely, fields getting ignored.
This commit is contained in:
@ -441,8 +441,11 @@ impl Backend {
|
||||
let token_modifiers_bitset = if let Some(ast) = self.ast_map.get(params.uri.as_str()) {
|
||||
let token_index = Arc::new(Mutex::new(token_type_index));
|
||||
let modifier_index: Arc<Mutex<u32>> = Arc::new(Mutex::new(0));
|
||||
crate::walk::walk(&ast, &|node: crate::walk::Node| {
|
||||
let node_range: SourceRange = (&node).into();
|
||||
crate::walk::walk(&ast, |node: crate::walk::Node| {
|
||||
let Ok(node_range): Result<SourceRange, _> = (&node).try_into() else {
|
||||
return Ok(true);
|
||||
};
|
||||
|
||||
if !node_range.contains(source_range.start()) {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
@ -184,7 +184,7 @@ impl Node<Program> {
|
||||
/// Walk the ast and get all the variables and tags as completion items.
|
||||
pub fn completion_items<'a>(&'a self) -> Result<Vec<CompletionItem>> {
|
||||
let completions = Arc::new(Mutex::new(vec![]));
|
||||
crate::walk::walk(self, &|node: crate::walk::Node<'a>| {
|
||||
crate::walk::walk(self, |node: crate::walk::Node<'a>| {
|
||||
let mut findings = completions.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
|
||||
match node {
|
||||
crate::walk::Node::TagDeclarator(tag) => {
|
||||
@ -195,7 +195,7 @@ impl Node<Program> {
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(true)
|
||||
Ok::<bool, anyhow::Error>(true)
|
||||
})?;
|
||||
let x = completions.lock().unwrap();
|
||||
Ok(x.clone())
|
||||
@ -204,7 +204,7 @@ impl Node<Program> {
|
||||
/// Returns all the lsp symbols in the program.
|
||||
pub fn get_lsp_symbols<'a>(&'a self, code: &str) -> Result<Vec<DocumentSymbol>> {
|
||||
let symbols = Arc::new(Mutex::new(vec![]));
|
||||
crate::walk::walk(self, &|node: crate::walk::Node<'a>| {
|
||||
crate::walk::walk(self, |node: crate::walk::Node<'a>| {
|
||||
let mut findings = symbols.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
|
||||
match node {
|
||||
crate::walk::Node::TagDeclarator(tag) => {
|
||||
@ -215,7 +215,7 @@ impl Node<Program> {
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(true)
|
||||
Ok::<bool, anyhow::Error>(true)
|
||||
})?;
|
||||
let x = symbols.lock().unwrap();
|
||||
Ok(x.clone())
|
||||
@ -227,10 +227,10 @@ impl Node<Program> {
|
||||
RuleT: crate::lint::Rule<'a>,
|
||||
{
|
||||
let v = Arc::new(Mutex::new(vec![]));
|
||||
crate::walk::walk(self, &|node: crate::walk::Node<'a>| {
|
||||
crate::walk::walk(self, |node: crate::walk::Node<'a>| {
|
||||
let mut findings = v.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
|
||||
findings.append(&mut rule.check(node)?);
|
||||
Ok(true)
|
||||
Ok::<bool, anyhow::Error>(true)
|
||||
})?;
|
||||
let x = v.lock().unwrap();
|
||||
Ok(x.clone())
|
||||
|
||||
@ -5,7 +5,7 @@ use crate::{
|
||||
|
||||
/// The "Node" type wraps all the AST elements we're able to find in a KCL
|
||||
/// file. Tokens we walk through will be one of these.
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum Node<'a> {
|
||||
Program(NodeRef<'a, types::Program>),
|
||||
|
||||
@ -31,6 +31,7 @@ pub enum Node<'a> {
|
||||
MemberExpression(NodeRef<'a, types::MemberExpression>),
|
||||
UnaryExpression(NodeRef<'a, types::UnaryExpression>),
|
||||
IfExpression(NodeRef<'a, types::IfExpression>),
|
||||
ElseIf(&'a types::ElseIf),
|
||||
|
||||
Parameter(&'a types::Parameter),
|
||||
|
||||
@ -38,11 +39,22 @@ pub enum Node<'a> {
|
||||
|
||||
MemberObject(&'a types::MemberObject),
|
||||
LiteralIdentifier(&'a types::LiteralIdentifier),
|
||||
|
||||
KclNone(&'a types::KclNone),
|
||||
}
|
||||
|
||||
impl From<&Node<'_>> for SourceRange {
|
||||
fn from(node: &Node) -> Self {
|
||||
match node {
|
||||
/// Returned during source_range conversion.
|
||||
#[derive(Debug)]
|
||||
pub enum AstNodeError {
|
||||
/// Returned if we try and [SourceRange] a [types::KclNone].
|
||||
NoSourceForAKclNone,
|
||||
}
|
||||
|
||||
impl TryFrom<&Node<'_>> for SourceRange {
|
||||
type Error = AstNodeError;
|
||||
|
||||
fn try_from(node: &Node) -> Result<Self, Self::Error> {
|
||||
Ok(match node {
|
||||
Node::Program(n) => SourceRange::from(*n),
|
||||
Node::ImportStatement(n) => SourceRange::from(*n),
|
||||
Node::ExpressionStatement(n) => SourceRange::from(*n),
|
||||
@ -68,6 +80,62 @@ impl From<&Node<'_>> for SourceRange {
|
||||
Node::MemberObject(m) => SourceRange::new(m.start(), m.end(), m.module_id()),
|
||||
Node::IfExpression(n) => SourceRange::from(*n),
|
||||
Node::LiteralIdentifier(l) => SourceRange::new(l.start(), l.end(), l.module_id()),
|
||||
|
||||
// This is broken too
|
||||
Node::ElseIf(n) => SourceRange::new(n.cond.start(), n.cond.end(), n.cond.module_id()),
|
||||
|
||||
// The KclNone type here isn't an actual node, so it has no
|
||||
// start/end information.
|
||||
Node::KclNone(_) => return Err(Self::Error::NoSourceForAKclNone),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tree> From<&'tree types::BodyItem> for Node<'tree> {
|
||||
fn from(node: &'tree types::BodyItem) -> Self {
|
||||
match node {
|
||||
types::BodyItem::ImportStatement(v) => v.as_ref().into(),
|
||||
types::BodyItem::ExpressionStatement(v) => v.into(),
|
||||
types::BodyItem::VariableDeclaration(v) => v.as_ref().into(),
|
||||
types::BodyItem::ReturnStatement(v) => v.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tree> From<&'tree types::Expr> for Node<'tree> {
|
||||
fn from(node: &'tree types::Expr) -> Self {
|
||||
match node {
|
||||
types::Expr::Literal(lit) => lit.as_ref().into(),
|
||||
types::Expr::TagDeclarator(tag) => tag.as_ref().into(),
|
||||
types::Expr::Identifier(id) => id.as_ref().into(),
|
||||
types::Expr::BinaryExpression(be) => be.as_ref().into(),
|
||||
types::Expr::FunctionExpression(fe) => fe.as_ref().into(),
|
||||
types::Expr::CallExpression(ce) => ce.as_ref().into(),
|
||||
types::Expr::CallExpressionKw(ce) => ce.as_ref().into(),
|
||||
types::Expr::PipeExpression(pe) => pe.as_ref().into(),
|
||||
types::Expr::PipeSubstitution(ps) => ps.as_ref().into(),
|
||||
types::Expr::ArrayExpression(ae) => ae.as_ref().into(),
|
||||
types::Expr::ArrayRangeExpression(are) => are.as_ref().into(),
|
||||
types::Expr::ObjectExpression(oe) => oe.as_ref().into(),
|
||||
types::Expr::MemberExpression(me) => me.as_ref().into(),
|
||||
types::Expr::UnaryExpression(ue) => ue.as_ref().into(),
|
||||
types::Expr::IfExpression(e) => e.as_ref().into(),
|
||||
types::Expr::None(n) => n.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tree> From<&'tree types::BinaryPart> for Node<'tree> {
|
||||
fn from(node: &'tree types::BinaryPart) -> Self {
|
||||
match node {
|
||||
types::BinaryPart::Literal(lit) => lit.as_ref().into(),
|
||||
types::BinaryPart::Identifier(id) => id.as_ref().into(),
|
||||
types::BinaryPart::BinaryExpression(be) => be.as_ref().into(),
|
||||
types::BinaryPart::CallExpression(ce) => ce.as_ref().into(),
|
||||
types::BinaryPart::CallExpressionKw(ce) => ce.as_ref().into(),
|
||||
types::BinaryPart::UnaryExpression(ue) => ue.as_ref().into(),
|
||||
types::BinaryPart::MemberExpression(me) => me.as_ref().into(),
|
||||
types::BinaryPart::IfExpression(e) => e.as_ref().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -116,4 +184,6 @@ impl_from!(Node, ObjectProperty);
|
||||
impl_from_ref!(Node, Parameter);
|
||||
impl_from_ref!(Node, MemberObject);
|
||||
impl_from!(Node, IfExpression);
|
||||
impl_from!(Node, ElseIf);
|
||||
impl_from_ref!(Node, LiteralIdentifier);
|
||||
impl_from!(Node, KclNone);
|
||||
|
||||
197
src/wasm-lib/kcl/src/walk/ast_visitor.rs
Normal file
197
src/wasm-lib/kcl/src/walk/ast_visitor.rs
Normal file
@ -0,0 +1,197 @@
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::walk::Node;
|
||||
|
||||
/// Walk-specific trait adding the ability to traverse the KCL AST.
|
||||
///
|
||||
/// This trait is implemented on [Node] to handle the fairly tricky bit of
|
||||
/// recursing into the AST in a single place, as well as helpers for traversing
|
||||
/// the tree. for callers to use.
|
||||
pub trait Visitable<'tree> {
|
||||
/// Return a `Vec<Node>` for all *direct* children of this AST node. This
|
||||
/// should only contain direct descendants.
|
||||
fn children(&self) -> Vec<Node<'tree>>;
|
||||
|
||||
/// Return `self` as a [Node]. Generally speaking, the [Visitable] trait
|
||||
/// is only going to be implemented on [Node], so this is purely used by
|
||||
/// helpers that are generic over a [Visitable] and want to deref back
|
||||
/// into a [Node].
|
||||
fn node(&self) -> Node<'tree>;
|
||||
|
||||
/// Call the provided [Visitor] in order to Visit `self`. This will
|
||||
/// only be called on `self` -- the [Visitor] is responsible for
|
||||
/// recursing into any children, if desired.
|
||||
fn visit<VisitorT>(&self, visitor: VisitorT) -> Result<bool, VisitorT::Error>
|
||||
where
|
||||
VisitorT: Visitor<'tree>,
|
||||
{
|
||||
visitor.visit_node(self.node())
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait used to enable visiting members of KCL AST.
|
||||
///
|
||||
/// Implementing this trait enables the implementer to be invoked over
|
||||
/// members of KCL AST by using the [Visitable::visit] function on
|
||||
/// a [Node].
|
||||
pub trait Visitor<'tree> {
|
||||
/// Error type returned by the [Self::visit] function.
|
||||
type Error;
|
||||
|
||||
/// Visit a KCL AST [Node].
|
||||
///
|
||||
/// In general, implementers likely wish to check to see if a Node is what
|
||||
/// they're looking for, and either descend into that [Node]'s children (by
|
||||
/// calling [Visitable::children] on [Node] to get children nodes,
|
||||
/// calling [Visitable::visit] on each node of interest), or perform
|
||||
/// some action.
|
||||
fn visit_node(&self, node: Node<'tree>) -> Result<bool, Self::Error>;
|
||||
}
|
||||
|
||||
impl<'a, FnT, ErrorT> Visitor<'a> for FnT
|
||||
where
|
||||
FnT: Fn(Node<'a>) -> Result<bool, ErrorT>,
|
||||
{
|
||||
type Error = ErrorT;
|
||||
|
||||
fn visit_node(&self, n: Node<'a>) -> Result<bool, ErrorT> {
|
||||
self(n)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tree> Visitable<'tree> for Node<'tree> {
|
||||
fn node(&self) -> Node<'tree> {
|
||||
*self
|
||||
}
|
||||
|
||||
fn children(&self) -> Vec<Node<'tree>> {
|
||||
match self {
|
||||
Node::Program(n) => n.body.iter().map(|node| node.into()).collect(),
|
||||
Node::ExpressionStatement(n) => {
|
||||
vec![(&n.expression).into()]
|
||||
}
|
||||
Node::BinaryExpression(n) => {
|
||||
vec![(&n.left).into(), (&n.right).into()]
|
||||
}
|
||||
Node::FunctionExpression(n) => {
|
||||
let mut children = n.params.iter().map(|v| v.into()).collect::<Vec<Node>>();
|
||||
children.push((&n.body).into());
|
||||
children
|
||||
}
|
||||
Node::CallExpression(n) => {
|
||||
let mut children = n.arguments.iter().map(|v| v.into()).collect::<Vec<Node>>();
|
||||
children.insert(0, (&n.callee).into());
|
||||
children
|
||||
}
|
||||
Node::CallExpressionKw(n) => {
|
||||
let mut children = n.unlabeled.iter().map(|v| v.into()).collect::<Vec<Node>>();
|
||||
|
||||
// TODO: this is wrong but it's what the old walk code was doing.
|
||||
// We likely need a real LabeledArg AST node, but I don't
|
||||
// want to tango with it since it's a lot deeper than
|
||||
// adding it to the enum.
|
||||
children.extend(n.arguments.iter().map(|v| (&v.arg).into()).collect::<Vec<Node>>());
|
||||
children
|
||||
}
|
||||
Node::PipeExpression(n) => n.body.iter().map(|v| v.into()).collect(),
|
||||
Node::ArrayExpression(n) => n.elements.iter().map(|v| v.into()).collect(),
|
||||
Node::ArrayRangeExpression(n) => {
|
||||
vec![(&n.start_element).into(), (&n.end_element).into()]
|
||||
}
|
||||
Node::ObjectExpression(n) => n.properties.iter().map(|v| v.into()).collect(),
|
||||
Node::MemberExpression(n) => {
|
||||
vec![(&n.object).into(), (&n.property).into()]
|
||||
}
|
||||
Node::IfExpression(n) => {
|
||||
let mut children = n.else_ifs.iter().map(|v| v.into()).collect::<Vec<Node>>();
|
||||
children.insert(0, n.cond.as_ref().into());
|
||||
children.push(n.final_else.as_ref().into());
|
||||
children
|
||||
}
|
||||
Node::VariableDeclaration(n) => vec![(&n.declaration).into()],
|
||||
Node::ReturnStatement(n) => {
|
||||
vec![(&n.argument).into()]
|
||||
}
|
||||
Node::VariableDeclarator(n) => {
|
||||
vec![(&n.id).into(), (&n.init).into()]
|
||||
}
|
||||
Node::UnaryExpression(n) => {
|
||||
vec![(&n.argument).into()]
|
||||
}
|
||||
Node::Parameter(n) => {
|
||||
vec![(&n.identifier).into()]
|
||||
}
|
||||
Node::ObjectProperty(n) => {
|
||||
vec![(&n.value).into()]
|
||||
}
|
||||
Node::ElseIf(n) => {
|
||||
vec![(&n.cond).into(), n.then_val.as_ref().into()]
|
||||
}
|
||||
Node::PipeSubstitution(_)
|
||||
| Node::TagDeclarator(_)
|
||||
| Node::Identifier(_)
|
||||
| Node::ImportStatement(_)
|
||||
| Node::MemberObject(_)
|
||||
| Node::LiteralIdentifier(_)
|
||||
| Node::KclNone(_)
|
||||
| Node::Literal(_) => vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::sync::Mutex;
|
||||
|
||||
macro_rules! kcl {
|
||||
( $kcl:expr ) => {{
|
||||
$crate::parsing::top_level_parse($kcl).unwrap()
|
||||
}};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn count_crows() {
|
||||
let program = kcl!(
|
||||
"\
|
||||
const crow1 = 1
|
||||
const crow2 = 2
|
||||
|
||||
fn crow3() {
|
||||
const crow4 = 3
|
||||
crow5()
|
||||
}
|
||||
"
|
||||
);
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct CountCrows {
|
||||
n: Box<Mutex<usize>>,
|
||||
}
|
||||
|
||||
impl<'tree> Visitor<'tree> for &CountCrows {
|
||||
type Error = ();
|
||||
|
||||
fn visit_node(&self, node: Node<'tree>) -> Result<bool, Self::Error> {
|
||||
if let Node::VariableDeclarator(vd) = node {
|
||||
if vd.id.name.starts_with("crow") {
|
||||
*self.n.lock().unwrap() += 1;
|
||||
}
|
||||
}
|
||||
|
||||
for child in node.children().iter() {
|
||||
if !child.visit(*self)? {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
let prog: Node = (&program).into();
|
||||
let count_crows: CountCrows = Default::default();
|
||||
Visitable::visit(&prog, &count_crows).unwrap();
|
||||
assert_eq!(*count_crows.n.lock().unwrap(), 4);
|
||||
}
|
||||
}
|
||||
@ -1,329 +1,55 @@
|
||||
use anyhow::Result;
|
||||
|
||||
use super::ast_visitor::{Visitable, Visitor};
|
||||
use crate::{
|
||||
parsing::ast::types::{
|
||||
BinaryPart, BodyItem, Expr, IfExpression, LiteralIdentifier, MemberExpression, MemberObject, NodeRef,
|
||||
ObjectExpression, ObjectProperty, Parameter, Program, UnaryExpression, VariableDeclarator,
|
||||
},
|
||||
parsing::ast::types::{NodeRef, Program},
|
||||
walk::Node,
|
||||
};
|
||||
|
||||
/// Walker is implemented by things that are able to walk an AST tree to
|
||||
/// produce lints. This trait is implemented automatically for a few of the
|
||||
/// common types, but can be manually implemented too.
|
||||
/// *DEPRECATED* Walk trait.
|
||||
///
|
||||
/// This was written before [Visitor], which is the better way to traverse
|
||||
/// a AST.
|
||||
///
|
||||
/// This trait continues to exist in order to not change all the linter
|
||||
/// as we refine the walk code.
|
||||
///
|
||||
/// This, internally, uses the new [Visitor] trait, and is only provided as
|
||||
/// a stub until we migrate all existing code off this trait.
|
||||
pub trait Walker<'a> {
|
||||
/// Walk will visit every element of the AST.
|
||||
/// Walk will visit every element of the AST, recursing through the
|
||||
/// whole tree.
|
||||
fn walk(&self, n: Node<'a>) -> Result<bool>;
|
||||
}
|
||||
|
||||
impl<'a, FnT> Walker<'a> for FnT
|
||||
impl<'tree, VisitorT> Walker<'tree> for VisitorT
|
||||
where
|
||||
FnT: Fn(Node<'a>) -> Result<bool>,
|
||||
VisitorT: Visitor<'tree>,
|
||||
VisitorT: Clone,
|
||||
anyhow::Error: From<VisitorT::Error>,
|
||||
VisitorT::Error: Send,
|
||||
VisitorT::Error: Sync,
|
||||
{
|
||||
fn walk(&self, n: Node<'a>) -> Result<bool> {
|
||||
self(n)
|
||||
fn walk(&self, n: Node<'tree>) -> Result<bool> {
|
||||
if !n.visit(self.clone())? {
|
||||
return Ok(false);
|
||||
}
|
||||
for child in n.children() {
|
||||
if !Self::walk(self, child)? {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
/// Run the Walker against all [Node]s in a [Program].
|
||||
pub fn walk<'a, WalkT>(prog: NodeRef<'a, Program>, f: &WalkT) -> Result<bool>
|
||||
pub fn walk<'a, WalkT>(prog: NodeRef<'a, Program>, f: WalkT) -> Result<bool>
|
||||
where
|
||||
WalkT: Walker<'a>,
|
||||
{
|
||||
if !f.walk(prog.into())? {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
for bi in &prog.body {
|
||||
if !walk_body_item(bi, f)? {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn walk_variable_declarator<'a, WalkT>(node: NodeRef<'a, VariableDeclarator>, f: &WalkT) -> Result<bool>
|
||||
where
|
||||
WalkT: Walker<'a>,
|
||||
{
|
||||
if !f.walk(node.into())? {
|
||||
return Ok(false);
|
||||
}
|
||||
if !f.walk((&node.id).into())? {
|
||||
return Ok(false);
|
||||
}
|
||||
walk_value(&node.init, f)
|
||||
}
|
||||
|
||||
fn walk_parameter<'a, WalkT>(node: &'a Parameter, f: &WalkT) -> Result<bool>
|
||||
where
|
||||
WalkT: Walker<'a>,
|
||||
{
|
||||
if !f.walk(node.into())? {
|
||||
return Ok(false);
|
||||
}
|
||||
f.walk((&node.identifier).into())
|
||||
}
|
||||
|
||||
fn walk_member_object<'a, WalkT>(node: &'a MemberObject, f: &WalkT) -> Result<bool>
|
||||
where
|
||||
WalkT: Walker<'a>,
|
||||
{
|
||||
f.walk(node.into())
|
||||
}
|
||||
|
||||
fn walk_literal_identifier<'a, WalkT>(node: &'a LiteralIdentifier, f: &WalkT) -> Result<bool>
|
||||
where
|
||||
WalkT: Walker<'a>,
|
||||
{
|
||||
f.walk(node.into())
|
||||
}
|
||||
|
||||
fn walk_member_expression<'a, WalkT>(node: NodeRef<'a, MemberExpression>, f: &WalkT) -> Result<bool>
|
||||
where
|
||||
WalkT: Walker<'a>,
|
||||
{
|
||||
if !f.walk(node.into())? {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
if !walk_member_object(&node.object, f)? {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
walk_literal_identifier(&node.property, f)
|
||||
}
|
||||
|
||||
fn walk_binary_part<'a, WalkT>(node: &'a BinaryPart, f: &WalkT) -> Result<bool>
|
||||
where
|
||||
WalkT: Walker<'a>,
|
||||
{
|
||||
match node {
|
||||
BinaryPart::Literal(lit) => f.walk(lit.as_ref().into()),
|
||||
BinaryPart::Identifier(id) => f.walk(id.as_ref().into()),
|
||||
BinaryPart::BinaryExpression(be) => f.walk(be.as_ref().into()),
|
||||
BinaryPart::CallExpression(ce) => f.walk(ce.as_ref().into()),
|
||||
BinaryPart::CallExpressionKw(ce) => f.walk(ce.as_ref().into()),
|
||||
BinaryPart::UnaryExpression(ue) => walk_unary_expression(ue, f),
|
||||
BinaryPart::MemberExpression(me) => walk_member_expression(me, f),
|
||||
BinaryPart::IfExpression(e) => walk_if_expression(e, f),
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Rename this to walk_expr
|
||||
fn walk_value<'a, WalkT>(node: &'a Expr, f: &WalkT) -> Result<bool>
|
||||
where
|
||||
WalkT: Walker<'a>,
|
||||
{
|
||||
match node {
|
||||
Expr::Literal(lit) => f.walk(lit.as_ref().into()),
|
||||
Expr::TagDeclarator(tag) => f.walk(tag.as_ref().into()),
|
||||
|
||||
Expr::Identifier(id) => {
|
||||
// sometimes there's a bare Identifier without a Value::Identifier.
|
||||
f.walk(id.as_ref().into())
|
||||
}
|
||||
|
||||
Expr::BinaryExpression(be) => {
|
||||
if !f.walk(be.as_ref().into())? {
|
||||
return Ok(false);
|
||||
}
|
||||
if !walk_binary_part(&be.left, f)? {
|
||||
return Ok(false);
|
||||
}
|
||||
walk_binary_part(&be.right, f)
|
||||
}
|
||||
Expr::FunctionExpression(fe) => {
|
||||
if !f.walk(fe.as_ref().into())? {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
for arg in &fe.params {
|
||||
if !walk_parameter(arg, f)? {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
walk(&fe.body, f)
|
||||
}
|
||||
Expr::CallExpression(ce) => {
|
||||
if !f.walk(ce.as_ref().into())? {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
if !f.walk((&ce.callee).into())? {
|
||||
return Ok(false);
|
||||
}
|
||||
for e in &ce.arguments {
|
||||
if !walk_value::<WalkT>(e, f)? {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
Expr::CallExpressionKw(ce) => {
|
||||
if !f.walk(ce.as_ref().into())? {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
if !f.walk((&ce.callee).into())? {
|
||||
return Ok(false);
|
||||
}
|
||||
if let Some(ref e) = ce.unlabeled {
|
||||
if !walk_value::<WalkT>(e, f)? {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
for e in &ce.arguments {
|
||||
if !walk_value::<WalkT>(&e.arg, f)? {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
Expr::PipeExpression(pe) => {
|
||||
if !f.walk(pe.as_ref().into())? {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
for e in &pe.body {
|
||||
if !walk_value::<WalkT>(e, f)? {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
Expr::PipeSubstitution(ps) => f.walk(ps.as_ref().into()),
|
||||
Expr::ArrayExpression(ae) => {
|
||||
if !f.walk(ae.as_ref().into())? {
|
||||
return Ok(false);
|
||||
}
|
||||
for e in &ae.elements {
|
||||
if !walk_value::<WalkT>(e, f)? {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
Expr::ArrayRangeExpression(are) => {
|
||||
if !f.walk(are.as_ref().into())? {
|
||||
return Ok(false);
|
||||
}
|
||||
if !walk_value::<WalkT>(&are.start_element, f)? {
|
||||
return Ok(false);
|
||||
}
|
||||
if !walk_value::<WalkT>(&are.end_element, f)? {
|
||||
return Ok(false);
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
Expr::ObjectExpression(oe) => walk_object_expression(oe, f),
|
||||
Expr::MemberExpression(me) => walk_member_expression(me, f),
|
||||
Expr::UnaryExpression(ue) => walk_unary_expression(ue, f),
|
||||
Expr::IfExpression(e) => walk_if_expression(e, f),
|
||||
Expr::None(_) => Ok(true),
|
||||
}
|
||||
}
|
||||
|
||||
/// Walk through an [ObjectProperty].
|
||||
fn walk_object_property<'a, WalkT>(node: NodeRef<'a, ObjectProperty>, f: &WalkT) -> Result<bool>
|
||||
where
|
||||
WalkT: Walker<'a>,
|
||||
{
|
||||
if !f.walk(node.into())? {
|
||||
return Ok(false);
|
||||
}
|
||||
walk_value(&node.value, f)
|
||||
}
|
||||
|
||||
/// Walk through an [ObjectExpression].
|
||||
fn walk_object_expression<'a, WalkT>(node: NodeRef<'a, ObjectExpression>, f: &WalkT) -> Result<bool>
|
||||
where
|
||||
WalkT: Walker<'a>,
|
||||
{
|
||||
if !f.walk(node.into())? {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
for prop in &node.properties {
|
||||
if !walk_object_property(prop, f)? {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Walk through an [IfExpression].
|
||||
fn walk_if_expression<'a, WalkT>(node: NodeRef<'a, IfExpression>, f: &WalkT) -> Result<bool>
|
||||
where
|
||||
WalkT: Walker<'a>,
|
||||
{
|
||||
if !f.walk(node.into())? {
|
||||
return Ok(false);
|
||||
}
|
||||
if !walk_value(&node.cond, f)? {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
for else_if in &node.else_ifs {
|
||||
if !walk_value(&else_if.cond, f)? {
|
||||
return Ok(false);
|
||||
}
|
||||
if !walk(&else_if.then_val, f)? {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
let final_else = &(*node.final_else);
|
||||
if !f.walk(final_else.into())? {
|
||||
return Ok(false);
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// walk through an [UnaryExpression].
|
||||
fn walk_unary_expression<'a, WalkT>(node: NodeRef<'a, UnaryExpression>, f: &WalkT) -> Result<bool>
|
||||
where
|
||||
WalkT: Walker<'a>,
|
||||
{
|
||||
if !f.walk(node.into())? {
|
||||
return Ok(false);
|
||||
}
|
||||
walk_binary_part(&node.argument, f)
|
||||
}
|
||||
|
||||
/// walk through a [BodyItem].
|
||||
fn walk_body_item<'a, WalkT>(node: &'a BodyItem, f: &WalkT) -> Result<bool>
|
||||
where
|
||||
WalkT: Walker<'a>,
|
||||
{
|
||||
// We don't walk a BodyItem since it's an enum itself.
|
||||
|
||||
match node {
|
||||
BodyItem::ImportStatement(xs) => {
|
||||
if !f.walk(xs.as_ref().into())? {
|
||||
return Ok(false);
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
BodyItem::ExpressionStatement(xs) => {
|
||||
if !f.walk(xs.into())? {
|
||||
return Ok(false);
|
||||
}
|
||||
walk_value(&xs.expression, f)
|
||||
}
|
||||
BodyItem::VariableDeclaration(vd) => {
|
||||
if !f.walk(vd.as_ref().into())? {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
walk_variable_declarator(&vd.declaration, f)
|
||||
}
|
||||
BodyItem::ReturnStatement(rs) => {
|
||||
if !f.walk(rs.into())? {
|
||||
return Ok(false);
|
||||
}
|
||||
walk_value(&rs.argument, f)
|
||||
}
|
||||
}
|
||||
let prog: Node = prog.into();
|
||||
f.walk(prog)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -345,10 +71,10 @@ const bar = 2
|
||||
"
|
||||
);
|
||||
|
||||
walk(&program, &|node| {
|
||||
walk(&program, |node| {
|
||||
if let Node::VariableDeclarator(vd) = node {
|
||||
if vd.id.name == "foo" {
|
||||
return Ok(false);
|
||||
return Ok::<bool, anyhow::Error>(false);
|
||||
}
|
||||
panic!("walk didn't stop");
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
mod ast_node;
|
||||
mod ast_visitor;
|
||||
mod ast_walk;
|
||||
|
||||
pub use ast_node::Node;
|
||||
|
||||
Reference in New Issue
Block a user