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    fn atom(&self) -> &Atom;
134    fn ctxt(&self) -> SyntaxContext;
135}
136impl RenamedVariable for Atom {
137    fn new_private(sym: Atom) -> Self {
138        sym
139    }
140
141    fn to_id(&self) -> Id {
142        (self.clone(), Default::default())
143    }
144
145    fn atom(&self) -> &Atom {
146        self
147    }
148
149    fn ctxt(&self) -> SyntaxContext {
150        Default::default()
151    }
152}
153impl RenamedVariable for Id {
154    fn new_private(sym: Atom) -> Self {
155        (sym, SyntaxContext::empty().apply_mark(Mark::new()))
156    }
157
158    fn to_id(&self) -> Id {
159        self.clone()
160    }
161
162    fn atom(&self) -> &Atom {
163        &self.0
164    }
165
166    fn ctxt(&self) -> SyntaxContext {
167        self.1
168    }
169}
170
171#[derive(Debug, Default)]
172struct RenamePass<R, V>
173where
174    R: Renamer<Target = V>,
175    V: RenamedVariable,
176{
177    config: Config,
178    renamer: R,
179
180    preserved: FxHashSet<Id>,
181    unresolved: FxHashSet<Atom>,
182
183    previous_cache: FxHashMap<Id, V>,
184
185    /// Used to store cache.
186    ///
187    /// [Some] if the [`Renamer::get_cached`] returns [Some].
188    total_map: Option<FxHashMap<Id, V>>,
189
190    marker: std::marker::PhantomData<V>,
191}
192
193impl<R, V> RenamePass<R, V>
194where
195    R: Renamer<Target = V>,
196    V: RenamedVariable,
197{
198    fn get_map<N>(
199        &mut self,
200        node: &N,
201        skip_one: bool,
202        top_level: bool,
203        has_eval: bool,
204    ) -> FxHashMap<Id, V>
205    where
206        N: VisitWith<AnalyzerAndCollector>,
207    {
208        let (mut scope, unresolved) = analyer_and_collector::analyzer_and_collect_unresolved(
209            node,
210            has_eval,
211            self.config.top_level_mark,
212            skip_one,
213        );
214
215        scope.prepare_renaming();
216
217        let mut unresolved = if !top_level {
218            let mut set = self.unresolved.clone();
219            set.extend(unresolved);
220            Cow::Owned(set)
221        } else {
222            self.unresolved = unresolved;
223            Cow::Borrowed(&self.unresolved)
224        };
225
226        if !self.preserved.is_empty() {
227            unresolved
228                .to_mut()
229                .extend(self.preserved.iter().map(|v| v.0.clone()));
230        }
231
232        {
233            let extra_unresolved = self.renamer.unresolved_symbols();
234
235            if !extra_unresolved.is_empty() {
236                unresolved.to_mut().extend(extra_unresolved);
237            }
238        }
239
240        let mut map = FxHashMap::<Id, V>::default();
241
242        if R::MANGLE {
243            let cost = scope.rename_cost();
244            scope.rename_in_mangle_mode(
245                &self.renamer,
246                &mut map,
247                &self.previous_cache,
248                &Default::default(),
249                &self.preserved,
250                &unresolved,
251                cost > 1024,
252            );
253        } else {
254            scope.rename_in_normal_mode(
255                &self.renamer,
256                &mut map,
257                &self.previous_cache,
258                &mut Default::default(),
259                &self.preserved,
260                &unresolved,
261            );
262        }
263
264        if let Some(total_map) = &mut self.total_map {
265            total_map.reserve(map.len());
266
267            for (k, v) in &map {
268                match total_map.entry(k.clone()) {
269                    Entry::Occupied(old) => {
270                        let old = old.get().to_id();
271                        let new = v.to_id();
272                        unreachable!(
273                            "{} is already renamed to {}, but it's renamed as {}",
274                            k.0, old.0, new.0
275                        );
276                    }
277                    Entry::Vacant(e) => {
278                        e.insert(v.clone());
279                    }
280                }
281            }
282        }
283
284        map
285    }
286
287    fn load_cache(&mut self) {
288        if let Some(cache) = self.renamer.get_cached() {
289            self.previous_cache = cache.into_owned();
290            self.total_map = Some(Default::default());
291        }
292    }
293}
294
295/// Mark a node as a unit of minification.
296///
297/// This is
298macro_rules! unit {
299    ($name:ident, $T:ty) => {
300        /// Only called if `eval` exists
301        fn $name(&mut self, n: &mut $T) {
302            if !self.config.ignore_eval && contains_eval(n, true) {
303                n.visit_mut_children_with(self);
304            } else {
305                let map = self.get_map(n, false, false, false);
306
307                if !map.is_empty() {
308                    n.visit_mut_with(&mut rename_with_config(&map, self.config.clone()));
309                }
310            }
311        }
312    };
313}
314
315impl<R, V> VisitMut for RenamePass<R, V>
316where
317    R: Renamer<Target = V>,
318    V: RenamedVariable,
319{
320    noop_visit_mut_type!();
321
322    unit!(visit_mut_arrow_expr, ArrowExpr);
323
324    unit!(visit_mut_setter_prop, SetterProp);
325
326    unit!(visit_mut_getter_prop, GetterProp);
327
328    unit!(visit_mut_constructor, Constructor);
329
330    unit!(visit_mut_fn_expr, FnExpr);
331
332    unit!(visit_mut_method_prop, MethodProp);
333
334    unit!(visit_mut_class_method, ClassMethod);
335
336    unit!(visit_mut_private_method, PrivateMethod);
337
338    fn visit_mut_fn_decl(&mut self, n: &mut FnDecl) {
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_class_decl(&mut self, n: &mut ClassDecl) {
357        if !self.config.ignore_eval && contains_eval(n, true) {
358            n.visit_mut_children_with(self);
359        } else {
360            let id = n.ident.to_id();
361            let inserted = self.preserved.insert(id.clone());
362            let map = self.get_map(n, true, false, false);
363
364            if inserted {
365                self.preserved.remove(&id);
366            }
367
368            if !map.is_empty() {
369                n.visit_mut_with(&mut rename_with_config(&map, self.config.clone()));
370            }
371        }
372    }
373
374    fn visit_mut_default_decl(&mut self, n: &mut DefaultDecl) {
375        match n {
376            DefaultDecl::Class(n) => {
377                n.visit_mut_children_with(self);
378            }
379            DefaultDecl::Fn(n) => {
380                n.visit_mut_children_with(self);
381            }
382            DefaultDecl::TsInterfaceDecl(n) => {
383                n.visit_mut_children_with(self);
384            }
385            #[cfg(swc_ast_unknown)]
386            _ => (),
387        }
388    }
389
390    fn visit_mut_expr(&mut self, n: &mut Expr) {
391        maybe_grow_default(|| n.visit_mut_children_with(self));
392    }
393
394    fn visit_mut_module(&mut self, m: &mut Module) {
395        self.load_cache();
396
397        let has_eval = !self.config.ignore_eval && contains_eval(m, true);
398
399        let map = self.get_map(m, false, true, has_eval);
400
401        // If we have eval, we cannot rename a whole program at once.
402        //
403        // Still, we can, and should rename some identifiers, if the containing scope
404        // (function-like nodes) does not have eval. This `eval` check includes
405        // `eval` in children.
406        //
407        // We calculate the top level map first, rename children, and then rename the
408        // top level.
409        //
410        //
411        // Order:
412        //
413        // 1. Top level map calculation
414        // 2. Per-unit map calculation
415        // 3. Per-unit renaming
416        // 4. Top level renaming
417        //
418        // This is because the top level map may contain a mapping which conflicts
419        // with a map from one of the children.
420        //
421        // See https://github.com/swc-project/swc/pull/7615
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    fn visit_mut_script(&mut self, m: &mut Script) {
436        self.load_cache();
437
438        let has_eval = !self.config.ignore_eval && contains_eval(m, true);
439
440        let map = self.get_map(m, false, true, has_eval);
441
442        if has_eval {
443            m.visit_mut_children_with(self);
444        }
445
446        if !map.is_empty() {
447            m.visit_mut_with(&mut rename_with_config(&map, self.config.clone()));
448        }
449
450        if let Some(total_map) = &self.total_map {
451            self.renamer.store_cache(total_map);
452        }
453    }
454}
455
456#[cfg(feature = "concurrent-renamer")]
457mod renamer_concurrent {
458    pub use std::marker::{Send, Sync};
459}
460
461#[cfg(not(feature = "concurrent-renamer"))]
462mod renamer_single {
463    /// Dummy trait because swc_common is in single thread mode.
464    pub trait Send {}
465    /// Dummy trait because swc_common is in single thread mode.
466    pub trait Sync {}
467
468    impl<T> Send for T where T: ?Sized {}
469    impl<T> Sync for T where T: ?Sized {}
470}