swc_ecma_transforms_base/rename/
mod.rs

1use std::{borrow::Cow, collections::hash_map::Entry};
2
3use analyer_and_collector::AnalyzerAndCollector;
4use rustc_hash::{FxHashMap, FxHashSet};
5use swc_atoms::Atom;
6use swc_common::{Mark, SyntaxContext};
7use swc_ecma_ast::*;
8use swc_ecma_utils::stack_size::maybe_grow_default;
9use swc_ecma_visit::{noop_visit_mut_type, visit_mut_pass, VisitMut, VisitMutWith, VisitWith};
10
11pub use self::eval::contains_eval;
12#[cfg(feature = "concurrent-renamer")]
13use self::renamer_concurrent::{Send, Sync};
14#[cfg(not(feature = "concurrent-renamer"))]
15use self::renamer_single::{Send, Sync};
16use self::{analyzer::Analyzer, ops::Operator};
17use crate::hygiene::Config;
18
19mod analyer_and_collector;
20mod analyzer;
21mod eval;
22mod ops;
23
24pub trait Renamer: Send + Sync {
25    /// See the [`RenamedVariable`] documentation, this type determines whether
26    /// impls can be used with [`renamer`] or [`renamer_keep_contexts`] .
27    type Target: RenamedVariable;
28
29    /// Should reset `n` to 0 for each identifier?
30    const RESET_N: bool;
31
32    /// It should be true if you expect lots of collisions
33    const MANGLE: bool;
34
35    fn get_cached(&self) -> Option<Cow<FxHashMap<Id, Self::Target>>> {
36        None
37    }
38
39    fn store_cache(&mut self, _update: &FxHashMap<Id, Self::Target>) {}
40
41    /// Should increment `n`.
42    fn new_name_for(&self, orig: &Id, n: &mut usize) -> Atom;
43
44    fn unresolved_symbols(&self) -> Vec<Atom> {
45        Default::default()
46    }
47
48    /// Return true if the identifier should be preserved.
49    #[inline]
50    fn preserve_name(&self, _orig: &Id) -> bool {
51        false
52    }
53}
54
55pub type RenameMap = FxHashMap<Id, Atom>;
56
57pub fn rename<V: RenamedVariable>(map: &FxHashMap<Id, V>) -> impl '_ + Pass + VisitMut {
58    rename_with_config(map, Default::default())
59}
60
61pub fn rename_with_config<V: RenamedVariable>(
62    map: &FxHashMap<Id, V>,
63    config: Config,
64) -> impl '_ + Pass + VisitMut {
65    visit_mut_pass(Operator {
66        rename: map,
67        config,
68        extra: Default::default(),
69    })
70}
71
72pub fn renamer<R>(config: Config, renamer: R) -> impl Pass + VisitMut
73where
74    R: Renamer<Target = Atom>,
75{
76    visit_mut_pass(RenamePass {
77        config,
78        renamer,
79        preserved: Default::default(),
80        unresolved: Default::default(),
81        previous_cache: Default::default(),
82        total_map: None,
83        marker: std::marker::PhantomData::<Atom>,
84    })
85}
86
87/// Create correct (unique) syntax contexts. Use this if you need the syntax
88/// contexts produces by this pass (unlike the default `hygiene` pass which
89/// removes them anyway.)
90pub fn renamer_keep_contexts<R>(config: Config, renamer: R) -> impl Pass + VisitMut
91where
92    R: Renamer<Target = Id>,
93{
94    visit_mut_pass(RenamePass {
95        config,
96        renamer,
97        preserved: Default::default(),
98        unresolved: Default::default(),
99        previous_cache: Default::default(),
100        total_map: None,
101        marker: std::marker::PhantomData::<Id>,
102    })
103}
104
105mod private {
106    use swc_atoms::Atom;
107    use swc_ecma_ast::Id;
108
109    pub trait Sealed {}
110
111    impl Sealed for Atom {}
112    impl Sealed for Id {}
113}
114
115/// A trait that is used to represent a renamed variable. For
116/// `renamer_keep_contexts`, the syntax contexts of the replacements should be
117/// correct (unique), while for `hygiene` (which calls `renamer`), the resulting
118/// syntax contexts are irrelevant. This type is used to handle both cases
119/// without code duplication by using `HashMap<Id, impl RenamedVariable>`
120/// everywhere:
121/// - For `renamer`, `HashMap<Id, Atom>` is used (and `SyntaxContext::empty()`
122///   isn't store unnecessarily). All replaced idents have the same
123///   SyntaxContext #0.
124/// - For `renamer_keep_contexts`, `HashMap<Id, Id>` is used. All replaced
125///   idents have a unique SyntaxContext.
126pub trait RenamedVariable:
127    private::Sealed + Clone + Sized + std::marker::Send + std::marker::Sync + 'static
128{
129    /// Potentially create a new private variable, depending on whether the
130    /// consumer cares about the syntax context after the renaming.
131    fn new_private(sym: Atom) -> Self;
132    fn to_id(&self) -> Id;
133}
134impl RenamedVariable for Atom {
135    fn new_private(sym: Atom) -> Self {
136        sym
137    }
138
139    fn to_id(&self) -> Id {
140        (self.clone(), Default::default())
141    }
142}
143impl RenamedVariable for Id {
144    fn new_private(sym: Atom) -> Self {
145        (sym, SyntaxContext::empty().apply_mark(Mark::new()))
146    }
147
148    fn to_id(&self) -> Id {
149        self.clone()
150    }
151}
152
153#[derive(Debug, Default)]
154struct RenamePass<R, V>
155where
156    R: Renamer<Target = V>,
157    V: RenamedVariable,
158{
159    config: Config,
160    renamer: R,
161
162    preserved: FxHashSet<Id>,
163    unresolved: FxHashSet<Atom>,
164
165    previous_cache: FxHashMap<Id, V>,
166
167    /// Used to store cache.
168    ///
169    /// [Some] if the [`Renamer::get_cached`] returns [Some].
170    total_map: Option<FxHashMap<Id, V>>,
171
172    marker: std::marker::PhantomData<V>,
173}
174
175impl<R, V> RenamePass<R, V>
176where
177    R: Renamer<Target = V>,
178    V: RenamedVariable,
179{
180    fn get_map<N>(
181        &mut self,
182        node: &N,
183        skip_one: bool,
184        top_level: bool,
185        has_eval: bool,
186    ) -> FxHashMap<Id, V>
187    where
188        N: VisitWith<AnalyzerAndCollector>,
189    {
190        let (mut scope, unresolved) = analyer_and_collector::analyzer_and_collect_unresolved(
191            node,
192            has_eval,
193            self.config.top_level_mark,
194            skip_one,
195        );
196
197        scope.prepare_renaming();
198
199        let mut unresolved = if !top_level {
200            let mut set = self.unresolved.clone();
201            set.extend(unresolved);
202            Cow::Owned(set)
203        } else {
204            self.unresolved = unresolved;
205            Cow::Borrowed(&self.unresolved)
206        };
207
208        if !self.preserved.is_empty() {
209            unresolved
210                .to_mut()
211                .extend(self.preserved.iter().map(|v| v.0.clone()));
212        }
213
214        {
215            let extra_unresolved = self.renamer.unresolved_symbols();
216
217            if !extra_unresolved.is_empty() {
218                unresolved.to_mut().extend(extra_unresolved);
219            }
220        }
221
222        let mut map = FxHashMap::<Id, V>::default();
223
224        if R::MANGLE {
225            let cost = scope.rename_cost();
226            scope.rename_in_mangle_mode(
227                &self.renamer,
228                &mut map,
229                &self.previous_cache,
230                &Default::default(),
231                &self.preserved,
232                &unresolved,
233                cost > 1024,
234            );
235        } else {
236            scope.rename_in_normal_mode(
237                &self.renamer,
238                &mut map,
239                &self.previous_cache,
240                &mut Default::default(),
241                &self.preserved,
242                &unresolved,
243            );
244        }
245
246        if let Some(total_map) = &mut self.total_map {
247            total_map.reserve(map.len());
248
249            for (k, v) in &map {
250                match total_map.entry(k.clone()) {
251                    Entry::Occupied(old) => {
252                        let old = old.get().to_id();
253                        let new = v.to_id();
254                        unreachable!(
255                            "{} is already renamed to {}, but it's renamed as {}",
256                            k.0, old.0, new.0
257                        );
258                    }
259                    Entry::Vacant(e) => {
260                        e.insert(v.clone());
261                    }
262                }
263            }
264        }
265
266        map
267    }
268
269    fn load_cache(&mut self) {
270        if let Some(cache) = self.renamer.get_cached() {
271            self.previous_cache = cache.into_owned();
272            self.total_map = Some(Default::default());
273        }
274    }
275}
276
277/// Mark a node as a unit of minification.
278///
279/// This is
280macro_rules! unit {
281    ($name:ident, $T:ty) => {
282        /// Only called if `eval` exists
283        fn $name(&mut self, n: &mut $T) {
284            if !self.config.ignore_eval && contains_eval(n, true) {
285                n.visit_mut_children_with(self);
286            } else {
287                let map = self.get_map(n, false, false, false);
288
289                if !map.is_empty() {
290                    n.visit_mut_with(&mut rename_with_config(&map, self.config.clone()));
291                }
292            }
293        }
294    };
295}
296
297impl<R, V> VisitMut for RenamePass<R, V>
298where
299    R: Renamer<Target = V>,
300    V: RenamedVariable,
301{
302    noop_visit_mut_type!();
303
304    unit!(visit_mut_arrow_expr, ArrowExpr);
305
306    unit!(visit_mut_setter_prop, SetterProp);
307
308    unit!(visit_mut_getter_prop, GetterProp);
309
310    unit!(visit_mut_constructor, Constructor);
311
312    unit!(visit_mut_fn_expr, FnExpr);
313
314    unit!(visit_mut_method_prop, MethodProp);
315
316    unit!(visit_mut_class_method, ClassMethod);
317
318    unit!(visit_mut_private_method, PrivateMethod);
319
320    fn visit_mut_fn_decl(&mut self, n: &mut FnDecl) {
321        if !self.config.ignore_eval && contains_eval(n, true) {
322            n.visit_mut_children_with(self);
323        } else {
324            let id = n.ident.to_id();
325            let inserted = self.preserved.insert(id.clone());
326            let map = self.get_map(n, true, false, false);
327
328            if inserted {
329                self.preserved.remove(&id);
330            }
331
332            if !map.is_empty() {
333                n.visit_mut_with(&mut rename_with_config(&map, self.config.clone()));
334            }
335        }
336    }
337
338    fn visit_mut_class_decl(&mut self, n: &mut ClassDecl) {
339        if !self.config.ignore_eval && contains_eval(n, true) {
340            n.visit_mut_children_with(self);
341        } else {
342            let id = n.ident.to_id();
343            let inserted = self.preserved.insert(id.clone());
344            let map = self.get_map(n, true, false, false);
345
346            if inserted {
347                self.preserved.remove(&id);
348            }
349
350            if !map.is_empty() {
351                n.visit_mut_with(&mut rename_with_config(&map, self.config.clone()));
352            }
353        }
354    }
355
356    fn visit_mut_default_decl(&mut self, n: &mut DefaultDecl) {
357        match n {
358            DefaultDecl::Class(n) => {
359                n.visit_mut_children_with(self);
360            }
361            DefaultDecl::Fn(n) => {
362                n.visit_mut_children_with(self);
363            }
364            DefaultDecl::TsInterfaceDecl(n) => {
365                n.visit_mut_children_with(self);
366            }
367        }
368    }
369
370    fn visit_mut_expr(&mut self, n: &mut Expr) {
371        maybe_grow_default(|| n.visit_mut_children_with(self));
372    }
373
374    fn visit_mut_module(&mut self, m: &mut Module) {
375        self.load_cache();
376
377        let has_eval = !self.config.ignore_eval && contains_eval(m, true);
378
379        let map = self.get_map(m, false, true, has_eval);
380
381        // If we have eval, we cannot rename a whole program at once.
382        //
383        // Still, we can, and should rename some identifiers, if the containing scope
384        // (function-like nodes) does not have eval. This `eval` check includes
385        // `eval` in children.
386        //
387        // We calculate the top level map first, rename children, and then rename the
388        // top level.
389        //
390        //
391        // Order:
392        //
393        // 1. Top level map calculation
394        // 2. Per-unit map calculation
395        // 3. Per-unit renaming
396        // 4. Top level renaming
397        //
398        // This is because the top level map may contain a mapping which conflicts
399        // with a map from one of the children.
400        //
401        // See https://github.com/swc-project/swc/pull/7615
402        if has_eval {
403            m.visit_mut_children_with(self);
404        }
405
406        if !map.is_empty() {
407            m.visit_mut_with(&mut rename_with_config(&map, self.config.clone()));
408        }
409
410        if let Some(total_map) = &self.total_map {
411            self.renamer.store_cache(total_map);
412        }
413    }
414
415    fn visit_mut_script(&mut self, m: &mut Script) {
416        self.load_cache();
417
418        let has_eval = !self.config.ignore_eval && contains_eval(m, true);
419
420        let map = self.get_map(m, false, true, has_eval);
421
422        if has_eval {
423            m.visit_mut_children_with(self);
424        }
425
426        if !map.is_empty() {
427            m.visit_mut_with(&mut rename_with_config(&map, self.config.clone()));
428        }
429
430        if let Some(total_map) = &self.total_map {
431            self.renamer.store_cache(total_map);
432        }
433    }
434}
435
436#[cfg(feature = "concurrent-renamer")]
437mod renamer_concurrent {
438    pub use std::marker::{Send, Sync};
439}
440
441#[cfg(not(feature = "concurrent-renamer"))]
442mod renamer_single {
443    /// Dummy trait because swc_common is in single thread mode.
444    pub trait Send {}
445    /// Dummy trait because swc_common is in single thread mode.
446    pub trait Sync {}
447
448    impl<T> Send for T where T: ?Sized {}
449    impl<T> Sync for T where T: ?Sized {}
450}