swc_ecma_minifier/pass/
mangle_props.rs1use 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: FxHashMap<Wtf8Atom, Atom>,
37
38 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}