Skip to main content

oxc_traverse/ast_operations/
gather_node_parts.rs

1//! Gather node parts trait.
2//!
3//! Ported from: <https://github.com/babel/babel/blob/419644f27c5c59deb19e71aaabd417a3bc5483ca/packages/babel-traverse/src/scope/index.ts>
4//!
5//! This trait is used to gather all the parts of a node that are identifiers.
6
7use oxc_ast::ast::*;
8use oxc_ecmascript::BoundNames;
9
10use super::to_identifier;
11
12pub fn get_var_name_from_node<'a, N: GatherNodeParts<'a>>(node: &N) -> String {
13    let mut name = String::new();
14    node.gather(&mut |mut part| {
15        if name.is_empty() {
16            part = part.trim_start_matches('_');
17        } else {
18            name.push('$');
19        }
20        name.push_str(part);
21    });
22
23    if name.is_empty() {
24        name = "ref".to_string();
25    } else if name.len() > 20 {
26        // Truncate to ~20 bytes, respecting UTF-8 char boundaries.
27        // This diverges slightly from Babel (which limits to 20 chars),
28        // but the goal is just to avoid overly long variable names.
29        let bytes = name.as_bytes();
30        if bytes[19] < 0x80 {
31            // 20th byte is ASCII, safe to truncate here
32            name.truncate(20);
33        } else {
34            // Byte 19 may be in the middle of a multi-byte char.
35            // Walk back to find the start byte (continuation bytes are 10xxxxxx).
36            let mut truncate_at = 19;
37            while truncate_at > 0 && (bytes[truncate_at] & 0xC0) == 0x80 {
38                truncate_at -= 1;
39            }
40            name.truncate(truncate_at);
41        }
42    }
43
44    to_identifier(name)
45}
46
47pub trait GatherNodeParts<'a> {
48    fn gather<F: FnMut(&str)>(&self, f: &mut F);
49}
50
51// -------------------- ModuleDeclaration --------------------
52impl<'a> GatherNodeParts<'a> for ImportDeclaration<'a> {
53    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
54        self.source.gather(f);
55    }
56}
57
58impl<'a> GatherNodeParts<'a> for ExportAllDeclaration<'a> {
59    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
60        self.source.gather(f);
61    }
62}
63
64impl<'a> GatherNodeParts<'a> for ExportNamedDeclaration<'a> {
65    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
66        if let Some(source) = &self.source {
67            source.gather(f);
68        } else if let Some(declaration) = &self.declaration {
69            declaration.gather(f);
70        } else {
71            for specifier in &self.specifiers {
72                specifier.gather(f);
73            }
74        }
75    }
76}
77
78impl<'a> GatherNodeParts<'a> for ExportDefaultDeclaration<'a> {
79    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
80        self.declaration.gather(f);
81    }
82}
83
84impl<'a> GatherNodeParts<'a> for ExportDefaultDeclarationKind<'a> {
85    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
86        match self {
87            ExportDefaultDeclarationKind::FunctionDeclaration(decl) => decl.gather(f),
88            ExportDefaultDeclarationKind::ClassDeclaration(decl) => decl.gather(f),
89            ExportDefaultDeclarationKind::TSInterfaceDeclaration(_) => {}
90            match_expression!(ExportDefaultDeclarationKind) => self.to_expression().gather(f),
91        }
92    }
93}
94
95impl<'a> GatherNodeParts<'a> for ExportSpecifier<'a> {
96    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
97        match &self.local {
98            ModuleExportName::IdentifierName(ident) => ident.gather(f),
99            ModuleExportName::IdentifierReference(ident) => ident.gather(f),
100            ModuleExportName::StringLiteral(lit) => lit.gather(f),
101        }
102    }
103}
104
105impl<'a> GatherNodeParts<'a> for ModuleExportName<'a> {
106    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
107        match self {
108            ModuleExportName::IdentifierName(ident) => ident.gather(f),
109            ModuleExportName::IdentifierReference(ident) => ident.gather(f),
110            ModuleExportName::StringLiteral(lit) => lit.gather(f),
111        }
112    }
113}
114
115impl<'a> GatherNodeParts<'a> for ImportSpecifier<'a> {
116    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
117        self.local.gather(f);
118    }
119}
120
121impl<'a> GatherNodeParts<'a> for ImportDefaultSpecifier<'a> {
122    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
123        self.local.gather(f);
124    }
125}
126
127impl<'a> GatherNodeParts<'a> for ImportNamespaceSpecifier<'a> {
128    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
129        self.local.gather(f);
130    }
131}
132
133// -------------------- Declaration --------------------
134
135impl<'a> GatherNodeParts<'a> for Declaration<'a> {
136    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
137        match self {
138            Self::FunctionDeclaration(decl) => decl.gather(f),
139            Self::ClassDeclaration(decl) => decl.gather(f),
140            _ => (),
141        }
142    }
143}
144
145// -------------------- Function --------------------
146
147impl<'a> GatherNodeParts<'a> for Function<'a> {
148    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
149        if let Some(id) = &self.id {
150            id.gather(f);
151        }
152    }
153}
154
155impl<'a> GatherNodeParts<'a> for BindingRestElement<'a> {
156    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
157        self.argument.gather(f);
158    }
159}
160
161// -------------------- BindingPattern --------------------
162
163impl<'a> GatherNodeParts<'a> for VariableDeclarator<'a> {
164    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
165        self.id.gather(f);
166    }
167}
168
169impl<'a> GatherNodeParts<'a> for BindingPattern<'a> {
170    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
171        self.bound_names(&mut |id| f(id.name.as_str()));
172    }
173}
174
175impl<'a> GatherNodeParts<'a> for ObjectPattern<'a> {
176    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
177        self.bound_names(&mut |id| f(id.name.as_str()));
178    }
179}
180
181impl<'a> GatherNodeParts<'a> for ArrayPattern<'a> {
182    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
183        self.bound_names(&mut |id| f(id.name.as_str()));
184    }
185}
186
187impl<'a> GatherNodeParts<'a> for AssignmentPattern<'a> {
188    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
189        self.bound_names(&mut |id| f(id.name.as_str()));
190    }
191}
192
193// -------------------- Expression --------------------
194
195impl<'a> GatherNodeParts<'a> for Expression<'a> {
196    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
197        match self {
198            match_member_expression!(Self) => self.to_member_expression().gather(f),
199            Self::Identifier(ident) => ident.gather(f),
200            Self::CallExpression(expr) => expr.gather(f),
201            Self::NewExpression(expr) => expr.gather(f),
202            Self::ObjectExpression(expr) => expr.gather(f),
203            Self::ThisExpression(expr) => expr.gather(f),
204            Self::Super(expr) => expr.gather(f),
205            Self::ImportExpression(expr) => expr.gather(f),
206            Self::YieldExpression(expr) => expr.gather(f),
207            Self::AwaitExpression(expr) => expr.gather(f),
208            Self::AssignmentExpression(expr) => expr.gather(f),
209            Self::FunctionExpression(expr) => expr.gather(f),
210            Self::ClassExpression(expr) => expr.gather(f),
211            Self::ParenthesizedExpression(expr) => expr.gather(f),
212            Self::UnaryExpression(expr) => expr.gather(f),
213            Self::UpdateExpression(expr) => expr.gather(f),
214            Self::ChainExpression(expr) => expr.gather(f),
215            Self::MetaProperty(expr) => expr.gather(f),
216            Self::JSXElement(expr) => expr.gather(f),
217            Self::JSXFragment(expr) => expr.gather(f),
218            Self::StringLiteral(expr) => expr.gather(f),
219            Self::NumericLiteral(expr) => expr.gather(f),
220            Self::BooleanLiteral(expr) => expr.gather(f),
221            Self::BigIntLiteral(expr) => expr.gather(f),
222            _ => (),
223        }
224    }
225}
226
227impl<'a> GatherNodeParts<'a> for ChainExpression<'a> {
228    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
229        self.expression.gather(f);
230    }
231}
232
233impl<'a> GatherNodeParts<'a> for ChainElement<'a> {
234    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
235        match self {
236            ChainElement::CallExpression(expr) => expr.gather(f),
237            ChainElement::TSNonNullExpression(expr) => expr.expression.gather(f),
238            expr @ match_member_expression!(Self) => expr.to_member_expression().gather(f),
239        }
240    }
241}
242
243impl<'a> GatherNodeParts<'a> for MemberExpression<'a> {
244    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
245        match self {
246            MemberExpression::ComputedMemberExpression(expr) => {
247                expr.gather(f);
248            }
249            MemberExpression::StaticMemberExpression(expr) => {
250                expr.gather(f);
251            }
252            MemberExpression::PrivateFieldExpression(expr) => {
253                expr.gather(f);
254            }
255        }
256    }
257}
258
259impl<'a> GatherNodeParts<'a> for ComputedMemberExpression<'a> {
260    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
261        self.object.gather(f);
262        self.expression.gather(f);
263    }
264}
265
266impl<'a> GatherNodeParts<'a> for StaticMemberExpression<'a> {
267    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
268        self.object.gather(f);
269        self.property.gather(f);
270    }
271}
272
273impl<'a> GatherNodeParts<'a> for PrivateFieldExpression<'a> {
274    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
275        self.object.gather(f);
276        self.field.gather(f);
277    }
278}
279
280impl<'a> GatherNodeParts<'a> for CallExpression<'a> {
281    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
282        self.callee.gather(f);
283    }
284}
285
286impl<'a> GatherNodeParts<'a> for NewExpression<'a> {
287    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
288        self.callee.gather(f);
289    }
290}
291
292impl<'a> GatherNodeParts<'a> for ObjectExpression<'a> {
293    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
294        for prop in &self.properties {
295            prop.gather(f);
296        }
297    }
298}
299
300impl GatherNodeParts<'_> for ThisExpression {
301    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
302        f("this");
303    }
304}
305
306impl GatherNodeParts<'_> for Super {
307    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
308        f("super");
309    }
310}
311
312impl<'a> GatherNodeParts<'a> for ImportExpression<'a> {
313    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
314        f("import");
315    }
316}
317
318impl<'a> GatherNodeParts<'a> for YieldExpression<'a> {
319    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
320        f("yield");
321        if let Some(argument) = &self.argument {
322            argument.gather(f);
323        }
324    }
325}
326
327impl<'a> GatherNodeParts<'a> for AwaitExpression<'a> {
328    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
329        f("await");
330        self.argument.gather(f);
331    }
332}
333
334impl<'a> GatherNodeParts<'a> for AssignmentExpression<'a> {
335    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
336        self.left.gather(f);
337    }
338}
339
340impl<'a> GatherNodeParts<'a> for ParenthesizedExpression<'a> {
341    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
342        self.expression.gather(f);
343    }
344}
345
346impl<'a> GatherNodeParts<'a> for UnaryExpression<'a> {
347    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
348        self.argument.gather(f);
349    }
350}
351
352impl<'a> GatherNodeParts<'a> for UpdateExpression<'a> {
353    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
354        self.argument.gather(f);
355    }
356}
357
358impl<'a> GatherNodeParts<'a> for MetaProperty<'a> {
359    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
360        self.meta.gather(f);
361        self.property.gather(f);
362    }
363}
364
365// -------------------- AssignmentTarget --------------------
366impl<'a> GatherNodeParts<'a> for AssignmentTarget<'a> {
367    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
368        match self {
369            match_simple_assignment_target!(Self) => {
370                self.to_simple_assignment_target().gather(f);
371            }
372            match_assignment_target_pattern!(Self) => {}
373        }
374    }
375}
376
377impl<'a> GatherNodeParts<'a> for SimpleAssignmentTarget<'a> {
378    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
379        match self {
380            Self::AssignmentTargetIdentifier(ident) => ident.gather(f),
381            match_member_expression!(Self) => self.to_member_expression().gather(f),
382            _ => {}
383        }
384    }
385}
386
387// -------------------- Classes --------------------
388
389impl<'a> GatherNodeParts<'a> for Class<'a> {
390    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
391        if let Some(id) = &self.id {
392            id.gather(f);
393        }
394    }
395}
396
397impl<'a> GatherNodeParts<'a> for ClassElement<'a> {
398    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
399        match self {
400            ClassElement::PropertyDefinition(def) => def.gather(f),
401            ClassElement::MethodDefinition(def) => def.gather(f),
402            ClassElement::AccessorProperty(def) => def.gather(f),
403            _ => (),
404        }
405    }
406}
407
408impl<'a> GatherNodeParts<'a> for PropertyDefinition<'a> {
409    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
410        self.key.gather(f);
411    }
412}
413
414impl<'a> GatherNodeParts<'a> for MethodDefinition<'a> {
415    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
416        self.key.gather(f);
417    }
418}
419
420impl<'a> GatherNodeParts<'a> for AccessorProperty<'a> {
421    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
422        self.key.gather(f);
423    }
424}
425
426// -------------------- Objects --------------------
427
428impl<'a> GatherNodeParts<'a> for ObjectPropertyKind<'a> {
429    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
430        match self {
431            ObjectPropertyKind::ObjectProperty(prop) => prop.gather(f),
432            ObjectPropertyKind::SpreadProperty(prop) => prop.gather(f),
433        }
434    }
435}
436
437impl<'a> GatherNodeParts<'a> for ObjectProperty<'a> {
438    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
439        self.key.gather(f);
440    }
441}
442
443impl<'a> GatherNodeParts<'a> for PropertyKey<'a> {
444    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
445        match self {
446            PropertyKey::StaticIdentifier(ident) => ident.gather(f),
447            PropertyKey::PrivateIdentifier(ident) => ident.gather(f),
448            match_expression!(Self) => self.to_expression().gather(f),
449        }
450    }
451}
452
453impl<'a> GatherNodeParts<'a> for SpreadElement<'a> {
454    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
455        self.argument.gather(f);
456    }
457}
458
459// -------------------- Identifiers --------------------
460
461impl<'a> GatherNodeParts<'a> for BindingIdentifier<'a> {
462    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
463        f(self.name.as_str());
464    }
465}
466
467impl<'a> GatherNodeParts<'a> for IdentifierReference<'a> {
468    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
469        f(self.name.as_str());
470    }
471}
472
473impl<'a> GatherNodeParts<'a> for IdentifierName<'a> {
474    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
475        f(self.name.as_str());
476    }
477}
478
479impl<'a> GatherNodeParts<'a> for PrivateIdentifier<'a> {
480    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
481        f(self.name.as_str());
482    }
483}
484
485// -------------------- Literals --------------------
486
487impl<'a> GatherNodeParts<'a> for StringLiteral<'a> {
488    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
489        f(self.value.as_str());
490    }
491}
492
493impl<'a> GatherNodeParts<'a> for NumericLiteral<'a> {
494    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
495        f(&self.raw_str());
496    }
497}
498
499impl GatherNodeParts<'_> for BooleanLiteral {
500    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
501        if self.value {
502            f("true");
503        } else {
504            f("false");
505        }
506    }
507}
508
509impl<'a> GatherNodeParts<'a> for BigIntLiteral<'a> {
510    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
511        f(self.value.as_str());
512    }
513}
514
515// -------------------- JSX --------------------
516
517impl<'a> GatherNodeParts<'a> for JSXElement<'a> {
518    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
519        self.opening_element.gather(f);
520    }
521}
522
523impl<'a> GatherNodeParts<'a> for JSXFragment<'a> {
524    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
525        self.opening_fragment.gather(f);
526    }
527}
528
529impl<'a> GatherNodeParts<'a> for JSXOpeningElement<'a> {
530    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
531        self.name.gather(f);
532    }
533}
534
535impl GatherNodeParts<'_> for JSXOpeningFragment {
536    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
537        f("Fragment");
538    }
539}
540
541impl<'a> GatherNodeParts<'a> for JSXElementName<'a> {
542    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
543        match self {
544            JSXElementName::Identifier(ident) => ident.gather(f),
545            JSXElementName::IdentifierReference(ident) => ident.gather(f),
546            JSXElementName::NamespacedName(ns) => ns.gather(f),
547            JSXElementName::MemberExpression(expr) => expr.gather(f),
548            JSXElementName::ThisExpression(expr) => expr.gather(f),
549        }
550    }
551}
552
553impl<'a> GatherNodeParts<'a> for JSXNamespacedName<'a> {
554    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
555        self.namespace.gather(f);
556        self.name.gather(f);
557    }
558}
559
560impl<'a> GatherNodeParts<'a> for JSXMemberExpression<'a> {
561    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
562        self.object.gather(f);
563        self.property.gather(f);
564    }
565}
566
567impl<'a> GatherNodeParts<'a> for JSXMemberExpressionObject<'a> {
568    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
569        match self {
570            JSXMemberExpressionObject::IdentifierReference(ident) => ident.gather(f),
571            JSXMemberExpressionObject::MemberExpression(expr) => expr.gather(f),
572            JSXMemberExpressionObject::ThisExpression(expr) => expr.gather(f),
573        }
574    }
575}
576
577impl<'a> GatherNodeParts<'a> for JSXIdentifier<'a> {
578    fn gather<F: FnMut(&str)>(&self, f: &mut F) {
579        f(self.name.as_str());
580    }
581}
582
583#[cfg(test)]
584mod tests {
585    use super::*;
586
587    /// Test wrapper that implements GatherNodeParts for testing purposes
588    struct TestNode<'a>(&'a str);
589
590    impl<'a> GatherNodeParts<'a> for TestNode<'a> {
591        fn gather<F: FnMut(&str)>(&self, f: &mut F) {
592            f(self.0);
593        }
594    }
595
596    #[test]
597    fn test_get_var_name_truncation_limits_to_approximately_20_bytes() {
598        // ASCII: 20 bytes - no truncation
599        let node = TestNode("abcdefghijklmnopqrst");
600        assert_eq!(get_var_name_from_node(&node), "abcdefghijklmnopqrst");
601
602        // ASCII: 21 bytes -> 20 bytes
603        let node = TestNode("abcdefghijklmnopqrstu");
604        assert_eq!(get_var_name_from_node(&node), "abcdefghijklmnopqrst");
605
606        // 2-byte UTF-8 (Greek): 21 chars (42 bytes) -> 9 chars (18 bytes)
607        let node = TestNode("αβγδεζηθικλμνξοπρστυφ");
608        assert_eq!(get_var_name_from_node(&node), "αβγδεζηθι");
609
610        // 3-byte UTF-8 (Korean): 10 chars (30 bytes) -> 6 chars (18 bytes)
611        let node = TestNode("가나다라마바사아자차");
612        assert_eq!(get_var_name_from_node(&node), "가나다라마바");
613
614        // 4-byte UTF-8 (CJK Ext B): 6 chars (24 bytes) -> 4 chars (16 bytes)
615        let node = TestNode("𠀀𠀁𠀂𠀃𠀄𠀅");
616        assert_eq!(get_var_name_from_node(&node), "𠀀𠀁𠀂𠀃");
617
618        // Mixed ASCII + Greek: 21 bytes -> 19 bytes
619        let node = TestNode("test_αβγδεζηθ");
620        assert_eq!(get_var_name_from_node(&node), "test_αβγδεζη");
621
622        // Short string - no truncation
623        let node = TestNode("short");
624        assert_eq!(get_var_name_from_node(&node), "short");
625
626        // Exactly 20 bytes - no truncation
627        let node = TestNode("αβγδεζηθικ");
628        assert_eq!(get_var_name_from_node(&node), "αβγδεζηθικ");
629    }
630
631    #[test]
632    fn test_get_var_name_empty_returns_ref() {
633        let node = TestNode("");
634        let result = get_var_name_from_node(&node);
635        assert_eq!(result, "ref");
636    }
637
638    #[test]
639    fn test_get_var_name_strips_leading_underscores() {
640        let node = TestNode("___foo");
641        let result = get_var_name_from_node(&node);
642        assert_eq!(result, "foo");
643    }
644}