swc_ecma_minifier/pass/
mangle_props.rs

1use std::collections::HashSet;
2
3use once_cell::sync::Lazy;
4use rustc_hash::{FxHashMap, FxHashSet};
5use swc_atoms::{Atom, Wtf8Atom};
6use swc_ecma_ast::*;
7use swc_ecma_usage_analyzer::util::get_mut_object_define_property_name_arg;
8use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith};
9
10use crate::{option::ManglePropertiesOptions, program_data::analyze, util::base54::Base54Chars};
11
12pub static JS_ENVIRONMENT_PROPS: Lazy<FxHashSet<Atom>> = Lazy::new(|| {
13    let domprops: Vec<Atom> = serde_json::from_str(include_str!("../lists/domprops.json"))
14        .expect("failed to parse domprops.json for property mangler");
15
16    let jsprops: Vec<Atom> = serde_json::from_str(include_str!("../lists/jsprops.json"))
17        .expect("Failed to parse jsprops.json for property mangler");
18
19    let mut word_set: FxHashSet<Atom> = HashSet::default();
20
21    for name in domprops.iter().chain(jsprops.iter()) {
22        word_set.insert(name.clone());
23    }
24
25    word_set
26});
27
28struct ManglePropertiesState<'a> {
29    chars: Base54Chars,
30    options: &'a ManglePropertiesOptions,
31
32    names_to_mangle: FxHashSet<Wtf8Atom>,
33    unmangleable: FxHashSet<Wtf8Atom>,
34
35    // Cache of already mangled names
36    cache: FxHashMap<Wtf8Atom, Atom>,
37
38    // Numbers to pass to base54()
39    n: usize,
40}
41
42impl<'a> ManglePropertiesState<'a> {
43    fn add(&mut self, name: Wtf8Atom) {
44        let can_mangle = self.can_mangle(&name);
45        let should_mangle = self.should_mangle(&name);
46        match (can_mangle, !should_mangle) {
47            (true, true) => {
48                self.names_to_mangle.insert(name.clone());
49                self.unmangleable.insert(name);
50            }
51            (false, true) => {
52                self.unmangleable.insert(name);
53            }
54            (true, false) => {
55                self.names_to_mangle.insert(name);
56            }
57            _ => {}
58        }
59    }
60
61    fn can_mangle(&self, name: &Wtf8Atom) -> bool {
62        !(self.unmangleable.contains(name) || self.is_reserved(name))
63    }
64
65    fn matches_regex_option(&self, name: &Wtf8Atom) -> bool {
66        if let Some(regex) = &self.options.regex {
67            if let Some(utf8_str) = name.as_str() {
68                regex.is_match(utf8_str)
69            } else {
70                false
71            }
72        } else {
73            true
74        }
75    }
76
77    fn should_mangle(&self, name: &Wtf8Atom) -> bool {
78        if !self.matches_regex_option(name) || self.is_reserved(name) {
79            false
80        } else {
81            self.cache.contains_key(name) || self.names_to_mangle.contains(name)
82        }
83    }
84
85    fn is_reserved(&self, name: &Wtf8Atom) -> bool {
86        if let Some(utf8_str) = name.as_str() {
87            let atom = Atom::from(utf8_str);
88            JS_ENVIRONMENT_PROPS.contains(&atom) || self.options.reserved.contains(&atom)
89        } else {
90            false
91        }
92    }
93
94    fn gen_name(&mut self, name: &Wtf8Atom) -> Option<Atom> {
95        if self.should_mangle(name) {
96            if let Some(cached) = self.cache.get(name) {
97                Some(cached.clone())
98            } else {
99                let mangled_name = self.chars.encode(&mut self.n, true);
100
101                self.cache.insert(name.clone(), mangled_name.clone());
102                Some(mangled_name)
103            }
104        } else {
105            None
106        }
107    }
108}
109
110pub(crate) fn mangle_properties(
111    m: &mut Program,
112    options: &ManglePropertiesOptions,
113    chars: Base54Chars,
114) {
115    let mut state = ManglePropertiesState {
116        options,
117        chars,
118        names_to_mangle: Default::default(),
119        unmangleable: Default::default(),
120        cache: Default::default(),
121        n: 0,
122    };
123
124    let mut data = analyze(&*m, None, true);
125
126    for prop in std::mem::take(data.property_atoms.as_mut().unwrap()) {
127        state.add(prop);
128    }
129
130    m.visit_mut_with(&mut Mangler { state: &mut state });
131}
132
133struct Mangler<'a, 'b> {
134    state: &'a mut ManglePropertiesState<'b>,
135}
136
137impl Mangler<'_, '_> {
138    fn mangle_ident(&mut self, ident: &mut IdentName) {
139        let wtf8_name = Wtf8Atom::from(ident.sym.clone());
140        if let Some(mangled) = self.state.gen_name(&wtf8_name) {
141            ident.sym = mangled;
142        }
143    }
144
145    fn mangle_str(&mut self, string: &mut Str) {
146        if let Some(mangled) = self.state.gen_name(&string.value) {
147            string.value = mangled.into();
148            string.raw = None;
149        }
150    }
151}
152
153impl VisitMut for Mangler<'_, '_> {
154    noop_visit_mut_type!(fail);
155
156    fn visit_mut_call_expr(&mut self, call: &mut CallExpr) {
157        call.visit_mut_children_with(self);
158
159        if let Some(prop_name_str) = get_mut_object_define_property_name_arg(call) {
160            self.mangle_str(prop_name_str);
161        }
162    }
163
164    fn visit_mut_member_expr(&mut self, member_expr: &mut MemberExpr) {
165        member_expr.visit_mut_children_with(self);
166
167        if let MemberProp::Ident(ident) = &mut member_expr.prop {
168            self.mangle_ident(ident);
169        }
170    }
171
172    fn visit_mut_prop(&mut self, prop: &mut Prop) {
173        prop.visit_mut_children_with(self);
174
175        if let Prop::Shorthand(ident) = prop {
176            let mut new_ident = IdentName::from(ident.clone());
177
178            self.mangle_ident(&mut new_ident);
179
180            *prop = Prop::KeyValue(KeyValueProp {
181                key: PropName::Ident(new_ident),
182                value: ident.clone().into(),
183            });
184        }
185    }
186
187    fn visit_mut_prop_name(&mut self, name: &mut PropName) {
188        name.visit_mut_children_with(self);
189
190        match name {
191            PropName::Ident(ident) => {
192                self.mangle_ident(ident);
193            }
194            PropName::Str(string) => {
195                self.mangle_str(string);
196            }
197            _ => {}
198        }
199    }
200
201    fn visit_mut_super_prop_expr(&mut self, super_expr: &mut SuperPropExpr) {
202        super_expr.visit_mut_children_with(self);
203
204        if let SuperProp::Ident(ident) = &mut super_expr.prop {
205            self.mangle_ident(ident);
206        }
207    }
208}