Skip to main content

oxc_traverse/context/
scoping.rs

1use std::str;
2
3use oxc_allocator::{Allocator, Vec as ArenaVec};
4use oxc_ast::ast::*;
5use oxc_ast_visit::Visit;
6use oxc_semantic::{NodeId, Reference, Scoping};
7use oxc_span::{Ident, SPAN};
8use oxc_syntax::{
9    reference::{ReferenceFlags, ReferenceId},
10    scope::{ScopeFlags, ScopeId},
11    symbol::{SymbolFlags, SymbolId},
12};
13
14use crate::{BoundIdentifier, scopes_collector::ChildScopeCollector};
15
16use super::uid::UidGenerator;
17
18/// Traverse scope context.
19///
20/// Contains the scope tree and symbols table, and provides methods to access them.
21///
22/// `current_scope_id` is the ID of current scope during traversal.
23/// `walk_*` functions update this field when entering/exiting a scope.
24pub struct TraverseScoping<'a> {
25    scoping: Scoping,
26    uid_generator: Option<UidGenerator<'a>>,
27    current_scope_id: ScopeId,
28    current_hoist_scope_id: ScopeId,
29    current_block_scope_id: ScopeId,
30}
31
32// Public methods
33impl<'a> TraverseScoping<'a> {
34    /// Get current scope ID
35    #[inline]
36    pub fn current_scope_id(&self) -> ScopeId {
37        self.current_scope_id
38    }
39
40    /// Get current var hoisting scope ID
41    #[inline]
42    pub(crate) fn current_hoist_scope_id(&self) -> ScopeId {
43        self.current_hoist_scope_id
44    }
45
46    /// Get current block scope ID
47    #[inline]
48    pub(crate) fn current_block_scope_id(&self) -> ScopeId {
49        self.current_block_scope_id
50    }
51
52    /// Get current scope flags
53    #[inline]
54    pub fn current_scope_flags(&self) -> ScopeFlags {
55        self.scoping.scope_flags(self.current_scope_id)
56    }
57
58    /// Get scopes tree
59    #[inline]
60    pub fn scoping(&self) -> &Scoping {
61        &self.scoping
62    }
63
64    /// Get mutable scopes tree
65    #[inline]
66    pub fn scoping_mut(&mut self) -> &mut Scoping {
67        &mut self.scoping
68    }
69
70    /// Get iterator over scopes, starting with current scope and working up
71    pub fn ancestor_scopes(&self) -> impl Iterator<Item = ScopeId> + '_ {
72        self.scoping.scope_ancestors(self.current_scope_id)
73    }
74
75    /// Create new scope as child of provided scope.
76    ///
77    /// `flags` provided are amended to inherit from parent scope's flags.
78    pub fn create_child_scope(&mut self, parent_id: ScopeId, flags: ScopeFlags) -> ScopeId {
79        let flags = self.scoping.get_new_scope_flags(flags, parent_id);
80        self.scoping.add_scope(Some(parent_id), NodeId::DUMMY, flags)
81    }
82
83    /// Create new scope as child of current scope.
84    ///
85    /// `flags` provided are amended to inherit from parent scope's flags.
86    pub fn create_child_scope_of_current(&mut self, flags: ScopeFlags) -> ScopeId {
87        self.create_child_scope(self.current_scope_id, flags)
88    }
89
90    /// Insert a scope into scope tree below a statement.
91    ///
92    /// Statement must be in current scope.
93    /// New scope is created as child of current scope.
94    /// All child scopes of the statement are reassigned to be children of the new scope.
95    ///
96    /// `flags` provided are amended to inherit from parent scope's flags.
97    pub fn insert_scope_below_statement(&mut self, stmt: &Statement, flags: ScopeFlags) -> ScopeId {
98        self.insert_scope_below_statement_from_scope_id(stmt, self.current_scope_id, flags)
99    }
100
101    /// Insert a scope into scope tree below a statement.
102    ///
103    /// Statement must be in provided scope.
104    /// New scope is created as child of the provided scope.
105    /// All child scopes of the statement are reassigned to be children of the new scope.
106    ///
107    /// `flags` provided are amended to inherit from parent scope's flags.
108    pub fn insert_scope_below_statement_from_scope_id(
109        &mut self,
110        stmt: &Statement,
111        scope_id: ScopeId,
112        flags: ScopeFlags,
113    ) -> ScopeId {
114        let mut collector = ChildScopeCollector::new();
115        collector.visit_statement(stmt);
116        self.insert_scope_below(scope_id, &collector.scope_ids, flags)
117    }
118
119    /// Insert a scope into scope tree below an expression.
120    ///
121    /// Expression must be in current scope.
122    /// New scope is created as child of current scope.
123    /// All child scopes of the expression are reassigned to be children of the new scope.
124    ///
125    /// `flags` provided are amended to inherit from parent scope's flags.
126    pub fn insert_scope_below_expression(
127        &mut self,
128        expr: &Expression,
129        flags: ScopeFlags,
130    ) -> ScopeId {
131        let mut collector = ChildScopeCollector::new();
132        collector.visit_expression(expr);
133        self.insert_scope_below(self.current_scope_id, &collector.scope_ids, flags)
134    }
135
136    /// Insert a scope into scope tree below a `Vec` of statements.
137    ///
138    /// Statements must be in current scope.
139    /// New scope is created as child of current scope.
140    /// All child scopes of the statement are reassigned to be children of the new scope.
141    ///
142    /// `flags` provided are amended to inherit from parent scope's flags.
143    pub fn insert_scope_below_statements(
144        &mut self,
145        stmts: &ArenaVec<Statement>,
146        flags: ScopeFlags,
147    ) -> ScopeId {
148        let mut collector = ChildScopeCollector::new();
149        collector.visit_statements(stmts);
150        self.insert_scope_below(self.current_scope_id, &collector.scope_ids, flags)
151    }
152
153    fn insert_scope_below(
154        &mut self,
155        scope_id: ScopeId,
156        child_scope_ids: &[ScopeId],
157        flags: ScopeFlags,
158    ) -> ScopeId {
159        // Create new scope as child of parent
160        let new_scope_id = self.create_child_scope(scope_id, flags);
161
162        // Set scopes as children of new scope instead
163        for &child_id in child_scope_ids {
164            self.scoping.set_scope_parent_id(child_id, Some(new_scope_id));
165        }
166
167        new_scope_id
168    }
169
170    /// Insert a scope between a parent and a child scope.
171    ///
172    /// For example, given the following scopes
173    /// ```ts
174    /// parentScope1: {
175    ///     childScope: { }
176    ///     childScope2: { }
177    /// }
178    /// ```
179    /// and calling this function with `parentScope1` and `childScope`,
180    /// the resulting scopes will be:
181    /// ```ts
182    /// parentScope1: {
183    ///     newScope: {
184    ///         childScope: { }
185    ///     }
186    ///     childScope2: { }
187    /// }
188    /// ```
189    pub fn insert_scope_between(
190        &mut self,
191        parent_id: ScopeId,
192        child_id: ScopeId,
193        flags: ScopeFlags,
194    ) -> ScopeId {
195        let scope_id = self.create_child_scope(parent_id, flags);
196
197        debug_assert_eq!(
198            self.scoping.scope_parent_id(child_id),
199            Some(parent_id),
200            "Child scope must be a child of parent scope"
201        );
202
203        self.scoping.set_scope_parent_id(child_id, Some(scope_id));
204        scope_id
205    }
206
207    /// Remove scope for an expression from the scope chain.
208    ///
209    /// Delete the scope and set parent of its child scopes to its parent scope.
210    /// e.g.:
211    /// * Starting scopes parentage `A -> B`, `B -> C`, `B -> D`.
212    /// * Remove scope `B` from chain.
213    /// * End result: scopes `A -> C`, `A -> D`.
214    ///
215    /// Use this when removing an expression which owns a scope, without removing its children.
216    /// For example when unwrapping `(() => foo)()` to just `foo`.
217    /// `foo` here could be an expression which itself contains scopes.
218    pub fn remove_scope_for_expression(&mut self, scope_id: ScopeId, expr: &Expression) {
219        let mut collector = ChildScopeCollector::new();
220        collector.visit_expression(expr);
221
222        let child_ids = collector.scope_ids;
223        if !child_ids.is_empty() {
224            let parent_id = self.scoping.scope_parent_id(scope_id);
225            for child_id in child_ids {
226                self.scoping.set_scope_parent_id(child_id, parent_id);
227            }
228        }
229    }
230
231    /// Add binding to `ScopeTree` and `SymbolTable`.
232    #[inline]
233    pub(crate) fn add_binding(
234        &mut self,
235        name: Ident<'_>,
236        scope_id: ScopeId,
237        flags: SymbolFlags,
238    ) -> SymbolId {
239        let symbol_id = self.scoping.create_symbol(SPAN, name, flags, scope_id, NodeId::DUMMY);
240        self.scoping.add_binding(scope_id, name, symbol_id);
241
242        symbol_id
243    }
244
245    /// Generate binding.
246    ///
247    /// Creates a symbol with the provided name and flags and adds it to the specified scope.
248    pub fn generate_binding(
249        &mut self,
250        name: Ident<'a>,
251        scope_id: ScopeId,
252        flags: SymbolFlags,
253    ) -> BoundIdentifier<'a> {
254        let symbol_id = self.add_binding(name, scope_id, flags);
255        BoundIdentifier::new(name, symbol_id)
256    }
257
258    /// Generate binding in current scope.
259    ///
260    /// Creates a symbol with the provided name and flags and adds it to the current scope.
261    pub fn generate_binding_in_current_scope(
262        &mut self,
263        name: Ident<'a>,
264        flags: SymbolFlags,
265    ) -> BoundIdentifier<'a> {
266        self.generate_binding(name, self.current_scope_id, flags)
267    }
268
269    /// Generate UID var name.
270    ///
271    /// Finds a unique variable name which does clash with any other variables used in the program.
272    ///
273    /// Caller must ensure `name` is a valid JS identifier, after a `_` is prepended on start.
274    /// The fact that a `_` will be prepended on start means providing an empty string or a string
275    /// starting with a digit (0-9) is fine.
276    ///
277    /// See comments on `UidGenerator` for further details.
278    pub fn generate_uid_name(&mut self, name: &str, allocator: &'a Allocator) -> Ident<'a> {
279        // If `uid_generator` is not already populated, initialize it
280        let uid_generator =
281            self.uid_generator.get_or_insert_with(|| UidGenerator::new(&self.scoping, allocator));
282        // Generate unique name
283        uid_generator.create(name)
284    }
285
286    /// Create a reference bound to a `SymbolId`
287    pub fn create_bound_reference(
288        &mut self,
289        symbol_id: SymbolId,
290        flags: ReferenceFlags,
291    ) -> ReferenceId {
292        let reference =
293            Reference::new_with_symbol_id(NodeId::DUMMY, symbol_id, self.current_scope_id, flags);
294        let reference_id = self.scoping.create_reference(reference);
295        self.scoping.add_resolved_reference(symbol_id, reference_id);
296        reference_id
297    }
298
299    /// Create an unbound reference
300    pub fn create_unbound_reference(
301        &mut self,
302        name: Ident<'_>,
303        flags: ReferenceFlags,
304    ) -> ReferenceId {
305        let reference = Reference::new(NodeId::DUMMY, self.current_scope_id, flags);
306        let reference_id = self.scoping.create_reference(reference);
307        self.scoping.add_root_unresolved_reference(name, reference_id);
308        reference_id
309    }
310
311    /// Create a reference optionally bound to a `SymbolId`.
312    ///
313    /// If you know if there's a `SymbolId` or not, prefer `TraverseCtx::create_bound_reference`
314    /// or `TraverseCtx::create_unbound_reference`.
315    pub fn create_reference(
316        &mut self,
317        name: Ident<'_>,
318        symbol_id: Option<SymbolId>,
319        flags: ReferenceFlags,
320    ) -> ReferenceId {
321        if let Some(symbol_id) = symbol_id {
322            self.create_bound_reference(symbol_id, flags)
323        } else {
324            self.create_unbound_reference(name, flags)
325        }
326    }
327
328    /// Create reference in current scope, looking up binding for `name`
329    pub fn create_reference_in_current_scope(
330        &mut self,
331        name: Ident<'_>,
332        flags: ReferenceFlags,
333    ) -> ReferenceId {
334        let symbol_id = self.scoping.find_binding(self.current_scope_id, name);
335        self.create_reference(name, symbol_id, flags)
336    }
337
338    /// Delete a reference.
339    ///
340    /// Provided `name` must match `reference_id`.
341    pub fn delete_reference(&mut self, reference_id: ReferenceId, name: Ident<'_>) {
342        let symbol_id = self.scoping.get_reference(reference_id).symbol_id();
343        if let Some(symbol_id) = symbol_id {
344            self.scoping.delete_resolved_reference(symbol_id, reference_id);
345        } else {
346            self.scoping.delete_root_unresolved_reference(name, reference_id);
347        }
348    }
349
350    /// Delete reference for an `IdentifierReference`.
351    pub fn delete_reference_for_identifier(&mut self, ident: &IdentifierReference) {
352        self.delete_reference(ident.reference_id(), ident.name);
353    }
354}
355
356// Methods used internally within crate
357impl TraverseScoping<'_> {
358    /// Create new `TraverseScoping`
359    pub(super) fn new(scoping: Scoping) -> Self {
360        Self {
361            scoping,
362            uid_generator: None,
363            // Dummy values. Both immediately overwritten in `walk_program`.
364            current_scope_id: ScopeId::new(0),
365            current_hoist_scope_id: ScopeId::new(0),
366            current_block_scope_id: ScopeId::new(0),
367        }
368    }
369
370    /// Consume [`TraverseScoping`] and return [`Scoping`].
371    pub(super) fn into_scoping(self) -> Scoping {
372        self.scoping
373    }
374
375    /// Set current scope ID
376    #[inline]
377    pub(crate) fn set_current_scope_id(&mut self, scope_id: ScopeId) {
378        self.current_scope_id = scope_id;
379    }
380
381    /// Set current hoist scope ID
382    #[inline]
383    pub(crate) fn set_current_hoist_scope_id(&mut self, scope_id: ScopeId) {
384        self.current_hoist_scope_id = scope_id;
385    }
386
387    /// Set current block scope ID
388    #[inline]
389    pub(crate) fn set_current_block_scope_id(&mut self, scope_id: ScopeId) {
390        self.current_block_scope_id = scope_id;
391    }
392
393    pub fn delete_typescript_bindings(&mut self) {
394        self.scoping.delete_typescript_bindings();
395    }
396}