Skip to main content

tsz_parser/syntax/
transform_utils.rs

1//! Transform utilities for syntax analysis.
2//!
3//! Common functions used by ES5 transformations.
4
5use crate::parser::{NodeArena, NodeIndex, node::NodeAccess, syntax_kind_ext};
6use tsz_scanner::SyntaxKind;
7
8#[derive(Clone, Copy)]
9enum ReferenceTarget {
10    Arguments,
11    This,
12}
13
14impl ReferenceTarget {
15    const fn identifier_name(self) -> &'static str {
16        match self {
17            Self::Arguments => "arguments",
18            Self::This => "this",
19        }
20    }
21
22    const fn include_keyword_check(self) -> bool {
23        matches!(self, Self::This)
24    }
25}
26
27/// Check if an AST node contains a reference to `this` or `super`.
28#[must_use]
29pub fn contains_this_reference(arena: &NodeArena, node_idx: NodeIndex) -> bool {
30    contains_target_reference(arena, node_idx, ReferenceTarget::This)
31}
32
33/// Check if a node contains a reference to `arguments`.
34///
35/// This is used to determine if an arrow function needs to capture the parent's
36/// `arguments` object for ES5 downleveling.
37///
38/// Important: Regular functions have their own `arguments`, so we don't recurse
39/// into them. Only arrow functions inherit the parent's `arguments`.
40#[must_use]
41pub fn contains_arguments_reference(arena: &NodeArena, node_idx: NodeIndex) -> bool {
42    contains_target_reference(arena, node_idx, ReferenceTarget::Arguments)
43}
44
45fn contains_target_reference(
46    arena: &NodeArena,
47    node_idx: NodeIndex,
48    target: ReferenceTarget,
49) -> bool {
50    let Some(node) = arena.get(node_idx) else {
51        return false;
52    };
53
54    if target.include_keyword_check()
55        && (node.kind == SyntaxKind::ThisKeyword as u16
56            || node.kind == SyntaxKind::SuperKeyword as u16)
57    {
58        return true;
59    }
60
61    if node.kind == SyntaxKind::Identifier as u16
62        && let Some(identifier) = arena.get_identifier(node)
63    {
64        return identifier.escaped_text == target.identifier_name();
65    }
66
67    target_reference_children(arena, node_idx)
68        .into_iter()
69        .any(|child_idx| contains_target_reference(arena, child_idx, target))
70}
71
72fn target_reference_children(arena: &NodeArena, node_idx: NodeIndex) -> Vec<NodeIndex> {
73    let Some(node) = arena.get(node_idx) else {
74        return Vec::new();
75    };
76
77    match node.kind {
78        kind if kind == syntax_kind_ext::FUNCTION_DECLARATION
79            || kind == syntax_kind_ext::FUNCTION_EXPRESSION =>
80        {
81            Vec::new()
82        }
83        kind if kind == syntax_kind_ext::METHOD_DECLARATION => {
84            if let Some(method) = arena.get_method_decl(node) {
85                Vec::from([method.name])
86            } else {
87                Vec::new()
88            }
89        }
90        kind if kind == syntax_kind_ext::GET_ACCESSOR || kind == syntax_kind_ext::SET_ACCESSOR => {
91            if let Some(accessor) = arena.get_accessor(node) {
92                Vec::from([accessor.name])
93            } else {
94                Vec::new()
95            }
96        }
97        kind if kind == syntax_kind_ext::ARROW_FUNCTION => {
98            if let Some(func) = arena.get_function(node) {
99                let mut children = Vec::new();
100                for &param_idx in &func.parameters.nodes {
101                    let Some(param_node) = arena.get(param_idx) else {
102                        continue;
103                    };
104                    let Some(param) = arena.get_parameter(param_node) else {
105                        continue;
106                    };
107                    if param.initializer.is_some() {
108                        children.push(param.initializer);
109                    }
110                }
111                if func.body.is_some() {
112                    children.push(func.body);
113                }
114                children
115            } else {
116                Vec::new()
117            }
118        }
119        kind if kind == syntax_kind_ext::VARIABLE_STATEMENT
120            || kind == syntax_kind_ext::VARIABLE_DECLARATION_LIST =>
121        {
122            if let Some(var_stmt) = arena.get_variable(node) {
123                var_stmt.declarations.nodes.clone()
124            } else {
125                Vec::new()
126            }
127        }
128        kind if kind == syntax_kind_ext::VARIABLE_DECLARATION => {
129            if let Some(decl) = arena.get_variable_declaration(node) {
130                if decl.initializer.is_none() {
131                    Vec::new()
132                } else {
133                    vec![decl.initializer]
134                }
135            } else {
136                Vec::new()
137            }
138        }
139        _ => arena.get_children(node_idx),
140    }
141}
142
143/// Check if a node is a private identifier (#field)
144#[must_use]
145pub fn is_private_identifier(arena: &NodeArena, name_idx: NodeIndex) -> bool {
146    let Some(node) = arena.get(name_idx) else {
147        return false;
148    };
149    node.kind == SyntaxKind::PrivateIdentifier as u16
150}