swc_ecma_transforms_base/rename/analyzer/
scope.rs

1#![allow(clippy::too_many_arguments)]
2
3use std::{hash::BuildHasherDefault, mem::take};
4
5use indexmap::IndexSet;
6#[cfg(feature = "concurrent-renamer")]
7use par_iter::prelude::*;
8use rustc_hash::{FxHashMap, FxHashSet, FxHasher};
9use swc_atoms::{atom, Atom};
10use swc_common::Mark;
11use swc_ecma_ast::*;
12use tracing::debug;
13
14use super::reverse_map::ReverseMap;
15use crate::rename::{RenamedVariable, Renamer};
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub(crate) enum ScopeKind {
19    Fn,
20    Block,
21}
22
23impl Default for ScopeKind {
24    fn default() -> Self {
25        Self::Fn
26    }
27}
28
29#[derive(Debug, Default)]
30pub(crate) struct Scope {
31    pub(super) kind: ScopeKind,
32    pub(super) data: ScopeData,
33
34    pub(super) children: Vec<Scope>,
35}
36
37pub(super) type FxIndexSet<T> = IndexSet<T, BuildHasherDefault<FxHasher>>;
38
39#[derive(Debug, Default)]
40pub(super) struct ScopeData {
41    /// All identifiers used by this scope or children.
42    ///
43    /// This is add-only.
44    ///
45    /// If the add-only contraint is violated, it is very likely to be a bug,
46    /// because we merge every items in children to current scope.
47    all: FxHashSet<Id>,
48
49    queue: FxIndexSet<Id>,
50}
51
52impl Scope {
53    pub(super) fn add_decl(&mut self, id: &Id, has_eval: bool, top_level_mark: Mark) {
54        if id.0 == atom!("arguments") {
55            return;
56        }
57
58        self.data.all.insert(id.clone());
59
60        if !self.data.queue.contains(id) {
61            if has_eval && id.1.outer().is_descendant_of(top_level_mark) {
62                return;
63            }
64
65            self.data.queue.insert(id.clone());
66        }
67    }
68
69    pub(crate) fn reserve_decl(&mut self, len: usize) {
70        self.data.all.reserve(len);
71
72        self.data.queue.reserve(len);
73    }
74
75    pub(super) fn add_usage(&mut self, id: Id) {
76        if id.0 == atom!("arguments") {
77            return;
78        }
79
80        self.data.all.insert(id);
81    }
82
83    pub(crate) fn reserve_usage(&mut self, len: usize) {
84        self.data.all.reserve(len);
85    }
86
87    /// Copy `children.data.all` to `self.data.all`.
88    pub(crate) fn prepare_renaming(&mut self) {
89        self.children.iter_mut().for_each(|child| {
90            child.prepare_renaming();
91
92            self.data.all.extend(child.data.all.iter().cloned());
93        });
94    }
95
96    pub(crate) fn rename_in_normal_mode<R, V>(
97        &mut self,
98        renamer: &R,
99        to: &mut FxHashMap<Id, V>,
100        previous: &FxHashMap<Id, V>,
101        reverse: &mut ReverseMap,
102        preserved: &FxHashSet<Id>,
103        preserved_symbols: &FxHashSet<Atom>,
104    ) where
105        R: Renamer,
106        V: RenamedVariable,
107    {
108        let queue = take(&mut self.data.queue);
109
110        // let mut cloned_reverse = reverse.clone();
111
112        self.rename_one_scope_in_normal_mode(
113            renamer,
114            to,
115            previous,
116            reverse,
117            queue,
118            preserved,
119            preserved_symbols,
120        );
121
122        for child in &mut self.children {
123            child.rename_in_normal_mode(
124                renamer,
125                to,
126                &Default::default(),
127                reverse,
128                preserved,
129                preserved_symbols,
130            );
131        }
132    }
133
134    fn rename_one_scope_in_normal_mode<R, V>(
135        &self,
136        renamer: &R,
137        to: &mut FxHashMap<Id, V>,
138        previous: &FxHashMap<Id, V>,
139        reverse: &mut ReverseMap,
140        queue: FxIndexSet<Id>,
141        preserved: &FxHashSet<Id>,
142        preserved_symbols: &FxHashSet<Atom>,
143    ) where
144        R: Renamer,
145        V: RenamedVariable,
146    {
147        let mut latest_n = FxHashMap::default();
148        let mut n = 0;
149
150        for id in queue {
151            if renamer.preserve_name(&id)
152                || preserved.contains(&id)
153                || to.get(&id).is_some()
154                || previous.get(&id).is_some()
155                || id.0 == "eval"
156            {
157                continue;
158            }
159
160            if R::RESET_N {
161                n = latest_n.get(&id.0).copied().unwrap_or(0);
162            }
163
164            loop {
165                let sym = renamer.new_name_for(&id, &mut n);
166
167                if preserved_symbols.contains(&sym) {
168                    continue;
169                }
170
171                if self.can_rename(&id, &sym, reverse) {
172                    let renamed = V::new_private(sym.clone());
173                    if cfg!(debug_assertions) {
174                        let renamed = renamed.to_id();
175                        debug!(
176                            "Renaming `{}{:?}` to `{}{:?}`",
177                            id.0, id.1, renamed.0, renamed.1
178                        );
179                    }
180                    latest_n.insert(id.0.clone(), n);
181
182                    reverse.push_entry(sym, id.clone());
183                    to.insert(id.clone(), renamed);
184                    break;
185                }
186            }
187        }
188    }
189
190    fn can_rename(&self, id: &Id, symbol: &Atom, reverse: &ReverseMap) -> bool {
191        // We can optimize this
192        // We only need to check the current scope and parents (ignoring `a` generated
193        // for unrelated scopes)
194        for left in reverse.get(symbol) {
195            if left.1 == id.1 && *left.0 == id.0 {
196                continue;
197            }
198
199            if self.data.all.contains(left) {
200                return false;
201            }
202        }
203
204        true
205    }
206
207    #[cfg_attr(
208        not(feature = "concurrent-renamer"),
209        allow(unused, clippy::only_used_in_recursion)
210    )]
211    pub(crate) fn rename_in_mangle_mode<R, V>(
212        &mut self,
213        renamer: &R,
214        to: &mut FxHashMap<Id, V>,
215        previous: &FxHashMap<Id, V>,
216        reverse: &ReverseMap,
217        preserved: &FxHashSet<Id>,
218        preserved_symbols: &FxHashSet<Atom>,
219        parallel: bool,
220    ) where
221        R: Renamer,
222        V: RenamedVariable,
223    {
224        let queue = take(&mut self.data.queue);
225
226        let mut cloned_reverse = reverse.next();
227
228        self.rename_one_scope_in_mangle_mode(
229            renamer,
230            to,
231            previous,
232            &mut cloned_reverse,
233            queue,
234            preserved,
235            preserved_symbols,
236        );
237
238        #[cfg(feature = "concurrent-renamer")]
239        if parallel {
240            #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"))))]
241            let iter = self.children.par_iter_mut();
242            #[cfg(target_arch = "wasm32")]
243            let iter = self.children.iter_mut();
244
245            let iter = iter
246                .map(|child| {
247                    use std::collections::HashMap;
248
249                    let mut new_map = HashMap::default();
250                    child.rename_in_mangle_mode(
251                        renamer,
252                        &mut new_map,
253                        to,
254                        &cloned_reverse,
255                        preserved,
256                        preserved_symbols,
257                        parallel,
258                    );
259                    new_map
260                })
261                .collect::<Vec<_>>();
262
263            for (k, v) in iter.into_iter().flatten() {
264                to.entry(k).or_insert(v);
265            }
266            return;
267        }
268
269        for child in &mut self.children {
270            child.rename_in_mangle_mode(
271                renamer,
272                to,
273                &Default::default(),
274                &cloned_reverse,
275                preserved,
276                preserved_symbols,
277                parallel,
278            );
279        }
280    }
281
282    fn rename_one_scope_in_mangle_mode<R, V>(
283        &self,
284        renamer: &R,
285        to: &mut FxHashMap<Id, V>,
286        previous: &FxHashMap<Id, V>,
287        reverse: &mut ReverseMap,
288        queue: FxIndexSet<Id>,
289        preserved: &FxHashSet<Id>,
290        preserved_symbols: &FxHashSet<Atom>,
291    ) where
292        R: Renamer,
293        V: RenamedVariable,
294    {
295        let mut n = 0;
296
297        for id in queue {
298            if renamer.preserve_name(&id)
299                || preserved.contains(&id)
300                || to.get(&id).is_some()
301                || previous.get(&id).is_some()
302                || id.0 == "eval"
303            {
304                continue;
305            }
306
307            loop {
308                let sym = renamer.new_name_for(&id, &mut n);
309
310                // TODO: Use base54::decode
311                if preserved_symbols.contains(&sym) {
312                    continue;
313                }
314
315                if self.can_rename(&id, &sym, reverse) {
316                    #[cfg(debug_assertions)]
317                    {
318                        debug!("mangle: `{}{:?}` -> {}", id.0, id.1, sym);
319                    }
320
321                    reverse.push_entry(sym.clone(), id.clone());
322                    to.insert(id.clone(), V::new_private(sym));
323                    // self.data.decls.remove(&id);
324                    // self.data.usages.remove(&id);
325
326                    break;
327                }
328            }
329        }
330    }
331
332    pub fn rename_cost(&self) -> usize {
333        let children = &self.children;
334        self.data.queue.len() + children.iter().map(|v| v.rename_cost()).sum::<usize>()
335    }
336}