Skip to main content

react_compiler_ast/
visitor.rs

1//! AST visitor with automatic scope tracking.
2//!
3//! Provides a [`Visitor`] trait with enter/leave hooks for specific node types,
4//! and an [`AstWalker`] that traverses the AST while tracking the active scope
5//! via the scope tree's `node_to_scope` map.
6
7use crate::Program;
8use crate::declarations::*;
9use crate::expressions::*;
10use crate::jsx::*;
11use crate::patterns::*;
12use crate::scope::ScopeId;
13use crate::scope::ScopeInfo;
14use crate::statements::*;
15
16/// Trait for visiting Babel AST nodes. All methods default to no-ops.
17/// Override specific methods to intercept nodes of interest.
18///
19/// The `'ast` lifetime ties visitor hooks to the AST being walked, allowing
20/// visitors to store references into the AST (e.g., for deferred processing).
21///
22/// The `scope_stack` parameter provides the current scope context during traversal.
23/// The active scope is `scope_stack.last()`.
24pub trait Visitor<'ast> {
25    /// Controls whether the walker recurses into function/arrow/method bodies.
26    /// Returns `true` by default. Override to `false` to skip function bodies
27    /// (similar to Babel's `path.skip()` in traverse visitors).
28    ///
29    /// When `false`, the walker still calls `enter_*` / `leave_*` for functions
30    /// but does not walk their params or body.
31    fn traverse_function_bodies(&self) -> bool {
32        true
33    }
34
35    fn enter_function_declaration(
36        &mut self,
37        _node: &'ast FunctionDeclaration,
38        _scope_stack: &[ScopeId],
39    ) {
40    }
41    fn leave_function_declaration(
42        &mut self,
43        _node: &'ast FunctionDeclaration,
44        _scope_stack: &[ScopeId],
45    ) {
46    }
47    fn enter_function_expression(
48        &mut self,
49        _node: &'ast FunctionExpression,
50        _scope_stack: &[ScopeId],
51    ) {
52    }
53    fn leave_function_expression(
54        &mut self,
55        _node: &'ast FunctionExpression,
56        _scope_stack: &[ScopeId],
57    ) {
58    }
59    fn enter_arrow_function_expression(
60        &mut self,
61        _node: &'ast ArrowFunctionExpression,
62        _scope_stack: &[ScopeId],
63    ) {
64    }
65    fn leave_arrow_function_expression(
66        &mut self,
67        _node: &'ast ArrowFunctionExpression,
68        _scope_stack: &[ScopeId],
69    ) {
70    }
71    fn enter_class_declaration(
72        &mut self,
73        _node: &'ast crate::statements::ClassDeclaration,
74        _scope_stack: &[ScopeId],
75    ) {
76    }
77    fn enter_class_expression(&mut self, _node: &'ast ClassExpression, _scope_stack: &[ScopeId]) {}
78    fn enter_object_method(&mut self, _node: &'ast ObjectMethod, _scope_stack: &[ScopeId]) {}
79    fn leave_object_method(&mut self, _node: &'ast ObjectMethod, _scope_stack: &[ScopeId]) {}
80    fn enter_assignment_expression(
81        &mut self,
82        _node: &'ast AssignmentExpression,
83        _scope_stack: &[ScopeId],
84    ) {
85    }
86    fn enter_update_expression(&mut self, _node: &'ast UpdateExpression, _scope_stack: &[ScopeId]) {
87    }
88    fn enter_identifier(&mut self, _node: &'ast Identifier, _scope_stack: &[ScopeId]) {}
89    fn enter_jsx_identifier(&mut self, _node: &'ast JSXIdentifier, _scope_stack: &[ScopeId]) {}
90    fn enter_jsx_opening_element(
91        &mut self,
92        _node: &'ast JSXOpeningElement,
93        _scope_stack: &[ScopeId],
94    ) {
95    }
96    fn leave_jsx_opening_element(
97        &mut self,
98        _node: &'ast JSXOpeningElement,
99        _scope_stack: &[ScopeId],
100    ) {
101    }
102
103    fn enter_variable_declarator(
104        &mut self,
105        _node: &'ast VariableDeclarator,
106        _scope_stack: &[ScopeId],
107    ) {
108    }
109    fn leave_variable_declarator(
110        &mut self,
111        _node: &'ast VariableDeclarator,
112        _scope_stack: &[ScopeId],
113    ) {
114    }
115
116    fn enter_call_expression(&mut self, _node: &'ast CallExpression, _scope_stack: &[ScopeId]) {}
117    fn leave_call_expression(&mut self, _node: &'ast CallExpression, _scope_stack: &[ScopeId]) {}
118
119    /// Called when the walker enters a loop expression context (while.test,
120    /// do-while.test, for-in.right, for-of.right). Functions found in these
121    /// positions are treated as non-program-scope by Babel, even though the
122    /// walker doesn't push a scope for them.
123    fn enter_loop_expression(&mut self) {}
124    fn leave_loop_expression(&mut self) {}
125}
126
127/// Walks the AST while tracking scope context via `node_to_scope`.
128pub struct AstWalker<'a> {
129    scope_info: &'a ScopeInfo,
130    scope_stack: Vec<ScopeId>,
131    /// Depth counter for loop/iteration expression positions (while.test,
132    /// do-while.test, for-in.right, for-of.right). These positions are
133    /// NOT inside a scope in the walker's model, but Babel's scope analysis
134    /// treats them as non-program-scope. Visitors can check this via
135    /// `in_loop_expression_depth()` to implement Babel-compatible scope checks.
136    loop_expression_depth: usize,
137}
138
139impl<'a> AstWalker<'a> {
140    pub fn new(scope_info: &'a ScopeInfo) -> Self {
141        AstWalker {
142            scope_info,
143            scope_stack: Vec::new(),
144            loop_expression_depth: 0,
145        }
146    }
147
148    /// Create a walker with an initial scope already on the stack.
149    pub fn with_initial_scope(scope_info: &'a ScopeInfo, initial_scope: ScopeId) -> Self {
150        AstWalker {
151            scope_info,
152            scope_stack: vec![initial_scope],
153            loop_expression_depth: 0,
154        }
155    }
156
157    pub fn scope_stack(&self) -> &[ScopeId] {
158        &self.scope_stack
159    }
160
161    /// Returns the current loop-expression depth. Non-zero when the walker is
162    /// inside a loop's test/right expression (while.test, do-while.test,
163    /// for-in.right, for-of.right). Visitors can use this to implement
164    /// Babel-compatible scope checks in 'all' compilation mode.
165    pub fn loop_expression_depth(&self) -> usize {
166        self.loop_expression_depth
167    }
168
169    /// Try to push a scope for a node. Returns true if a scope was pushed.
170    fn try_push_scope(&mut self, _start: Option<u32>, node_id: Option<u32>) -> bool {
171        let scope = self.scope_info.resolve_scope_for_node(node_id);
172        if let Some(scope_id) = scope {
173            self.scope_stack.push(scope_id);
174            return true;
175        }
176        false
177    }
178
179    // ---- Public walk methods ----
180
181    pub fn walk_program<'ast>(&mut self, v: &mut impl Visitor<'ast>, node: &'ast Program) {
182        let pushed = self.try_push_scope(node.base.start, node.base.node_id);
183        for stmt in &node.body {
184            self.walk_statement(v, stmt);
185        }
186        if pushed {
187            self.scope_stack.pop();
188        }
189    }
190
191    pub fn walk_block_statement<'ast>(
192        &mut self,
193        v: &mut impl Visitor<'ast>,
194        node: &'ast BlockStatement,
195    ) {
196        let pushed = self.try_push_scope(node.base.start, node.base.node_id);
197        for stmt in &node.body {
198            self.walk_statement(v, stmt);
199        }
200        if pushed {
201            self.scope_stack.pop();
202        }
203    }
204
205    pub fn walk_statement<'ast>(&mut self, v: &mut impl Visitor<'ast>, stmt: &'ast Statement) {
206        match stmt {
207            Statement::BlockStatement(node) => self.walk_block_statement(v, node),
208            Statement::ReturnStatement(node) => {
209                if let Some(arg) = &node.argument {
210                    self.walk_expression(v, arg);
211                }
212            }
213            Statement::ExpressionStatement(node) => {
214                self.walk_expression(v, &node.expression);
215            }
216            Statement::IfStatement(node) => {
217                self.walk_expression(v, &node.test);
218                self.walk_statement(v, &node.consequent);
219                if let Some(alt) = &node.alternate {
220                    self.walk_statement(v, alt);
221                }
222            }
223            Statement::ForStatement(node) => {
224                let pushed = self.try_push_scope(node.base.start, node.base.node_id);
225                if let Some(init) = &node.init {
226                    match init.as_ref() {
227                        ForInit::VariableDeclaration(decl) => {
228                            self.walk_variable_declaration(v, decl)
229                        }
230                        ForInit::Expression(expr) => self.walk_expression(v, expr),
231                    }
232                }
233                if let Some(test) = &node.test {
234                    self.walk_expression(v, test);
235                }
236                if let Some(update) = &node.update {
237                    self.walk_expression(v, update);
238                }
239                self.walk_statement(v, &node.body);
240                if pushed {
241                    self.scope_stack.pop();
242                }
243            }
244            Statement::WhileStatement(node) => {
245                self.loop_expression_depth += 1;
246                v.enter_loop_expression();
247                self.walk_expression(v, &node.test);
248                v.leave_loop_expression();
249                self.loop_expression_depth -= 1;
250                self.walk_statement(v, &node.body);
251            }
252            Statement::DoWhileStatement(node) => {
253                self.walk_statement(v, &node.body);
254                self.loop_expression_depth += 1;
255                v.enter_loop_expression();
256                self.walk_expression(v, &node.test);
257                v.leave_loop_expression();
258                self.loop_expression_depth -= 1;
259            }
260            Statement::ForInStatement(node) => {
261                let pushed = self.try_push_scope(node.base.start, node.base.node_id);
262                self.walk_for_in_of_left(v, &node.left);
263                self.loop_expression_depth += 1;
264                v.enter_loop_expression();
265                self.walk_expression(v, &node.right);
266                v.leave_loop_expression();
267                self.loop_expression_depth -= 1;
268                self.walk_statement(v, &node.body);
269                if pushed {
270                    self.scope_stack.pop();
271                }
272            }
273            Statement::ForOfStatement(node) => {
274                let pushed = self.try_push_scope(node.base.start, node.base.node_id);
275                self.walk_for_in_of_left(v, &node.left);
276                self.loop_expression_depth += 1;
277                v.enter_loop_expression();
278                self.walk_expression(v, &node.right);
279                v.leave_loop_expression();
280                self.loop_expression_depth -= 1;
281                self.walk_statement(v, &node.body);
282                if pushed {
283                    self.scope_stack.pop();
284                }
285            }
286            Statement::SwitchStatement(node) => {
287                let pushed = self.try_push_scope(node.base.start, node.base.node_id);
288                self.walk_expression(v, &node.discriminant);
289                for case in &node.cases {
290                    if let Some(test) = &case.test {
291                        self.walk_expression(v, test);
292                    }
293                    for consequent in &case.consequent {
294                        self.walk_statement(v, consequent);
295                    }
296                }
297                if pushed {
298                    self.scope_stack.pop();
299                }
300            }
301            Statement::ThrowStatement(node) => {
302                self.walk_expression(v, &node.argument);
303            }
304            Statement::TryStatement(node) => {
305                self.walk_block_statement(v, &node.block);
306                if let Some(handler) = &node.handler {
307                    let pushed = self.try_push_scope(handler.base.start, handler.base.node_id);
308                    if let Some(param) = &handler.param {
309                        self.walk_pattern(v, param);
310                    }
311                    self.walk_block_statement(v, &handler.body);
312                    if pushed {
313                        self.scope_stack.pop();
314                    }
315                }
316                if let Some(finalizer) = &node.finalizer {
317                    self.walk_block_statement(v, finalizer);
318                }
319            }
320            Statement::LabeledStatement(node) => {
321                self.walk_statement(v, &node.body);
322            }
323            Statement::VariableDeclaration(node) => {
324                self.walk_variable_declaration(v, node);
325            }
326            Statement::FunctionDeclaration(node) => {
327                self.walk_function_declaration_inner(v, node);
328            }
329            Statement::ClassDeclaration(node) => {
330                // Call the visitor hook so consumers can index the class name,
331                // but skip walking the class body (no compilable functions inside)
332                v.enter_class_declaration(node, &self.scope_stack);
333            }
334            Statement::WithStatement(node) => {
335                self.walk_expression(v, &node.object);
336                self.walk_statement(v, &node.body);
337            }
338            Statement::ExportNamedDeclaration(node) => {
339                if let Some(decl) = &node.declaration {
340                    self.walk_declaration(v, decl);
341                }
342            }
343            Statement::ExportDefaultDeclaration(node) => {
344                self.walk_export_default_decl(v, &node.declaration);
345            }
346            // No runtime expressions to traverse
347            Statement::BreakStatement(_)
348            | Statement::ContinueStatement(_)
349            | Statement::EmptyStatement(_)
350            | Statement::DebuggerStatement(_)
351            | Statement::ImportDeclaration(_)
352            | Statement::ExportAllDeclaration(_)
353            | Statement::TSTypeAliasDeclaration(_)
354            | Statement::TSInterfaceDeclaration(_)
355            | Statement::TSEnumDeclaration(_)
356            | Statement::TSModuleDeclaration(_)
357            | Statement::TSDeclareFunction(_)
358            | Statement::TypeAlias(_)
359            | Statement::OpaqueType(_)
360            | Statement::InterfaceDeclaration(_)
361            | Statement::DeclareVariable(_)
362            | Statement::DeclareFunction(_)
363            | Statement::DeclareClass(_)
364            | Statement::DeclareModule(_)
365            | Statement::DeclareModuleExports(_)
366            | Statement::DeclareExportDeclaration(_)
367            | Statement::DeclareExportAllDeclaration(_)
368            | Statement::DeclareInterface(_)
369            | Statement::DeclareTypeAlias(_)
370            | Statement::DeclareOpaqueType(_)
371            | Statement::EnumDeclaration(_)
372            // Unmodeled raw node: opaque, no compilable children to traverse.
373            | Statement::Unknown(_) => {}
374        }
375    }
376
377    pub fn walk_expression<'ast>(&mut self, v: &mut impl Visitor<'ast>, expr: &'ast Expression) {
378        match expr {
379            Expression::Identifier(node) => {
380                v.enter_identifier(node, &self.scope_stack);
381            }
382            Expression::CallExpression(node) => {
383                v.enter_call_expression(node, &self.scope_stack);
384                self.walk_expression(v, &node.callee);
385                for arg in &node.arguments {
386                    self.walk_expression(v, arg);
387                }
388                v.leave_call_expression(node, &self.scope_stack);
389            }
390            Expression::MemberExpression(node) => {
391                self.walk_expression(v, &node.object);
392                if node.computed {
393                    self.walk_expression(v, &node.property);
394                }
395            }
396            Expression::OptionalCallExpression(node) => {
397                self.walk_expression(v, &node.callee);
398                for arg in &node.arguments {
399                    self.walk_expression(v, arg);
400                }
401            }
402            Expression::OptionalMemberExpression(node) => {
403                self.walk_expression(v, &node.object);
404                if node.computed {
405                    self.walk_expression(v, &node.property);
406                }
407            }
408            Expression::BinaryExpression(node) => {
409                self.walk_expression(v, &node.left);
410                self.walk_expression(v, &node.right);
411            }
412            Expression::LogicalExpression(node) => {
413                self.walk_expression(v, &node.left);
414                self.walk_expression(v, &node.right);
415            }
416            Expression::UnaryExpression(node) => {
417                self.walk_expression(v, &node.argument);
418            }
419            Expression::UpdateExpression(node) => {
420                v.enter_update_expression(node, &self.scope_stack);
421                self.walk_expression(v, &node.argument);
422            }
423            Expression::ConditionalExpression(node) => {
424                self.walk_expression(v, &node.test);
425                self.walk_expression(v, &node.consequent);
426                self.walk_expression(v, &node.alternate);
427            }
428            Expression::AssignmentExpression(node) => {
429                v.enter_assignment_expression(node, &self.scope_stack);
430                self.walk_pattern(v, &node.left);
431                self.walk_expression(v, &node.right);
432            }
433            Expression::SequenceExpression(node) => {
434                for expr in &node.expressions {
435                    self.walk_expression(v, expr);
436                }
437            }
438            Expression::ArrowFunctionExpression(node) => {
439                let pushed = self.try_push_scope(node.base.start, node.base.node_id);
440                v.enter_arrow_function_expression(node, &self.scope_stack);
441                if v.traverse_function_bodies() {
442                    for param in &node.params {
443                        self.walk_pattern(v, param);
444                    }
445                    match node.body.as_ref() {
446                        ArrowFunctionBody::BlockStatement(block) => {
447                            self.walk_block_statement(v, block);
448                        }
449                        ArrowFunctionBody::Expression(expr) => {
450                            self.walk_expression(v, expr);
451                        }
452                    }
453                }
454                v.leave_arrow_function_expression(node, &self.scope_stack);
455                if pushed {
456                    self.scope_stack.pop();
457                }
458            }
459            Expression::FunctionExpression(node) => {
460                let pushed = self.try_push_scope(node.base.start, node.base.node_id);
461                v.enter_function_expression(node, &self.scope_stack);
462                if v.traverse_function_bodies() {
463                    for param in &node.params {
464                        self.walk_pattern(v, param);
465                    }
466                    self.walk_block_statement(v, &node.body);
467                }
468                v.leave_function_expression(node, &self.scope_stack);
469                if pushed {
470                    self.scope_stack.pop();
471                }
472            }
473            Expression::ObjectExpression(node) => {
474                for prop in &node.properties {
475                    self.walk_object_expression_property(v, prop);
476                }
477            }
478            Expression::ArrayExpression(node) => {
479                for element in &node.elements {
480                    if let Some(el) = element {
481                        self.walk_expression(v, el);
482                    }
483                }
484            }
485            Expression::NewExpression(node) => {
486                self.walk_expression(v, &node.callee);
487                for arg in &node.arguments {
488                    self.walk_expression(v, arg);
489                }
490            }
491            Expression::TemplateLiteral(node) => {
492                for expr in &node.expressions {
493                    self.walk_expression(v, expr);
494                }
495            }
496            Expression::TaggedTemplateExpression(node) => {
497                self.walk_expression(v, &node.tag);
498                for expr in &node.quasi.expressions {
499                    self.walk_expression(v, expr);
500                }
501            }
502            Expression::AwaitExpression(node) => {
503                self.walk_expression(v, &node.argument);
504            }
505            Expression::YieldExpression(node) => {
506                if let Some(arg) = &node.argument {
507                    self.walk_expression(v, arg);
508                }
509            }
510            Expression::SpreadElement(node) => {
511                self.walk_expression(v, &node.argument);
512            }
513            Expression::ParenthesizedExpression(node) => {
514                self.walk_expression(v, &node.expression);
515            }
516            Expression::AssignmentPattern(node) => {
517                self.walk_pattern(v, &node.left);
518                self.walk_expression(v, &node.right);
519            }
520            Expression::ClassExpression(node) => {
521                // Call the visitor hook so consumers can index the class name,
522                // but skip walking the class body
523                v.enter_class_expression(node, &self.scope_stack);
524            }
525            // JSX
526            Expression::JSXElement(node) => self.walk_jsx_element(v, node),
527            Expression::JSXFragment(node) => self.walk_jsx_fragment(v, node),
528            // TS/Flow wrappers - traverse inner expression
529            Expression::TSAsExpression(node) => self.walk_expression(v, &node.expression),
530            Expression::TSSatisfiesExpression(node) => self.walk_expression(v, &node.expression),
531            Expression::TSNonNullExpression(node) => self.walk_expression(v, &node.expression),
532            Expression::TSTypeAssertion(node) => self.walk_expression(v, &node.expression),
533            Expression::TSInstantiationExpression(node) => {
534                self.walk_expression(v, &node.expression)
535            }
536            Expression::TypeCastExpression(node) => self.walk_expression(v, &node.expression),
537            // Leaf nodes
538            Expression::StringLiteral(_)
539            | Expression::NumericLiteral(_)
540            | Expression::BooleanLiteral(_)
541            | Expression::NullLiteral(_)
542            | Expression::BigIntLiteral(_)
543            | Expression::RegExpLiteral(_)
544            | Expression::MetaProperty(_)
545            | Expression::PrivateName(_)
546            | Expression::Super(_)
547            | Expression::Import(_)
548            | Expression::ThisExpression(_) => {}
549        }
550    }
551
552    pub fn walk_pattern<'ast>(&mut self, v: &mut impl Visitor<'ast>, pat: &'ast PatternLike) {
553        match pat {
554            PatternLike::Identifier(node) => {
555                v.enter_identifier(node, &self.scope_stack);
556            }
557            PatternLike::ObjectPattern(node) => {
558                for prop in &node.properties {
559                    match prop {
560                        ObjectPatternProperty::ObjectProperty(p) => {
561                            if p.computed {
562                                self.walk_expression(v, &p.key);
563                            }
564                            self.walk_pattern(v, &p.value);
565                        }
566                        ObjectPatternProperty::RestElement(p) => {
567                            self.walk_pattern(v, &p.argument);
568                        }
569                    }
570                }
571            }
572            PatternLike::ArrayPattern(node) => {
573                for element in &node.elements {
574                    if let Some(el) = element {
575                        self.walk_pattern(v, el);
576                    }
577                }
578            }
579            PatternLike::AssignmentPattern(node) => {
580                self.walk_pattern(v, &node.left);
581                self.walk_expression(v, &node.right);
582            }
583            PatternLike::RestElement(node) => {
584                self.walk_pattern(v, &node.argument);
585            }
586            PatternLike::MemberExpression(node) => {
587                self.walk_expression(v, &node.object);
588                if node.computed {
589                    self.walk_expression(v, &node.property);
590                }
591            }
592            PatternLike::TSAsExpression(node) => self.walk_expression(v, &node.expression),
593            PatternLike::TSSatisfiesExpression(node) => self.walk_expression(v, &node.expression),
594            PatternLike::TSNonNullExpression(node) => self.walk_expression(v, &node.expression),
595            PatternLike::TSTypeAssertion(node) => self.walk_expression(v, &node.expression),
596            PatternLike::TypeCastExpression(node) => self.walk_expression(v, &node.expression),
597        }
598    }
599
600    // ---- Private helper walk methods ----
601
602    fn walk_for_in_of_left<'ast>(&mut self, v: &mut impl Visitor<'ast>, left: &'ast ForInOfLeft) {
603        match left {
604            ForInOfLeft::VariableDeclaration(decl) => self.walk_variable_declaration(v, decl),
605            ForInOfLeft::Pattern(pat) => self.walk_pattern(v, pat),
606        }
607    }
608
609    fn walk_variable_declaration<'ast>(
610        &mut self,
611        v: &mut impl Visitor<'ast>,
612        decl: &'ast VariableDeclaration,
613    ) {
614        for declarator in &decl.declarations {
615            v.enter_variable_declarator(declarator, &self.scope_stack);
616            self.walk_pattern(v, &declarator.id);
617            if let Some(init) = &declarator.init {
618                self.walk_expression(v, init);
619            }
620            v.leave_variable_declarator(declarator, &self.scope_stack);
621        }
622    }
623
624    fn walk_function_declaration_inner<'ast>(
625        &mut self,
626        v: &mut impl Visitor<'ast>,
627        node: &'ast FunctionDeclaration,
628    ) {
629        let pushed = self.try_push_scope(node.base.start, node.base.node_id);
630        v.enter_function_declaration(node, &self.scope_stack);
631        if v.traverse_function_bodies() {
632            for param in &node.params {
633                self.walk_pattern(v, param);
634            }
635            self.walk_block_statement(v, &node.body);
636        }
637        v.leave_function_declaration(node, &self.scope_stack);
638        if pushed {
639            self.scope_stack.pop();
640        }
641    }
642
643    fn walk_object_expression_property<'ast>(
644        &mut self,
645        v: &mut impl Visitor<'ast>,
646        prop: &'ast ObjectExpressionProperty,
647    ) {
648        match prop {
649            ObjectExpressionProperty::ObjectProperty(p) => {
650                if p.computed {
651                    self.walk_expression(v, &p.key);
652                }
653                self.walk_expression(v, &p.value);
654            }
655            ObjectExpressionProperty::ObjectMethod(node) => {
656                let pushed = self.try_push_scope(node.base.start, node.base.node_id);
657                v.enter_object_method(node, &self.scope_stack);
658                if v.traverse_function_bodies() {
659                    if node.computed {
660                        self.walk_expression(v, &node.key);
661                    }
662                    for param in &node.params {
663                        self.walk_pattern(v, param);
664                    }
665                    self.walk_block_statement(v, &node.body);
666                }
667                v.leave_object_method(node, &self.scope_stack);
668                if pushed {
669                    self.scope_stack.pop();
670                }
671            }
672            ObjectExpressionProperty::SpreadElement(p) => {
673                self.walk_expression(v, &p.argument);
674            }
675        }
676    }
677
678    fn walk_declaration<'ast>(&mut self, v: &mut impl Visitor<'ast>, decl: &'ast Declaration) {
679        match decl {
680            Declaration::FunctionDeclaration(node) => {
681                self.walk_function_declaration_inner(v, node);
682            }
683            Declaration::VariableDeclaration(node) => {
684                self.walk_variable_declaration(v, node);
685            }
686            // TS/Flow declarations - no runtime expressions
687            _ => {}
688        }
689    }
690
691    fn walk_export_default_decl<'ast>(
692        &mut self,
693        v: &mut impl Visitor<'ast>,
694        decl: &'ast ExportDefaultDecl,
695    ) {
696        match decl {
697            ExportDefaultDecl::FunctionDeclaration(node) => {
698                self.walk_function_declaration_inner(v, node);
699            }
700            ExportDefaultDecl::ClassDeclaration(node) => {
701                // Call the visitor hook, but skip the class body
702                v.enter_class_declaration(node, &self.scope_stack);
703            }
704            ExportDefaultDecl::EnumDeclaration(_) => {
705                // Flow enum declarations are opaque — no visitor hooks needed
706            }
707            ExportDefaultDecl::Expression(expr) => {
708                self.walk_expression(v, expr);
709            }
710        }
711    }
712
713    fn walk_jsx_element<'ast>(&mut self, v: &mut impl Visitor<'ast>, node: &'ast JSXElement) {
714        v.enter_jsx_opening_element(&node.opening_element, &self.scope_stack);
715        self.walk_jsx_element_name(v, &node.opening_element.name);
716        v.leave_jsx_opening_element(&node.opening_element, &self.scope_stack);
717        for attr in &node.opening_element.attributes {
718            match attr {
719                JSXAttributeItem::JSXAttribute(a) => {
720                    if let Some(value) = &a.value {
721                        match value {
722                            JSXAttributeValue::JSXExpressionContainer(c) => {
723                                self.walk_jsx_expr_container(v, c);
724                            }
725                            JSXAttributeValue::JSXElement(el) => {
726                                self.walk_jsx_element(v, el);
727                            }
728                            JSXAttributeValue::JSXFragment(f) => {
729                                self.walk_jsx_fragment(v, f);
730                            }
731                            JSXAttributeValue::StringLiteral(_) => {}
732                        }
733                    }
734                }
735                JSXAttributeItem::JSXSpreadAttribute(a) => {
736                    self.walk_expression(v, &a.argument);
737                }
738            }
739        }
740        for child in &node.children {
741            self.walk_jsx_child(v, child);
742        }
743    }
744
745    fn walk_jsx_fragment<'ast>(&mut self, v: &mut impl Visitor<'ast>, node: &'ast JSXFragment) {
746        for child in &node.children {
747            self.walk_jsx_child(v, child);
748        }
749    }
750
751    fn walk_jsx_child<'ast>(&mut self, v: &mut impl Visitor<'ast>, child: &'ast JSXChild) {
752        match child {
753            JSXChild::JSXElement(el) => self.walk_jsx_element(v, el),
754            JSXChild::JSXFragment(f) => self.walk_jsx_fragment(v, f),
755            JSXChild::JSXExpressionContainer(c) => self.walk_jsx_expr_container(v, c),
756            JSXChild::JSXSpreadChild(s) => self.walk_expression(v, &s.expression),
757            JSXChild::JSXText(_) => {}
758        }
759    }
760
761    fn walk_jsx_expr_container<'ast>(
762        &mut self,
763        v: &mut impl Visitor<'ast>,
764        node: &'ast JSXExpressionContainer,
765    ) {
766        match &node.expression {
767            JSXExpressionContainerExpr::Expression(expr) => self.walk_expression(v, expr),
768            JSXExpressionContainerExpr::JSXEmptyExpression(_) => {}
769        }
770    }
771
772    fn walk_jsx_element_name<'ast>(
773        &mut self,
774        v: &mut impl Visitor<'ast>,
775        name: &'ast JSXElementName,
776    ) {
777        match name {
778            JSXElementName::JSXIdentifier(id) => {
779                v.enter_jsx_identifier(id, &self.scope_stack);
780            }
781            JSXElementName::JSXMemberExpression(expr) => {
782                self.walk_jsx_member_expression(v, expr);
783            }
784            JSXElementName::JSXNamespacedName(_) => {}
785        }
786    }
787
788    fn walk_jsx_member_expression<'ast>(
789        &mut self,
790        v: &mut impl Visitor<'ast>,
791        expr: &'ast JSXMemberExpression,
792    ) {
793        match &*expr.object {
794            JSXMemberExprObject::JSXIdentifier(id) => {
795                v.enter_jsx_identifier(id, &self.scope_stack);
796            }
797            JSXMemberExprObject::JSXMemberExpression(inner) => {
798                self.walk_jsx_member_expression(v, inner);
799            }
800        }
801        v.enter_jsx_identifier(&expr.property, &self.scope_stack);
802    }
803}
804
805// =============================================================================
806// Mutable visitor
807// =============================================================================
808
809/// Result from a mutable visitor hook.
810#[derive(Debug, Clone, Copy, PartialEq, Eq)]
811pub enum VisitResult {
812    /// Continue traversal to children.
813    Continue,
814    /// Stop traversal immediately.
815    Stop,
816}
817
818impl VisitResult {
819    pub fn is_stop(self) -> bool {
820        self == VisitResult::Stop
821    }
822}
823
824/// Trait for mutating Babel AST nodes during traversal.
825///
826/// Override hooks to intercept and mutate specific node types.
827/// Return [`VisitResult::Stop`] from any hook to halt the walk.
828/// Hooks are called *before* the walker recurses into children,
829/// so returning `Stop` prevents child traversal.
830pub trait MutVisitor {
831    /// Called for every statement before recursing into its children.
832    fn visit_statement(&mut self, _stmt: &mut Statement) -> VisitResult {
833        VisitResult::Continue
834    }
835
836    /// Called for every expression before recursing into its children.
837    fn visit_expression(&mut self, _expr: &mut Expression) -> VisitResult {
838        VisitResult::Continue
839    }
840
841    /// Called for identifiers in expression position.
842    fn visit_identifier(&mut self, _node: &mut Identifier) -> VisitResult {
843        VisitResult::Continue
844    }
845}
846
847/// Walk a program's body mutably, calling visitor hooks for each node.
848pub fn walk_program_mut(v: &mut impl MutVisitor, program: &mut Program) -> VisitResult {
849    for stmt in program.body.iter_mut() {
850        if walk_statement_mut(v, stmt).is_stop() {
851            return VisitResult::Stop;
852        }
853    }
854    VisitResult::Continue
855}
856
857/// Walk a single statement mutably, calling visitor hooks and recursing into children.
858pub fn walk_statement_mut(v: &mut impl MutVisitor, stmt: &mut Statement) -> VisitResult {
859    if v.visit_statement(stmt).is_stop() {
860        return VisitResult::Stop;
861    }
862    match stmt {
863        Statement::BlockStatement(node) => {
864            for s in node.body.iter_mut() {
865                if walk_statement_mut(v, s).is_stop() {
866                    return VisitResult::Stop;
867                }
868            }
869        }
870        Statement::ReturnStatement(node) => {
871            if let Some(ref mut arg) = node.argument {
872                if walk_expression_mut(v, arg).is_stop() {
873                    return VisitResult::Stop;
874                }
875            }
876        }
877        Statement::ExpressionStatement(node) => {
878            if walk_expression_mut(v, &mut node.expression).is_stop() {
879                return VisitResult::Stop;
880            }
881        }
882        Statement::IfStatement(node) => {
883            if walk_expression_mut(v, &mut node.test).is_stop() {
884                return VisitResult::Stop;
885            }
886            if walk_statement_mut(v, &mut node.consequent).is_stop() {
887                return VisitResult::Stop;
888            }
889            if let Some(ref mut alt) = node.alternate {
890                if walk_statement_mut(v, alt).is_stop() {
891                    return VisitResult::Stop;
892                }
893            }
894        }
895        Statement::ForStatement(node) => {
896            if let Some(ref mut init) = node.init {
897                match init.as_mut() {
898                    ForInit::VariableDeclaration(decl) => {
899                        if walk_variable_declaration_mut(v, decl).is_stop() {
900                            return VisitResult::Stop;
901                        }
902                    }
903                    ForInit::Expression(expr) => {
904                        if walk_expression_mut(v, expr).is_stop() {
905                            return VisitResult::Stop;
906                        }
907                    }
908                }
909            }
910            if let Some(ref mut test) = node.test {
911                if walk_expression_mut(v, test).is_stop() {
912                    return VisitResult::Stop;
913                }
914            }
915            if let Some(ref mut update) = node.update {
916                if walk_expression_mut(v, update).is_stop() {
917                    return VisitResult::Stop;
918                }
919            }
920            if walk_statement_mut(v, &mut node.body).is_stop() {
921                return VisitResult::Stop;
922            }
923        }
924        Statement::WhileStatement(node) => {
925            if walk_expression_mut(v, &mut node.test).is_stop() {
926                return VisitResult::Stop;
927            }
928            if walk_statement_mut(v, &mut node.body).is_stop() {
929                return VisitResult::Stop;
930            }
931        }
932        Statement::DoWhileStatement(node) => {
933            if walk_statement_mut(v, &mut node.body).is_stop() {
934                return VisitResult::Stop;
935            }
936            if walk_expression_mut(v, &mut node.test).is_stop() {
937                return VisitResult::Stop;
938            }
939        }
940        Statement::ForInStatement(node) => {
941            if walk_expression_mut(v, &mut node.right).is_stop() {
942                return VisitResult::Stop;
943            }
944            if walk_statement_mut(v, &mut node.body).is_stop() {
945                return VisitResult::Stop;
946            }
947        }
948        Statement::ForOfStatement(node) => {
949            if walk_expression_mut(v, &mut node.right).is_stop() {
950                return VisitResult::Stop;
951            }
952            if walk_statement_mut(v, &mut node.body).is_stop() {
953                return VisitResult::Stop;
954            }
955        }
956        Statement::SwitchStatement(node) => {
957            if walk_expression_mut(v, &mut node.discriminant).is_stop() {
958                return VisitResult::Stop;
959            }
960            for case in node.cases.iter_mut() {
961                if let Some(ref mut test) = case.test {
962                    if walk_expression_mut(v, test).is_stop() {
963                        return VisitResult::Stop;
964                    }
965                }
966                for s in case.consequent.iter_mut() {
967                    if walk_statement_mut(v, s).is_stop() {
968                        return VisitResult::Stop;
969                    }
970                }
971            }
972        }
973        Statement::ThrowStatement(node) => {
974            if walk_expression_mut(v, &mut node.argument).is_stop() {
975                return VisitResult::Stop;
976            }
977        }
978        Statement::TryStatement(node) => {
979            for s in node.block.body.iter_mut() {
980                if walk_statement_mut(v, s).is_stop() {
981                    return VisitResult::Stop;
982                }
983            }
984            if let Some(ref mut handler) = node.handler {
985                for s in handler.body.body.iter_mut() {
986                    if walk_statement_mut(v, s).is_stop() {
987                        return VisitResult::Stop;
988                    }
989                }
990            }
991            if let Some(ref mut finalizer) = node.finalizer {
992                for s in finalizer.body.iter_mut() {
993                    if walk_statement_mut(v, s).is_stop() {
994                        return VisitResult::Stop;
995                    }
996                }
997            }
998        }
999        Statement::LabeledStatement(node) => {
1000            if walk_statement_mut(v, &mut node.body).is_stop() {
1001                return VisitResult::Stop;
1002            }
1003        }
1004        Statement::VariableDeclaration(node) => {
1005            if walk_variable_declaration_mut(v, node).is_stop() {
1006                return VisitResult::Stop;
1007            }
1008        }
1009        Statement::FunctionDeclaration(node) => {
1010            for s in node.body.body.iter_mut() {
1011                if walk_statement_mut(v, s).is_stop() {
1012                    return VisitResult::Stop;
1013                }
1014            }
1015        }
1016        Statement::ClassDeclaration(node) => {
1017            if let Some(ref mut sc) = node.super_class {
1018                if walk_expression_mut(v, sc).is_stop() {
1019                    return VisitResult::Stop;
1020                }
1021            }
1022        }
1023        Statement::WithStatement(node) => {
1024            if walk_expression_mut(v, &mut node.object).is_stop() {
1025                return VisitResult::Stop;
1026            }
1027            if walk_statement_mut(v, &mut node.body).is_stop() {
1028                return VisitResult::Stop;
1029            }
1030        }
1031        Statement::ExportNamedDeclaration(node) => {
1032            if let Some(ref mut decl) = node.declaration {
1033                if walk_declaration_mut(v, decl).is_stop() {
1034                    return VisitResult::Stop;
1035                }
1036            }
1037        }
1038        Statement::ExportDefaultDeclaration(node) => {
1039            if walk_export_default_decl_mut(v, &mut node.declaration).is_stop() {
1040                return VisitResult::Stop;
1041            }
1042        }
1043        // No runtime expressions to traverse
1044        Statement::BreakStatement(_)
1045        | Statement::ContinueStatement(_)
1046        | Statement::EmptyStatement(_)
1047        | Statement::DebuggerStatement(_)
1048        | Statement::ImportDeclaration(_)
1049        | Statement::ExportAllDeclaration(_)
1050        | Statement::TSTypeAliasDeclaration(_)
1051        | Statement::TSInterfaceDeclaration(_)
1052        | Statement::TSEnumDeclaration(_)
1053        | Statement::TSModuleDeclaration(_)
1054        | Statement::TSDeclareFunction(_)
1055        | Statement::TypeAlias(_)
1056        | Statement::OpaqueType(_)
1057        | Statement::InterfaceDeclaration(_)
1058        | Statement::DeclareVariable(_)
1059        | Statement::DeclareFunction(_)
1060        | Statement::DeclareClass(_)
1061        | Statement::DeclareModule(_)
1062        | Statement::DeclareModuleExports(_)
1063        | Statement::DeclareExportDeclaration(_)
1064        | Statement::DeclareExportAllDeclaration(_)
1065        | Statement::DeclareInterface(_)
1066        | Statement::DeclareTypeAlias(_)
1067        | Statement::DeclareOpaqueType(_)
1068        | Statement::EnumDeclaration(_)
1069        // Unmodeled raw node: opaque, no compilable children to traverse.
1070        | Statement::Unknown(_) => {}
1071    }
1072    VisitResult::Continue
1073}
1074
1075/// Walk an expression mutably, calling visitor hooks and recursing into children.
1076pub fn walk_expression_mut(v: &mut impl MutVisitor, expr: &mut Expression) -> VisitResult {
1077    if v.visit_expression(expr).is_stop() {
1078        return VisitResult::Stop;
1079    }
1080    match expr {
1081        Expression::Identifier(node) => {
1082            if v.visit_identifier(node).is_stop() {
1083                return VisitResult::Stop;
1084            }
1085        }
1086        Expression::CallExpression(node) => {
1087            if walk_expression_mut(v, &mut node.callee).is_stop() {
1088                return VisitResult::Stop;
1089            }
1090            for arg in node.arguments.iter_mut() {
1091                if walk_expression_mut(v, arg).is_stop() {
1092                    return VisitResult::Stop;
1093                }
1094            }
1095        }
1096        Expression::MemberExpression(node) => {
1097            if walk_expression_mut(v, &mut node.object).is_stop() {
1098                return VisitResult::Stop;
1099            }
1100            if node.computed {
1101                if walk_expression_mut(v, &mut node.property).is_stop() {
1102                    return VisitResult::Stop;
1103                }
1104            }
1105        }
1106        Expression::OptionalCallExpression(node) => {
1107            if walk_expression_mut(v, &mut node.callee).is_stop() {
1108                return VisitResult::Stop;
1109            }
1110            for arg in node.arguments.iter_mut() {
1111                if walk_expression_mut(v, arg).is_stop() {
1112                    return VisitResult::Stop;
1113                }
1114            }
1115        }
1116        Expression::OptionalMemberExpression(node) => {
1117            if walk_expression_mut(v, &mut node.object).is_stop() {
1118                return VisitResult::Stop;
1119            }
1120            if node.computed {
1121                if walk_expression_mut(v, &mut node.property).is_stop() {
1122                    return VisitResult::Stop;
1123                }
1124            }
1125        }
1126        Expression::BinaryExpression(node) => {
1127            if walk_expression_mut(v, &mut node.left).is_stop() {
1128                return VisitResult::Stop;
1129            }
1130            if walk_expression_mut(v, &mut node.right).is_stop() {
1131                return VisitResult::Stop;
1132            }
1133        }
1134        Expression::LogicalExpression(node) => {
1135            if walk_expression_mut(v, &mut node.left).is_stop() {
1136                return VisitResult::Stop;
1137            }
1138            if walk_expression_mut(v, &mut node.right).is_stop() {
1139                return VisitResult::Stop;
1140            }
1141        }
1142        Expression::UnaryExpression(node) => {
1143            if walk_expression_mut(v, &mut node.argument).is_stop() {
1144                return VisitResult::Stop;
1145            }
1146        }
1147        Expression::UpdateExpression(node) => {
1148            if walk_expression_mut(v, &mut node.argument).is_stop() {
1149                return VisitResult::Stop;
1150            }
1151        }
1152        Expression::ConditionalExpression(node) => {
1153            if walk_expression_mut(v, &mut node.test).is_stop() {
1154                return VisitResult::Stop;
1155            }
1156            if walk_expression_mut(v, &mut node.consequent).is_stop() {
1157                return VisitResult::Stop;
1158            }
1159            if walk_expression_mut(v, &mut node.alternate).is_stop() {
1160                return VisitResult::Stop;
1161            }
1162        }
1163        Expression::AssignmentExpression(node) => {
1164            if walk_expression_mut(v, &mut node.right).is_stop() {
1165                return VisitResult::Stop;
1166            }
1167        }
1168        Expression::SequenceExpression(node) => {
1169            for e in node.expressions.iter_mut() {
1170                if walk_expression_mut(v, e).is_stop() {
1171                    return VisitResult::Stop;
1172                }
1173            }
1174        }
1175        Expression::ArrowFunctionExpression(node) => match node.body.as_mut() {
1176            ArrowFunctionBody::BlockStatement(block) => {
1177                for s in block.body.iter_mut() {
1178                    if walk_statement_mut(v, s).is_stop() {
1179                        return VisitResult::Stop;
1180                    }
1181                }
1182            }
1183            ArrowFunctionBody::Expression(e) => {
1184                if walk_expression_mut(v, e).is_stop() {
1185                    return VisitResult::Stop;
1186                }
1187            }
1188        },
1189        Expression::FunctionExpression(node) => {
1190            for s in node.body.body.iter_mut() {
1191                if walk_statement_mut(v, s).is_stop() {
1192                    return VisitResult::Stop;
1193                }
1194            }
1195        }
1196        Expression::ObjectExpression(node) => {
1197            for prop in node.properties.iter_mut() {
1198                match prop {
1199                    ObjectExpressionProperty::ObjectProperty(p) => {
1200                        if p.computed {
1201                            if walk_expression_mut(v, &mut p.key).is_stop() {
1202                                return VisitResult::Stop;
1203                            }
1204                        }
1205                        if walk_expression_mut(v, &mut p.value).is_stop() {
1206                            return VisitResult::Stop;
1207                        }
1208                    }
1209                    ObjectExpressionProperty::ObjectMethod(m) => {
1210                        for s in m.body.body.iter_mut() {
1211                            if walk_statement_mut(v, s).is_stop() {
1212                                return VisitResult::Stop;
1213                            }
1214                        }
1215                    }
1216                    ObjectExpressionProperty::SpreadElement(s) => {
1217                        if walk_expression_mut(v, &mut s.argument).is_stop() {
1218                            return VisitResult::Stop;
1219                        }
1220                    }
1221                }
1222            }
1223        }
1224        Expression::ArrayExpression(node) => {
1225            for elem in node.elements.iter_mut().flatten() {
1226                if walk_expression_mut(v, elem).is_stop() {
1227                    return VisitResult::Stop;
1228                }
1229            }
1230        }
1231        Expression::NewExpression(node) => {
1232            if walk_expression_mut(v, &mut node.callee).is_stop() {
1233                return VisitResult::Stop;
1234            }
1235            for arg in node.arguments.iter_mut() {
1236                if walk_expression_mut(v, arg).is_stop() {
1237                    return VisitResult::Stop;
1238                }
1239            }
1240        }
1241        Expression::TemplateLiteral(node) => {
1242            for e in node.expressions.iter_mut() {
1243                if walk_expression_mut(v, e).is_stop() {
1244                    return VisitResult::Stop;
1245                }
1246            }
1247        }
1248        Expression::TaggedTemplateExpression(node) => {
1249            if walk_expression_mut(v, &mut node.tag).is_stop() {
1250                return VisitResult::Stop;
1251            }
1252            for e in node.quasi.expressions.iter_mut() {
1253                if walk_expression_mut(v, e).is_stop() {
1254                    return VisitResult::Stop;
1255                }
1256            }
1257        }
1258        Expression::AwaitExpression(node) => {
1259            if walk_expression_mut(v, &mut node.argument).is_stop() {
1260                return VisitResult::Stop;
1261            }
1262        }
1263        Expression::YieldExpression(node) => {
1264            if let Some(ref mut arg) = node.argument {
1265                if walk_expression_mut(v, arg).is_stop() {
1266                    return VisitResult::Stop;
1267                }
1268            }
1269        }
1270        Expression::SpreadElement(node) => {
1271            if walk_expression_mut(v, &mut node.argument).is_stop() {
1272                return VisitResult::Stop;
1273            }
1274        }
1275        Expression::ParenthesizedExpression(node) => {
1276            if walk_expression_mut(v, &mut node.expression).is_stop() {
1277                return VisitResult::Stop;
1278            }
1279        }
1280        Expression::AssignmentPattern(node) => {
1281            if walk_expression_mut(v, &mut node.right).is_stop() {
1282                return VisitResult::Stop;
1283            }
1284        }
1285        Expression::ClassExpression(node) => {
1286            if let Some(ref mut sc) = node.super_class {
1287                if walk_expression_mut(v, sc).is_stop() {
1288                    return VisitResult::Stop;
1289                }
1290            }
1291        }
1292        Expression::JSXElement(node) => {
1293            if walk_jsx_mut(v, &mut node.opening_element.attributes, &mut node.children).is_stop() {
1294                return VisitResult::Stop;
1295            }
1296        }
1297        Expression::JSXFragment(node) => {
1298            if walk_jsx_children_mut(v, &mut node.children).is_stop() {
1299                return VisitResult::Stop;
1300            }
1301        }
1302        // TS/Flow wrappers — traverse inner expression
1303        Expression::TSAsExpression(node) => {
1304            if walk_expression_mut(v, &mut node.expression).is_stop() {
1305                return VisitResult::Stop;
1306            }
1307        }
1308        Expression::TSSatisfiesExpression(node) => {
1309            if walk_expression_mut(v, &mut node.expression).is_stop() {
1310                return VisitResult::Stop;
1311            }
1312        }
1313        Expression::TSNonNullExpression(node) => {
1314            if walk_expression_mut(v, &mut node.expression).is_stop() {
1315                return VisitResult::Stop;
1316            }
1317        }
1318        Expression::TSTypeAssertion(node) => {
1319            if walk_expression_mut(v, &mut node.expression).is_stop() {
1320                return VisitResult::Stop;
1321            }
1322        }
1323        Expression::TSInstantiationExpression(node) => {
1324            if walk_expression_mut(v, &mut node.expression).is_stop() {
1325                return VisitResult::Stop;
1326            }
1327        }
1328        Expression::TypeCastExpression(node) => {
1329            if walk_expression_mut(v, &mut node.expression).is_stop() {
1330                return VisitResult::Stop;
1331            }
1332        }
1333        // Leaf nodes
1334        Expression::StringLiteral(_)
1335        | Expression::NumericLiteral(_)
1336        | Expression::BooleanLiteral(_)
1337        | Expression::NullLiteral(_)
1338        | Expression::BigIntLiteral(_)
1339        | Expression::RegExpLiteral(_)
1340        | Expression::MetaProperty(_)
1341        | Expression::PrivateName(_)
1342        | Expression::Super(_)
1343        | Expression::Import(_)
1344        | Expression::ThisExpression(_) => {}
1345    }
1346    VisitResult::Continue
1347}
1348
1349fn walk_jsx_mut(
1350    v: &mut impl MutVisitor,
1351    attrs: &mut [crate::jsx::JSXAttributeItem],
1352    children: &mut [crate::jsx::JSXChild],
1353) -> VisitResult {
1354    for attr in attrs.iter_mut() {
1355        match attr {
1356            crate::jsx::JSXAttributeItem::JSXAttribute(a) => {
1357                if let Some(ref mut val) = a.value {
1358                    match val {
1359                        crate::jsx::JSXAttributeValue::JSXExpressionContainer(c) => {
1360                            if let crate::jsx::JSXExpressionContainerExpr::Expression(ref mut e) =
1361                                c.expression
1362                            {
1363                                if walk_expression_mut(v, e).is_stop() {
1364                                    return VisitResult::Stop;
1365                                }
1366                            }
1367                        }
1368                        _ => {}
1369                    }
1370                }
1371            }
1372            crate::jsx::JSXAttributeItem::JSXSpreadAttribute(s) => {
1373                if walk_expression_mut(v, &mut s.argument).is_stop() {
1374                    return VisitResult::Stop;
1375                }
1376            }
1377        }
1378    }
1379    walk_jsx_children_mut(v, children)
1380}
1381
1382fn walk_jsx_children_mut(
1383    v: &mut impl MutVisitor,
1384    children: &mut [crate::jsx::JSXChild],
1385) -> VisitResult {
1386    for child in children.iter_mut() {
1387        match child {
1388            crate::jsx::JSXChild::JSXElement(el) => {
1389                if walk_jsx_mut(v, &mut el.opening_element.attributes, &mut el.children).is_stop() {
1390                    return VisitResult::Stop;
1391                }
1392            }
1393            crate::jsx::JSXChild::JSXFragment(f) => {
1394                if walk_jsx_children_mut(v, &mut f.children).is_stop() {
1395                    return VisitResult::Stop;
1396                }
1397            }
1398            crate::jsx::JSXChild::JSXExpressionContainer(c) => {
1399                if let crate::jsx::JSXExpressionContainerExpr::Expression(ref mut e) = c.expression
1400                {
1401                    if walk_expression_mut(v, e).is_stop() {
1402                        return VisitResult::Stop;
1403                    }
1404                }
1405            }
1406            crate::jsx::JSXChild::JSXSpreadChild(s) => {
1407                if walk_expression_mut(v, &mut s.expression).is_stop() {
1408                    return VisitResult::Stop;
1409                }
1410            }
1411            _ => {}
1412        }
1413    }
1414    VisitResult::Continue
1415}
1416
1417// ---- Private helper walk-mut functions ----
1418
1419fn walk_variable_declaration_mut(
1420    v: &mut impl MutVisitor,
1421    decl: &mut VariableDeclaration,
1422) -> VisitResult {
1423    for declarator in decl.declarations.iter_mut() {
1424        if let Some(ref mut init) = declarator.init {
1425            if walk_expression_mut(v, init).is_stop() {
1426                return VisitResult::Stop;
1427            }
1428        }
1429    }
1430    VisitResult::Continue
1431}
1432
1433fn walk_declaration_mut(v: &mut impl MutVisitor, decl: &mut Declaration) -> VisitResult {
1434    match decl {
1435        Declaration::FunctionDeclaration(node) => {
1436            for s in node.body.body.iter_mut() {
1437                if walk_statement_mut(v, s).is_stop() {
1438                    return VisitResult::Stop;
1439                }
1440            }
1441        }
1442        Declaration::VariableDeclaration(node) => {
1443            if walk_variable_declaration_mut(v, node).is_stop() {
1444                return VisitResult::Stop;
1445            }
1446        }
1447        Declaration::ClassDeclaration(node) => {
1448            if let Some(ref mut sc) = node.super_class {
1449                if walk_expression_mut(v, sc).is_stop() {
1450                    return VisitResult::Stop;
1451                }
1452            }
1453        }
1454        _ => {}
1455    }
1456    VisitResult::Continue
1457}
1458
1459fn walk_export_default_decl_mut(
1460    v: &mut impl MutVisitor,
1461    decl: &mut ExportDefaultDecl,
1462) -> VisitResult {
1463    match decl {
1464        ExportDefaultDecl::FunctionDeclaration(node) => {
1465            for s in node.body.body.iter_mut() {
1466                if walk_statement_mut(v, s).is_stop() {
1467                    return VisitResult::Stop;
1468                }
1469            }
1470        }
1471        ExportDefaultDecl::Expression(expr) => {
1472            if walk_expression_mut(v, expr).is_stop() {
1473                return VisitResult::Stop;
1474            }
1475        }
1476        ExportDefaultDecl::ClassDeclaration(node) => {
1477            if let Some(ref mut sc) = node.super_class {
1478                if walk_expression_mut(v, sc).is_stop() {
1479                    return VisitResult::Stop;
1480                }
1481            }
1482        }
1483        ExportDefaultDecl::EnumDeclaration(_) => {
1484            // Flow enum declarations are opaque — nothing to walk
1485        }
1486    }
1487    VisitResult::Continue
1488}