swc_ecma_compat_es2015/block_scoping/
vars.rs

1use indexmap::IndexMap;
2use rustc_hash::{FxBuildHasher, FxHashMap};
3use swc_atoms::Atom;
4use swc_common::{Mark, SyntaxContext};
5use swc_ecma_ast::*;
6use swc_ecma_transforms_base::{rename::rename_with_config, scope::ScopeKind};
7use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith};
8use swc_trace_macro::swc_trace;
9
10pub(super) fn block_scoped_vars() -> impl VisitMut {
11    BlockScopedVars::default()
12}
13
14#[derive(Default)]
15struct BlockScopedVars {
16    scope: Scope,
17
18    var_decl_kind: Option<VarDeclKind>,
19    is_param: bool,
20}
21
22#[derive(Debug, Default)]
23struct Scope {
24    kind: ScopeKind,
25
26    vars: IndexMap<Id, VarDeclKind, FxBuildHasher>,
27    usages: Vec<Id>,
28
29    children: Vec<Scope>,
30}
31
32#[derive(Clone, Copy)]
33struct ParentScope<'a> {
34    parent: Option<&'a ParentScope<'a>>,
35
36    vars: &'a IndexMap<Id, VarDeclKind, FxBuildHasher>,
37}
38
39#[swc_trace]
40impl BlockScopedVars {
41    fn add_usage(&mut self, id: Id) {
42        if !self.scope.usages.contains(&id) {
43            self.scope.usages.push(id);
44        }
45    }
46
47    /// We rename declarations, not usages.
48    /// This is because we want to preserve access to global items.
49    ///
50    /// But we use usages to potential conflicts, so we do it renaming in
51    /// multiple steps.
52    ///
53    ///  - For first, we collect all variables and usages.
54    ///
55    ///  - For second, we expand all usages to get list of symbols which may
56    ///    conflict.
57    ///
58    ///  - For third, we rename all declarations which may conflict.
59    fn handle_program<N>(&mut self, n: &mut N)
60    where
61        N: VisitMutWith<Self> + for<'aa> VisitMutWith<dyn 'aa + VisitMut>,
62    {
63        n.visit_mut_children_with(self);
64
65        let empty_vars = Default::default();
66        let parent = ParentScope {
67            parent: None,
68            vars: &empty_vars,
69        };
70
71        let mut rename_map = FxHashMap::default();
72
73        // dbg!(&self.scope);
74
75        self.scope.rename(parent, &mut rename_map, true);
76        self.scope.rename(parent, &mut rename_map, false);
77
78        // dbg!(&rename_map);
79
80        n.visit_mut_with(
81            &mut rename_with_config(&rename_map, Default::default()) as &mut dyn VisitMut
82        );
83    }
84
85    fn with_scope(&mut self, kind: ScopeKind, op: impl FnOnce(&mut Self)) {
86        let scope = Scope {
87            kind,
88            ..Default::default()
89        };
90
91        let mut v = BlockScopedVars { scope, ..*self };
92        op(&mut v);
93
94        if kind == ScopeKind::Block {
95            for (k, v) in &v.scope.vars {
96                if *v == VarDeclKind::Var {
97                    self.scope.vars.insert(k.clone(), VarDeclKind::Var);
98                }
99            }
100        }
101
102        self.scope.children.push(v.scope);
103    }
104}
105
106#[swc_trace]
107impl Scope {
108    fn rename(&mut self, parent: ParentScope, rename_map: &mut FxHashMap<Id, Id>, fn_only: bool) {
109        for s in self.children.iter_mut() {
110            let parent = ParentScope {
111                parent: Some(&parent),
112                vars: &self.vars,
113            };
114
115            s.rename(parent, rename_map, fn_only);
116        }
117
118        if fn_only && self.kind != ScopeKind::Fn {
119            return;
120        }
121
122        let mut symbols = Default::default();
123
124        self.collect_candidates(parent, &mut symbols);
125
126        // dbg!(&symbols);
127
128        self.rename_decls(&symbols, rename_map);
129    }
130
131    ///
132    /// ## Falsy case
133    ///
134    /// This returns false for the code below, because the variable `a` is
135    /// block-scoped.
136    ///
137    /// ```js
138    /// {
139    ///     let a = 1;
140    /// }
141    /// console.log(a)
142    /// ```
143    fn can_access(&self, id: &Id, parent: ParentScope, deny_let_const: bool) -> bool {
144        if parent.get_var(id).is_some() {
145            return true;
146        }
147
148        if let Some(kind) = self.vars.get(id).copied() {
149            if deny_let_const && matches!(kind, VarDeclKind::Let | VarDeclKind::Const) {
150                return false;
151            }
152
153            return true;
154        }
155
156        self.children.iter().any(|s| match s.kind {
157            ScopeKind::Block => s.can_access(id, parent, true),
158            ScopeKind::Fn => false,
159        })
160    }
161
162    fn remove_usage(&mut self, id: &Id) {
163        if let Some(pos) = self.usages.iter().position(|i| *i == *id) {
164            self.usages.remove(pos);
165        }
166    }
167
168    /// If a used identifier is declared in a child scope using `let` or
169    /// `const`, add it to `rename_map`.
170    fn collect_candidates(&mut self, parent: ParentScope, symbols: &mut Vec<Atom>) {
171        for id in &self.usages {
172            if self.can_access(id, parent, false) {
173                self.children.iter_mut().for_each(|s| {
174                    s.remove_usage(id);
175                });
176            } else if !symbols.contains(&id.0) {
177                symbols.push(id.0.clone());
178            }
179        }
180        self.usages.clear();
181
182        let parent = ParentScope {
183            parent: Some(&parent),
184            vars: &self.vars,
185        };
186
187        self.children
188            .iter_mut()
189            .for_each(|s| s.collect_candidates(parent, symbols));
190    }
191
192    fn rename_decls(&self, symbols: &[Atom], rename_map: &mut FxHashMap<Id, Id>) {
193        for (id, _) in &self.vars {
194            if !symbols.contains(&id.0) {
195                continue;
196            }
197            if rename_map.contains_key(id) {
198                continue;
199            }
200
201            // We use _$ as prefix because other passes use `_` as prefix.
202            // (To avoid lots of renaming)
203            let sym = format!("_${}", id.0);
204
205            // We create a new syntax context instead of using original.
206            //
207            // This is required because
208            //
209            // {
210            //      let a = 1;
211            //      var _a = 2;
212            // }
213            //
214            // We can avoid this by detecting variable names, but using different syntax
215            // context is much easier.
216            let ctxt = SyntaxContext::empty().apply_mark(Mark::fresh(Mark::root()));
217
218            rename_map.insert(id.clone(), (sym.into(), ctxt));
219        }
220
221        self.children
222            .iter()
223            .for_each(|s| s.rename_decls(symbols, rename_map));
224    }
225}
226
227impl ParentScope<'_> {
228    fn get_var(&self, id: &Id) -> Option<VarDeclKind> {
229        if let Some(kind) = self.vars.get(id).copied() {
230            return Some(kind);
231        }
232
233        self.parent?.get_var(id)
234    }
235}
236
237#[swc_trace]
238impl VisitMut for BlockScopedVars {
239    noop_visit_mut_type!(fail);
240
241    fn visit_mut_arrow_expr(&mut self, n: &mut ArrowExpr) {
242        self.with_scope(ScopeKind::Fn, |v| {
243            let old = v.is_param;
244            v.is_param = true;
245            n.params.visit_mut_with(v);
246            v.is_param = old;
247
248            match &mut *n.body {
249                BlockStmtOrExpr::BlockStmt(b) => {
250                    b.visit_mut_children_with(v);
251                }
252                BlockStmtOrExpr::Expr(b) => {
253                    b.visit_mut_with(v);
254                }
255                #[cfg(swc_ast_unknown)]
256                _ => panic!("unable to access unknown nodes"),
257            }
258        });
259    }
260
261    fn visit_mut_assign_pat_prop(&mut self, n: &mut AssignPatProp) {
262        n.visit_mut_children_with(self);
263
264        if let Some(kind) = self.var_decl_kind {
265            self.scope.vars.insert(n.key.to_id(), kind);
266        } else if !self.is_param {
267            self.add_usage(n.key.to_id())
268        }
269    }
270
271    fn visit_mut_binding_ident(&mut self, i: &mut BindingIdent) {
272        if let Some(kind) = self.var_decl_kind {
273            self.scope.vars.insert(i.to_id(), kind);
274        } else if !self.is_param {
275            self.add_usage(i.to_id())
276        }
277    }
278
279    fn visit_mut_block_stmt(&mut self, n: &mut BlockStmt) {
280        self.with_scope(ScopeKind::Block, |v| {
281            n.visit_mut_children_with(v);
282        });
283    }
284
285    fn visit_mut_catch_clause(&mut self, n: &mut CatchClause) {
286        let old_is_param = self.is_param;
287        self.is_param = true;
288
289        let old_var_decl_kind = self.var_decl_kind;
290        self.var_decl_kind = None;
291
292        n.visit_mut_children_with(self);
293
294        self.var_decl_kind = old_var_decl_kind;
295        self.is_param = old_is_param;
296    }
297
298    fn visit_mut_constructor(&mut self, n: &mut Constructor) {
299        self.with_scope(ScopeKind::Fn, |v| {
300            n.params.visit_mut_with(v);
301
302            if let Some(body) = &mut n.body {
303                body.visit_mut_children_with(v);
304            }
305        });
306    }
307
308    fn visit_mut_expr(&mut self, n: &mut Expr) {
309        let old_var_decl_kind = self.var_decl_kind;
310        self.var_decl_kind = None;
311
312        n.visit_mut_children_with(self);
313
314        if let Expr::Ident(i) = n {
315            self.add_usage(i.to_id());
316        }
317
318        self.var_decl_kind = old_var_decl_kind;
319    }
320
321    fn visit_mut_for_in_stmt(&mut self, n: &mut ForInStmt) {
322        n.right.visit_mut_with(self);
323
324        match &n.left {
325            ForHead::VarDecl(v)
326                if matches!(
327                    &**v,
328                    VarDecl {
329                        kind: VarDeclKind::Let | VarDeclKind::Const,
330                        ..
331                    }
332                ) =>
333            {
334                self.with_scope(ScopeKind::Block, |v| {
335                    n.left.visit_mut_with(v);
336                    n.body.visit_mut_with(v);
337                });
338            }
339            _ => {
340                n.left.visit_mut_with(self);
341                n.body.visit_mut_with(self);
342            }
343        }
344    }
345
346    fn visit_mut_for_of_stmt(&mut self, n: &mut ForOfStmt) {
347        n.right.visit_mut_with(self);
348
349        match &n.left {
350            ForHead::VarDecl(v)
351                if matches!(
352                    &**v,
353                    VarDecl {
354                        kind: VarDeclKind::Let | VarDeclKind::Const,
355                        ..
356                    }
357                ) =>
358            {
359                self.with_scope(ScopeKind::Block, |v| {
360                    n.left.visit_mut_with(v);
361                    n.body.visit_mut_with(v);
362                });
363            }
364            _ => {
365                n.left.visit_mut_with(self);
366                n.body.visit_mut_with(self);
367            }
368        }
369    }
370
371    fn visit_mut_for_stmt(&mut self, n: &mut ForStmt) {
372        match &n.init {
373            Some(VarDeclOrExpr::VarDecl(v))
374                if matches!(
375                    &**v,
376                    VarDecl {
377                        kind: VarDeclKind::Let | VarDeclKind::Const,
378                        ..
379                    }
380                ) =>
381            {
382                self.with_scope(ScopeKind::Block, |v| {
383                    n.init.visit_mut_with(v);
384                    n.update.visit_mut_with(v);
385                    n.test.visit_mut_with(v);
386
387                    n.body.visit_mut_with(v);
388                });
389            }
390            _ => {
391                n.init.visit_mut_with(self);
392                n.update.visit_mut_with(self);
393                n.test.visit_mut_with(self);
394
395                n.body.visit_mut_with(self);
396            }
397        }
398    }
399
400    fn visit_mut_function(&mut self, n: &mut Function) {
401        n.decorators.visit_mut_with(self);
402
403        self.with_scope(ScopeKind::Fn, |v| {
404            n.params.visit_mut_with(v);
405
406            if let Some(body) = &mut n.body {
407                body.visit_mut_children_with(v);
408            }
409        });
410    }
411
412    fn visit_mut_module(&mut self, n: &mut Module) {
413        self.handle_program(n)
414    }
415
416    fn visit_mut_param(&mut self, n: &mut Param) {
417        let old_is_param = self.is_param;
418        self.is_param = true;
419
420        let old_var_decl_kind = self.var_decl_kind;
421        self.var_decl_kind = None;
422
423        n.visit_mut_children_with(self);
424
425        self.var_decl_kind = old_var_decl_kind;
426        self.is_param = old_is_param;
427    }
428
429    fn visit_mut_prop(&mut self, n: &mut Prop) {
430        n.visit_mut_children_with(self);
431
432        if let Prop::Shorthand(i) = n {
433            self.add_usage(i.to_id());
434        }
435    }
436
437    fn visit_mut_script(&mut self, n: &mut Script) {
438        self.handle_program(n)
439    }
440
441    fn visit_mut_var_decl(&mut self, n: &mut VarDecl) {
442        let old_var_decl_kind = self.var_decl_kind;
443        self.var_decl_kind = Some(n.kind);
444
445        n.visit_mut_children_with(self);
446
447        self.var_decl_kind = old_var_decl_kind;
448    }
449}