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