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) => {
594                self.walk_expression(v, &node.expression)
595            }
596            PatternLike::TSNonNullExpression(node) => {
597                self.walk_expression(v, &node.expression)
598            }
599            PatternLike::TSTypeAssertion(node) => self.walk_expression(v, &node.expression),
600            PatternLike::TypeCastExpression(node) => {
601                self.walk_expression(v, &node.expression)
602            }
603        }
604    }
605
606    // ---- Private helper walk methods ----
607
608    fn walk_for_in_of_left<'ast>(&mut self, v: &mut impl Visitor<'ast>, left: &'ast ForInOfLeft) {
609        match left {
610            ForInOfLeft::VariableDeclaration(decl) => self.walk_variable_declaration(v, decl),
611            ForInOfLeft::Pattern(pat) => self.walk_pattern(v, pat),
612        }
613    }
614
615    fn walk_variable_declaration<'ast>(
616        &mut self,
617        v: &mut impl Visitor<'ast>,
618        decl: &'ast VariableDeclaration,
619    ) {
620        for declarator in &decl.declarations {
621            v.enter_variable_declarator(declarator, &self.scope_stack);
622            self.walk_pattern(v, &declarator.id);
623            if let Some(init) = &declarator.init {
624                self.walk_expression(v, init);
625            }
626            v.leave_variable_declarator(declarator, &self.scope_stack);
627        }
628    }
629
630    fn walk_function_declaration_inner<'ast>(
631        &mut self,
632        v: &mut impl Visitor<'ast>,
633        node: &'ast FunctionDeclaration,
634    ) {
635        let pushed = self.try_push_scope(node.base.start, node.base.node_id);
636        v.enter_function_declaration(node, &self.scope_stack);
637        if v.traverse_function_bodies() {
638            for param in &node.params {
639                self.walk_pattern(v, param);
640            }
641            self.walk_block_statement(v, &node.body);
642        }
643        v.leave_function_declaration(node, &self.scope_stack);
644        if pushed {
645            self.scope_stack.pop();
646        }
647    }
648
649    fn walk_object_expression_property<'ast>(
650        &mut self,
651        v: &mut impl Visitor<'ast>,
652        prop: &'ast ObjectExpressionProperty,
653    ) {
654        match prop {
655            ObjectExpressionProperty::ObjectProperty(p) => {
656                if p.computed {
657                    self.walk_expression(v, &p.key);
658                }
659                self.walk_expression(v, &p.value);
660            }
661            ObjectExpressionProperty::ObjectMethod(node) => {
662                let pushed = self.try_push_scope(node.base.start, node.base.node_id);
663                v.enter_object_method(node, &self.scope_stack);
664                if v.traverse_function_bodies() {
665                    if node.computed {
666                        self.walk_expression(v, &node.key);
667                    }
668                    for param in &node.params {
669                        self.walk_pattern(v, param);
670                    }
671                    self.walk_block_statement(v, &node.body);
672                }
673                v.leave_object_method(node, &self.scope_stack);
674                if pushed {
675                    self.scope_stack.pop();
676                }
677            }
678            ObjectExpressionProperty::SpreadElement(p) => {
679                self.walk_expression(v, &p.argument);
680            }
681        }
682    }
683
684    fn walk_declaration<'ast>(&mut self, v: &mut impl Visitor<'ast>, decl: &'ast Declaration) {
685        match decl {
686            Declaration::FunctionDeclaration(node) => {
687                self.walk_function_declaration_inner(v, node);
688            }
689            Declaration::VariableDeclaration(node) => {
690                self.walk_variable_declaration(v, node);
691            }
692            // TS/Flow declarations - no runtime expressions
693            _ => {}
694        }
695    }
696
697    fn walk_export_default_decl<'ast>(
698        &mut self,
699        v: &mut impl Visitor<'ast>,
700        decl: &'ast ExportDefaultDecl,
701    ) {
702        match decl {
703            ExportDefaultDecl::FunctionDeclaration(node) => {
704                self.walk_function_declaration_inner(v, node);
705            }
706            ExportDefaultDecl::ClassDeclaration(node) => {
707                // Call the visitor hook, but skip the class body
708                v.enter_class_declaration(node, &self.scope_stack);
709            }
710            ExportDefaultDecl::EnumDeclaration(_) => {
711                // Flow enum declarations are opaque — no visitor hooks needed
712            }
713            ExportDefaultDecl::Expression(expr) => {
714                self.walk_expression(v, expr);
715            }
716        }
717    }
718
719    fn walk_jsx_element<'ast>(&mut self, v: &mut impl Visitor<'ast>, node: &'ast JSXElement) {
720        v.enter_jsx_opening_element(&node.opening_element, &self.scope_stack);
721        self.walk_jsx_element_name(v, &node.opening_element.name);
722        v.leave_jsx_opening_element(&node.opening_element, &self.scope_stack);
723        for attr in &node.opening_element.attributes {
724            match attr {
725                JSXAttributeItem::JSXAttribute(a) => {
726                    if let Some(value) = &a.value {
727                        match value {
728                            JSXAttributeValue::JSXExpressionContainer(c) => {
729                                self.walk_jsx_expr_container(v, c);
730                            }
731                            JSXAttributeValue::JSXElement(el) => {
732                                self.walk_jsx_element(v, el);
733                            }
734                            JSXAttributeValue::JSXFragment(f) => {
735                                self.walk_jsx_fragment(v, f);
736                            }
737                            JSXAttributeValue::StringLiteral(_) => {}
738                        }
739                    }
740                }
741                JSXAttributeItem::JSXSpreadAttribute(a) => {
742                    self.walk_expression(v, &a.argument);
743                }
744            }
745        }
746        for child in &node.children {
747            self.walk_jsx_child(v, child);
748        }
749    }
750
751    fn walk_jsx_fragment<'ast>(&mut self, v: &mut impl Visitor<'ast>, node: &'ast JSXFragment) {
752        for child in &node.children {
753            self.walk_jsx_child(v, child);
754        }
755    }
756
757    fn walk_jsx_child<'ast>(&mut self, v: &mut impl Visitor<'ast>, child: &'ast JSXChild) {
758        match child {
759            JSXChild::JSXElement(el) => self.walk_jsx_element(v, el),
760            JSXChild::JSXFragment(f) => self.walk_jsx_fragment(v, f),
761            JSXChild::JSXExpressionContainer(c) => self.walk_jsx_expr_container(v, c),
762            JSXChild::JSXSpreadChild(s) => self.walk_expression(v, &s.expression),
763            JSXChild::JSXText(_) => {}
764        }
765    }
766
767    fn walk_jsx_expr_container<'ast>(
768        &mut self,
769        v: &mut impl Visitor<'ast>,
770        node: &'ast JSXExpressionContainer,
771    ) {
772        match &node.expression {
773            JSXExpressionContainerExpr::Expression(expr) => self.walk_expression(v, expr),
774            JSXExpressionContainerExpr::JSXEmptyExpression(_) => {}
775        }
776    }
777
778    fn walk_jsx_element_name<'ast>(
779        &mut self,
780        v: &mut impl Visitor<'ast>,
781        name: &'ast JSXElementName,
782    ) {
783        match name {
784            JSXElementName::JSXIdentifier(id) => {
785                v.enter_jsx_identifier(id, &self.scope_stack);
786            }
787            JSXElementName::JSXMemberExpression(expr) => {
788                self.walk_jsx_member_expression(v, expr);
789            }
790            JSXElementName::JSXNamespacedName(_) => {}
791        }
792    }
793
794    fn walk_jsx_member_expression<'ast>(
795        &mut self,
796        v: &mut impl Visitor<'ast>,
797        expr: &'ast JSXMemberExpression,
798    ) {
799        match &*expr.object {
800            JSXMemberExprObject::JSXIdentifier(id) => {
801                v.enter_jsx_identifier(id, &self.scope_stack);
802            }
803            JSXMemberExprObject::JSXMemberExpression(inner) => {
804                self.walk_jsx_member_expression(v, inner);
805            }
806        }
807        v.enter_jsx_identifier(&expr.property, &self.scope_stack);
808    }
809}
810
811// =============================================================================
812// Mutable visitor
813// =============================================================================
814
815/// Result from a mutable visitor hook.
816#[derive(Debug, Clone, Copy, PartialEq, Eq)]
817pub enum VisitResult {
818    /// Continue traversal to children.
819    Continue,
820    /// Stop traversal immediately.
821    Stop,
822}
823
824impl VisitResult {
825    pub fn is_stop(self) -> bool {
826        self == VisitResult::Stop
827    }
828}
829
830/// Trait for mutating Babel AST nodes during traversal.
831///
832/// Override hooks to intercept and mutate specific node types.
833/// Return [`VisitResult::Stop`] from any hook to halt the walk.
834/// Hooks are called *before* the walker recurses into children,
835/// so returning `Stop` prevents child traversal.
836pub trait MutVisitor {
837    /// Called for every statement before recursing into its children.
838    fn visit_statement(&mut self, _stmt: &mut Statement) -> VisitResult {
839        VisitResult::Continue
840    }
841
842    /// Called for every expression before recursing into its children.
843    fn visit_expression(&mut self, _expr: &mut Expression) -> VisitResult {
844        VisitResult::Continue
845    }
846
847    /// Called for identifiers in expression position.
848    fn visit_identifier(&mut self, _node: &mut Identifier) -> VisitResult {
849        VisitResult::Continue
850    }
851}
852
853/// Walk a program's body mutably, calling visitor hooks for each node.
854pub fn walk_program_mut(v: &mut impl MutVisitor, program: &mut Program) -> VisitResult {
855    for stmt in program.body.iter_mut() {
856        if walk_statement_mut(v, stmt).is_stop() {
857            return VisitResult::Stop;
858        }
859    }
860    VisitResult::Continue
861}
862
863/// Walk a single statement mutably, calling visitor hooks and recursing into children.
864pub fn walk_statement_mut(v: &mut impl MutVisitor, stmt: &mut Statement) -> VisitResult {
865    if v.visit_statement(stmt).is_stop() {
866        return VisitResult::Stop;
867    }
868    match stmt {
869        Statement::BlockStatement(node) => {
870            for s in node.body.iter_mut() {
871                if walk_statement_mut(v, s).is_stop() {
872                    return VisitResult::Stop;
873                }
874            }
875        }
876        Statement::ReturnStatement(node) => {
877            if let Some(ref mut arg) = node.argument {
878                if walk_expression_mut(v, arg).is_stop() {
879                    return VisitResult::Stop;
880                }
881            }
882        }
883        Statement::ExpressionStatement(node) => {
884            if walk_expression_mut(v, &mut node.expression).is_stop() {
885                return VisitResult::Stop;
886            }
887        }
888        Statement::IfStatement(node) => {
889            if walk_expression_mut(v, &mut node.test).is_stop() {
890                return VisitResult::Stop;
891            }
892            if walk_statement_mut(v, &mut node.consequent).is_stop() {
893                return VisitResult::Stop;
894            }
895            if let Some(ref mut alt) = node.alternate {
896                if walk_statement_mut(v, alt).is_stop() {
897                    return VisitResult::Stop;
898                }
899            }
900        }
901        Statement::ForStatement(node) => {
902            if let Some(ref mut init) = node.init {
903                match init.as_mut() {
904                    ForInit::VariableDeclaration(decl) => {
905                        if walk_variable_declaration_mut(v, decl).is_stop() {
906                            return VisitResult::Stop;
907                        }
908                    }
909                    ForInit::Expression(expr) => {
910                        if walk_expression_mut(v, expr).is_stop() {
911                            return VisitResult::Stop;
912                        }
913                    }
914                }
915            }
916            if let Some(ref mut test) = node.test {
917                if walk_expression_mut(v, test).is_stop() {
918                    return VisitResult::Stop;
919                }
920            }
921            if let Some(ref mut update) = node.update {
922                if walk_expression_mut(v, update).is_stop() {
923                    return VisitResult::Stop;
924                }
925            }
926            if walk_statement_mut(v, &mut node.body).is_stop() {
927                return VisitResult::Stop;
928            }
929        }
930        Statement::WhileStatement(node) => {
931            if walk_expression_mut(v, &mut node.test).is_stop() {
932                return VisitResult::Stop;
933            }
934            if walk_statement_mut(v, &mut node.body).is_stop() {
935                return VisitResult::Stop;
936            }
937        }
938        Statement::DoWhileStatement(node) => {
939            if walk_statement_mut(v, &mut node.body).is_stop() {
940                return VisitResult::Stop;
941            }
942            if walk_expression_mut(v, &mut node.test).is_stop() {
943                return VisitResult::Stop;
944            }
945        }
946        Statement::ForInStatement(node) => {
947            if walk_expression_mut(v, &mut node.right).is_stop() {
948                return VisitResult::Stop;
949            }
950            if walk_statement_mut(v, &mut node.body).is_stop() {
951                return VisitResult::Stop;
952            }
953        }
954        Statement::ForOfStatement(node) => {
955            if walk_expression_mut(v, &mut node.right).is_stop() {
956                return VisitResult::Stop;
957            }
958            if walk_statement_mut(v, &mut node.body).is_stop() {
959                return VisitResult::Stop;
960            }
961        }
962        Statement::SwitchStatement(node) => {
963            if walk_expression_mut(v, &mut node.discriminant).is_stop() {
964                return VisitResult::Stop;
965            }
966            for case in node.cases.iter_mut() {
967                if let Some(ref mut test) = case.test {
968                    if walk_expression_mut(v, test).is_stop() {
969                        return VisitResult::Stop;
970                    }
971                }
972                for s in case.consequent.iter_mut() {
973                    if walk_statement_mut(v, s).is_stop() {
974                        return VisitResult::Stop;
975                    }
976                }
977            }
978        }
979        Statement::ThrowStatement(node) => {
980            if walk_expression_mut(v, &mut node.argument).is_stop() {
981                return VisitResult::Stop;
982            }
983        }
984        Statement::TryStatement(node) => {
985            for s in node.block.body.iter_mut() {
986                if walk_statement_mut(v, s).is_stop() {
987                    return VisitResult::Stop;
988                }
989            }
990            if let Some(ref mut handler) = node.handler {
991                for s in handler.body.body.iter_mut() {
992                    if walk_statement_mut(v, s).is_stop() {
993                        return VisitResult::Stop;
994                    }
995                }
996            }
997            if let Some(ref mut finalizer) = node.finalizer {
998                for s in finalizer.body.iter_mut() {
999                    if walk_statement_mut(v, s).is_stop() {
1000                        return VisitResult::Stop;
1001                    }
1002                }
1003            }
1004        }
1005        Statement::LabeledStatement(node) => {
1006            if walk_statement_mut(v, &mut node.body).is_stop() {
1007                return VisitResult::Stop;
1008            }
1009        }
1010        Statement::VariableDeclaration(node) => {
1011            if walk_variable_declaration_mut(v, node).is_stop() {
1012                return VisitResult::Stop;
1013            }
1014        }
1015        Statement::FunctionDeclaration(node) => {
1016            for s in node.body.body.iter_mut() {
1017                if walk_statement_mut(v, s).is_stop() {
1018                    return VisitResult::Stop;
1019                }
1020            }
1021        }
1022        Statement::ClassDeclaration(node) => {
1023            if let Some(ref mut sc) = node.super_class {
1024                if walk_expression_mut(v, sc).is_stop() {
1025                    return VisitResult::Stop;
1026                }
1027            }
1028        }
1029        Statement::WithStatement(node) => {
1030            if walk_expression_mut(v, &mut node.object).is_stop() {
1031                return VisitResult::Stop;
1032            }
1033            if walk_statement_mut(v, &mut node.body).is_stop() {
1034                return VisitResult::Stop;
1035            }
1036        }
1037        Statement::ExportNamedDeclaration(node) => {
1038            if let Some(ref mut decl) = node.declaration {
1039                if walk_declaration_mut(v, decl).is_stop() {
1040                    return VisitResult::Stop;
1041                }
1042            }
1043        }
1044        Statement::ExportDefaultDeclaration(node) => {
1045            if walk_export_default_decl_mut(v, &mut node.declaration).is_stop() {
1046                return VisitResult::Stop;
1047            }
1048        }
1049        // No runtime expressions to traverse
1050        Statement::BreakStatement(_)
1051        | Statement::ContinueStatement(_)
1052        | Statement::EmptyStatement(_)
1053        | Statement::DebuggerStatement(_)
1054        | Statement::ImportDeclaration(_)
1055        | Statement::ExportAllDeclaration(_)
1056        | Statement::TSTypeAliasDeclaration(_)
1057        | Statement::TSInterfaceDeclaration(_)
1058        | Statement::TSEnumDeclaration(_)
1059        | Statement::TSModuleDeclaration(_)
1060        | Statement::TSDeclareFunction(_)
1061        | Statement::TypeAlias(_)
1062        | Statement::OpaqueType(_)
1063        | Statement::InterfaceDeclaration(_)
1064        | Statement::DeclareVariable(_)
1065        | Statement::DeclareFunction(_)
1066        | Statement::DeclareClass(_)
1067        | Statement::DeclareModule(_)
1068        | Statement::DeclareModuleExports(_)
1069        | Statement::DeclareExportDeclaration(_)
1070        | Statement::DeclareExportAllDeclaration(_)
1071        | Statement::DeclareInterface(_)
1072        | Statement::DeclareTypeAlias(_)
1073        | Statement::DeclareOpaqueType(_)
1074        | Statement::EnumDeclaration(_)
1075        // Unmodeled raw node: opaque, no compilable children to traverse.
1076        | Statement::Unknown(_) => {}
1077    }
1078    VisitResult::Continue
1079}
1080
1081/// Walk an expression mutably, calling visitor hooks and recursing into children.
1082pub fn walk_expression_mut(v: &mut impl MutVisitor, expr: &mut Expression) -> VisitResult {
1083    if v.visit_expression(expr).is_stop() {
1084        return VisitResult::Stop;
1085    }
1086    match expr {
1087        Expression::Identifier(node) => {
1088            if v.visit_identifier(node).is_stop() {
1089                return VisitResult::Stop;
1090            }
1091        }
1092        Expression::CallExpression(node) => {
1093            if walk_expression_mut(v, &mut node.callee).is_stop() {
1094                return VisitResult::Stop;
1095            }
1096            for arg in node.arguments.iter_mut() {
1097                if walk_expression_mut(v, arg).is_stop() {
1098                    return VisitResult::Stop;
1099                }
1100            }
1101        }
1102        Expression::MemberExpression(node) => {
1103            if walk_expression_mut(v, &mut node.object).is_stop() {
1104                return VisitResult::Stop;
1105            }
1106            if node.computed {
1107                if walk_expression_mut(v, &mut node.property).is_stop() {
1108                    return VisitResult::Stop;
1109                }
1110            }
1111        }
1112        Expression::OptionalCallExpression(node) => {
1113            if walk_expression_mut(v, &mut node.callee).is_stop() {
1114                return VisitResult::Stop;
1115            }
1116            for arg in node.arguments.iter_mut() {
1117                if walk_expression_mut(v, arg).is_stop() {
1118                    return VisitResult::Stop;
1119                }
1120            }
1121        }
1122        Expression::OptionalMemberExpression(node) => {
1123            if walk_expression_mut(v, &mut node.object).is_stop() {
1124                return VisitResult::Stop;
1125            }
1126            if node.computed {
1127                if walk_expression_mut(v, &mut node.property).is_stop() {
1128                    return VisitResult::Stop;
1129                }
1130            }
1131        }
1132        Expression::BinaryExpression(node) => {
1133            if walk_expression_mut(v, &mut node.left).is_stop() {
1134                return VisitResult::Stop;
1135            }
1136            if walk_expression_mut(v, &mut node.right).is_stop() {
1137                return VisitResult::Stop;
1138            }
1139        }
1140        Expression::LogicalExpression(node) => {
1141            if walk_expression_mut(v, &mut node.left).is_stop() {
1142                return VisitResult::Stop;
1143            }
1144            if walk_expression_mut(v, &mut node.right).is_stop() {
1145                return VisitResult::Stop;
1146            }
1147        }
1148        Expression::UnaryExpression(node) => {
1149            if walk_expression_mut(v, &mut node.argument).is_stop() {
1150                return VisitResult::Stop;
1151            }
1152        }
1153        Expression::UpdateExpression(node) => {
1154            if walk_expression_mut(v, &mut node.argument).is_stop() {
1155                return VisitResult::Stop;
1156            }
1157        }
1158        Expression::ConditionalExpression(node) => {
1159            if walk_expression_mut(v, &mut node.test).is_stop() {
1160                return VisitResult::Stop;
1161            }
1162            if walk_expression_mut(v, &mut node.consequent).is_stop() {
1163                return VisitResult::Stop;
1164            }
1165            if walk_expression_mut(v, &mut node.alternate).is_stop() {
1166                return VisitResult::Stop;
1167            }
1168        }
1169        Expression::AssignmentExpression(node) => {
1170            if walk_expression_mut(v, &mut node.right).is_stop() {
1171                return VisitResult::Stop;
1172            }
1173        }
1174        Expression::SequenceExpression(node) => {
1175            for e in node.expressions.iter_mut() {
1176                if walk_expression_mut(v, e).is_stop() {
1177                    return VisitResult::Stop;
1178                }
1179            }
1180        }
1181        Expression::ArrowFunctionExpression(node) => match node.body.as_mut() {
1182            ArrowFunctionBody::BlockStatement(block) => {
1183                for s in block.body.iter_mut() {
1184                    if walk_statement_mut(v, s).is_stop() {
1185                        return VisitResult::Stop;
1186                    }
1187                }
1188            }
1189            ArrowFunctionBody::Expression(e) => {
1190                if walk_expression_mut(v, e).is_stop() {
1191                    return VisitResult::Stop;
1192                }
1193            }
1194        },
1195        Expression::FunctionExpression(node) => {
1196            for s in node.body.body.iter_mut() {
1197                if walk_statement_mut(v, s).is_stop() {
1198                    return VisitResult::Stop;
1199                }
1200            }
1201        }
1202        Expression::ObjectExpression(node) => {
1203            for prop in node.properties.iter_mut() {
1204                match prop {
1205                    ObjectExpressionProperty::ObjectProperty(p) => {
1206                        if p.computed {
1207                            if walk_expression_mut(v, &mut p.key).is_stop() {
1208                                return VisitResult::Stop;
1209                            }
1210                        }
1211                        if walk_expression_mut(v, &mut p.value).is_stop() {
1212                            return VisitResult::Stop;
1213                        }
1214                    }
1215                    ObjectExpressionProperty::ObjectMethod(m) => {
1216                        for s in m.body.body.iter_mut() {
1217                            if walk_statement_mut(v, s).is_stop() {
1218                                return VisitResult::Stop;
1219                            }
1220                        }
1221                    }
1222                    ObjectExpressionProperty::SpreadElement(s) => {
1223                        if walk_expression_mut(v, &mut s.argument).is_stop() {
1224                            return VisitResult::Stop;
1225                        }
1226                    }
1227                }
1228            }
1229        }
1230        Expression::ArrayExpression(node) => {
1231            for elem in node.elements.iter_mut().flatten() {
1232                if walk_expression_mut(v, elem).is_stop() {
1233                    return VisitResult::Stop;
1234                }
1235            }
1236        }
1237        Expression::NewExpression(node) => {
1238            if walk_expression_mut(v, &mut node.callee).is_stop() {
1239                return VisitResult::Stop;
1240            }
1241            for arg in node.arguments.iter_mut() {
1242                if walk_expression_mut(v, arg).is_stop() {
1243                    return VisitResult::Stop;
1244                }
1245            }
1246        }
1247        Expression::TemplateLiteral(node) => {
1248            for e in node.expressions.iter_mut() {
1249                if walk_expression_mut(v, e).is_stop() {
1250                    return VisitResult::Stop;
1251                }
1252            }
1253        }
1254        Expression::TaggedTemplateExpression(node) => {
1255            if walk_expression_mut(v, &mut node.tag).is_stop() {
1256                return VisitResult::Stop;
1257            }
1258            for e in node.quasi.expressions.iter_mut() {
1259                if walk_expression_mut(v, e).is_stop() {
1260                    return VisitResult::Stop;
1261                }
1262            }
1263        }
1264        Expression::AwaitExpression(node) => {
1265            if walk_expression_mut(v, &mut node.argument).is_stop() {
1266                return VisitResult::Stop;
1267            }
1268        }
1269        Expression::YieldExpression(node) => {
1270            if let Some(ref mut arg) = node.argument {
1271                if walk_expression_mut(v, arg).is_stop() {
1272                    return VisitResult::Stop;
1273                }
1274            }
1275        }
1276        Expression::SpreadElement(node) => {
1277            if walk_expression_mut(v, &mut node.argument).is_stop() {
1278                return VisitResult::Stop;
1279            }
1280        }
1281        Expression::ParenthesizedExpression(node) => {
1282            if walk_expression_mut(v, &mut node.expression).is_stop() {
1283                return VisitResult::Stop;
1284            }
1285        }
1286        Expression::AssignmentPattern(node) => {
1287            if walk_expression_mut(v, &mut node.right).is_stop() {
1288                return VisitResult::Stop;
1289            }
1290        }
1291        Expression::ClassExpression(node) => {
1292            if let Some(ref mut sc) = node.super_class {
1293                if walk_expression_mut(v, sc).is_stop() {
1294                    return VisitResult::Stop;
1295                }
1296            }
1297        }
1298        Expression::JSXElement(node) => {
1299            if walk_jsx_mut(v, &mut node.opening_element.attributes, &mut node.children).is_stop() {
1300                return VisitResult::Stop;
1301            }
1302        }
1303        Expression::JSXFragment(node) => {
1304            if walk_jsx_children_mut(v, &mut node.children).is_stop() {
1305                return VisitResult::Stop;
1306            }
1307        }
1308        // TS/Flow wrappers — traverse inner expression
1309        Expression::TSAsExpression(node) => {
1310            if walk_expression_mut(v, &mut node.expression).is_stop() {
1311                return VisitResult::Stop;
1312            }
1313        }
1314        Expression::TSSatisfiesExpression(node) => {
1315            if walk_expression_mut(v, &mut node.expression).is_stop() {
1316                return VisitResult::Stop;
1317            }
1318        }
1319        Expression::TSNonNullExpression(node) => {
1320            if walk_expression_mut(v, &mut node.expression).is_stop() {
1321                return VisitResult::Stop;
1322            }
1323        }
1324        Expression::TSTypeAssertion(node) => {
1325            if walk_expression_mut(v, &mut node.expression).is_stop() {
1326                return VisitResult::Stop;
1327            }
1328        }
1329        Expression::TSInstantiationExpression(node) => {
1330            if walk_expression_mut(v, &mut node.expression).is_stop() {
1331                return VisitResult::Stop;
1332            }
1333        }
1334        Expression::TypeCastExpression(node) => {
1335            if walk_expression_mut(v, &mut node.expression).is_stop() {
1336                return VisitResult::Stop;
1337            }
1338        }
1339        // Leaf nodes
1340        Expression::StringLiteral(_)
1341        | Expression::NumericLiteral(_)
1342        | Expression::BooleanLiteral(_)
1343        | Expression::NullLiteral(_)
1344        | Expression::BigIntLiteral(_)
1345        | Expression::RegExpLiteral(_)
1346        | Expression::MetaProperty(_)
1347        | Expression::PrivateName(_)
1348        | Expression::Super(_)
1349        | Expression::Import(_)
1350        | Expression::ThisExpression(_) => {}
1351    }
1352    VisitResult::Continue
1353}
1354
1355fn walk_jsx_mut(
1356    v: &mut impl MutVisitor,
1357    attrs: &mut [crate::jsx::JSXAttributeItem],
1358    children: &mut [crate::jsx::JSXChild],
1359) -> VisitResult {
1360    for attr in attrs.iter_mut() {
1361        match attr {
1362            crate::jsx::JSXAttributeItem::JSXAttribute(a) => {
1363                if let Some(ref mut val) = a.value {
1364                    match val {
1365                        crate::jsx::JSXAttributeValue::JSXExpressionContainer(c) => {
1366                            if let crate::jsx::JSXExpressionContainerExpr::Expression(ref mut e) =
1367                                c.expression
1368                            {
1369                                if walk_expression_mut(v, e).is_stop() {
1370                                    return VisitResult::Stop;
1371                                }
1372                            }
1373                        }
1374                        _ => {}
1375                    }
1376                }
1377            }
1378            crate::jsx::JSXAttributeItem::JSXSpreadAttribute(s) => {
1379                if walk_expression_mut(v, &mut s.argument).is_stop() {
1380                    return VisitResult::Stop;
1381                }
1382            }
1383        }
1384    }
1385    walk_jsx_children_mut(v, children)
1386}
1387
1388fn walk_jsx_children_mut(
1389    v: &mut impl MutVisitor,
1390    children: &mut [crate::jsx::JSXChild],
1391) -> VisitResult {
1392    for child in children.iter_mut() {
1393        match child {
1394            crate::jsx::JSXChild::JSXElement(el) => {
1395                if walk_jsx_mut(v, &mut el.opening_element.attributes, &mut el.children).is_stop() {
1396                    return VisitResult::Stop;
1397                }
1398            }
1399            crate::jsx::JSXChild::JSXFragment(f) => {
1400                if walk_jsx_children_mut(v, &mut f.children).is_stop() {
1401                    return VisitResult::Stop;
1402                }
1403            }
1404            crate::jsx::JSXChild::JSXExpressionContainer(c) => {
1405                if let crate::jsx::JSXExpressionContainerExpr::Expression(ref mut e) = c.expression
1406                {
1407                    if walk_expression_mut(v, e).is_stop() {
1408                        return VisitResult::Stop;
1409                    }
1410                }
1411            }
1412            crate::jsx::JSXChild::JSXSpreadChild(s) => {
1413                if walk_expression_mut(v, &mut s.expression).is_stop() {
1414                    return VisitResult::Stop;
1415                }
1416            }
1417            _ => {}
1418        }
1419    }
1420    VisitResult::Continue
1421}
1422
1423// ---- Private helper walk-mut functions ----
1424
1425fn walk_variable_declaration_mut(
1426    v: &mut impl MutVisitor,
1427    decl: &mut VariableDeclaration,
1428) -> VisitResult {
1429    for declarator in decl.declarations.iter_mut() {
1430        if let Some(ref mut init) = declarator.init {
1431            if walk_expression_mut(v, init).is_stop() {
1432                return VisitResult::Stop;
1433            }
1434        }
1435    }
1436    VisitResult::Continue
1437}
1438
1439fn walk_declaration_mut(v: &mut impl MutVisitor, decl: &mut Declaration) -> VisitResult {
1440    match decl {
1441        Declaration::FunctionDeclaration(node) => {
1442            for s in node.body.body.iter_mut() {
1443                if walk_statement_mut(v, s).is_stop() {
1444                    return VisitResult::Stop;
1445                }
1446            }
1447        }
1448        Declaration::VariableDeclaration(node) => {
1449            if walk_variable_declaration_mut(v, node).is_stop() {
1450                return VisitResult::Stop;
1451            }
1452        }
1453        Declaration::ClassDeclaration(node) => {
1454            if let Some(ref mut sc) = node.super_class {
1455                if walk_expression_mut(v, sc).is_stop() {
1456                    return VisitResult::Stop;
1457                }
1458            }
1459        }
1460        _ => {}
1461    }
1462    VisitResult::Continue
1463}
1464
1465fn walk_export_default_decl_mut(
1466    v: &mut impl MutVisitor,
1467    decl: &mut ExportDefaultDecl,
1468) -> VisitResult {
1469    match decl {
1470        ExportDefaultDecl::FunctionDeclaration(node) => {
1471            for s in node.body.body.iter_mut() {
1472                if walk_statement_mut(v, s).is_stop() {
1473                    return VisitResult::Stop;
1474                }
1475            }
1476        }
1477        ExportDefaultDecl::Expression(expr) => {
1478            if walk_expression_mut(v, expr).is_stop() {
1479                return VisitResult::Stop;
1480            }
1481        }
1482        ExportDefaultDecl::ClassDeclaration(node) => {
1483            if let Some(ref mut sc) = node.super_class {
1484                if walk_expression_mut(v, sc).is_stop() {
1485                    return VisitResult::Stop;
1486                }
1487            }
1488        }
1489        ExportDefaultDecl::EnumDeclaration(_) => {
1490            // Flow enum declarations are opaque — nothing to walk
1491        }
1492    }
1493    VisitResult::Continue
1494}