swc_ecma_minifier/compress/optimize/
props.rs1use std::borrow::Borrow;
2
3use swc_common::{util::take::Take, DUMMY_SP};
4use swc_ecma_ast::*;
5use swc_ecma_utils::{contains_this_expr, private_ident, prop_name_eq, ExprExt};
6
7use super::{unused::PropertyAccessOpts, BitCtx, Optimizer};
8use crate::{program_data::VarUsageInfoFlags, util::deeply_contains_this_expr};
9
10impl Optimizer<'_> {
12 pub(super) fn hoist_props_of_var(
13 &mut self,
14 n: &mut VarDeclarator,
15 ) -> Option<Vec<VarDeclarator>> {
16 if !self.options.hoist_props {
17 log_abort!("hoist_props: option is disabled");
18 return None;
19 }
20 if self.ctx.bit_ctx.contains(BitCtx::IsExported) {
21 log_abort!("hoist_props: Exported variable is not hoisted");
22 return None;
23 }
24 if self.ctx.in_top_level() && !self.options.top_level() {
25 log_abort!("hoist_props: Top-level variable is not hoisted");
26 return None;
27 }
28
29 if let Pat::Ident(name) = &mut n.name {
30 if name.id.ctxt == self.marks.top_level_ctxt
31 && self.options.top_retain.contains(&name.id.sym)
32 {
33 log_abort!("hoist_props: Variable `{}` is retained", name.id.sym);
34 return None;
35 }
36
37 if !self.may_add_ident() {
38 return None;
39 }
40
41 let usage = self.data.vars.get(&name.to_id())?;
44 if usage.mutated()
45 || usage.flags.intersects(
46 VarUsageInfoFlags::USED_ABOVE_DECL
47 .union(VarUsageInfoFlags::USED_AS_REF)
48 .union(VarUsageInfoFlags::USED_AS_ARG)
49 .union(VarUsageInfoFlags::INDEXED_WITH_DYNAMIC_KEY)
50 .union(VarUsageInfoFlags::USED_RECURSIVELY),
51 )
52 {
53 log_abort!("hoist_props: Variable `{}` is not a candidate", name.id);
54 return None;
55 }
56
57 if usage.accessed_props.is_empty() {
58 log_abort!(
59 "hoist_props: Variable `{}` is not accessed with known keys",
60 name.id
61 );
62 return None;
63 }
64
65 let accessed_props_count: u32 = usage.accessed_props.values().sum();
66 if accessed_props_count < usage.ref_count {
67 log_abort!(
68 "hoist_props: Variable `{}` is directly used without accessing its properties",
69 name.id
70 );
71 return None;
72 }
73
74 let mut unknown_used_props = self
76 .data
77 .vars
78 .get(&name.to_id())
79 .map(|v| v.accessed_props.clone())
80 .unwrap_or_default();
81
82 if let Some(Expr::Object(init)) = n.init.as_deref() {
83 for prop in &init.props {
84 let prop = match prop {
85 PropOrSpread::Spread(_) => return None,
86 PropOrSpread::Prop(prop) => prop,
87 #[cfg(swc_ast_unknown)]
88 _ => panic!("unable to access unknown nodes"),
89 };
90
91 match &**prop {
92 Prop::KeyValue(p) => {
93 if !is_expr_fine_for_hoist_props(&p.value) {
94 return None;
95 }
96
97 match &p.key {
98 PropName::Str(s) => {
99 if let Some(v) = unknown_used_props.get_mut(&s.value) {
100 *v = 0;
101 }
102 }
103 PropName::Ident(i) => {
104 if let Some(v) = unknown_used_props.get_mut(i.sym.borrow()) {
105 *v = 0;
106 }
107 }
108 _ => return None,
109 }
110 }
111 Prop::Shorthand(p) => {
112 if let Some(v) = unknown_used_props.get_mut(p.sym.borrow()) {
113 *v = 0;
114 }
115 }
116 _ => return None,
117 }
118 }
119 } else {
120 if self.mode.should_be_very_correct() {
121 return None;
122 }
123 }
124
125 if !unknown_used_props.iter().all(|(_, v)| *v == 0) {
126 log_abort!("[x] unknown used props: {:?}", unknown_used_props);
127 return None;
128 }
129
130 if let Some(init) = n.init.as_deref() {
131 self.mode.store(name.to_id(), init);
132 }
133
134 let mut new_vars = Vec::new();
135
136 let object = n.init.as_mut()?.as_mut_object()?;
137
138 self.changed = true;
139 report_change!(
140 "hoist_props: Hoisting properties of a variable `{}`",
141 name.id.sym
142 );
143
144 for prop in &mut object.props {
145 let prop = match prop {
146 PropOrSpread::Spread(_) => unreachable!(),
147 PropOrSpread::Prop(prop) => prop,
148 #[cfg(swc_ast_unknown)]
149 _ => panic!("unable to access unknown nodes"),
150 };
151
152 let value = match &mut **prop {
153 Prop::KeyValue(p) => p.value.take(),
154 Prop::Shorthand(p) => p.clone().into(),
155 _ => unreachable!(),
156 };
157
158 let (key, suffix) = match &**prop {
159 Prop::KeyValue(p) => match &p.key {
160 PropName::Ident(i) => (i.sym.clone().into(), i.sym.clone()),
161 PropName::Str(s) => (
162 s.value.clone(),
163 s.value
164 .code_points()
165 .map(|c| {
166 c.to_char()
167 .filter(|&c| Ident::is_valid_start(c))
168 .unwrap_or('$')
169 })
170 .collect::<String>()
171 .into(),
172 ),
173 _ => unreachable!(),
174 },
175 Prop::Shorthand(p) => (p.sym.clone().into(), p.sym.clone()),
176 _ => unreachable!(),
177 };
178
179 let new_var_name = private_ident!(format!("{}_{}", name.id.sym, suffix));
180
181 let new_var = VarDeclarator {
182 span: DUMMY_SP,
183 name: new_var_name.clone().into(),
184 init: Some(value),
185 definite: false,
186 };
187
188 self.vars
189 .hoisted_props
190 .insert((name.to_id(), key), new_var_name);
191
192 new_vars.push(new_var);
193 }
194 n.name.take();
196
197 return Some(new_vars);
198 }
199
200 None
201 }
202
203 pub(super) fn replace_props(&mut self, e: &mut Expr) {
204 let member = match e {
205 Expr::Member(m) => m,
206 Expr::OptChain(m) => match &mut *m.base {
207 OptChainBase::Member(m) => m,
208 _ => return,
209 },
210 _ => return,
211 };
212 if let Expr::Ident(obj) = &*member.obj {
213 let sym = match &member.prop {
214 MemberProp::Ident(i) => i.sym.borrow(),
215 MemberProp::Computed(e) => match &*e.expr {
216 Expr::Lit(Lit::Str(s)) => &s.value,
217 _ => return,
218 },
219 _ => return,
220 };
221
222 if let Some(value) = self
223 .vars
224 .hoisted_props
225 .get(&(obj.to_id(), sym.clone()))
226 .cloned()
227 {
228 report_change!("hoist_props: Inlining `{}.{}`", obj.sym, sym);
229 self.changed = true;
230 *e = value.into();
231 }
232 }
233 }
234}
235
236fn is_expr_fine_for_hoist_props(value: &Expr) -> bool {
237 match value {
238 Expr::Ident(..) | Expr::Lit(..) | Expr::Arrow(..) | Expr::Class(..) => true,
239
240 Expr::Fn(f) => !contains_this_expr(&f.function.body),
241
242 Expr::Unary(u) => match u.op {
243 op!("void") | op!("typeof") | op!("!") => is_expr_fine_for_hoist_props(&u.arg),
244 _ => false,
245 },
246
247 Expr::Array(a) => a.elems.iter().all(|elem| match elem {
248 Some(elem) => elem.spread.is_none() && is_expr_fine_for_hoist_props(&elem.expr),
249 None => true,
250 }),
251
252 Expr::Object(o) => o.props.iter().all(|prop| match prop {
253 PropOrSpread::Spread(_) => false,
254 PropOrSpread::Prop(p) => match &**p {
255 Prop::Shorthand(..) => true,
256 Prop::KeyValue(p) => is_expr_fine_for_hoist_props(&p.value),
257 _ => false,
258 },
259 #[cfg(swc_ast_unknown)]
260 _ => panic!("unable to access unknown nodes"),
261 }),
262
263 _ => false,
264 }
265}
266
267impl Optimizer<'_> {
268 pub(super) fn handle_property_access(&mut self, e: &mut Expr) {
270 if !self.options.props {
271 return;
272 }
273
274 if self
275 .ctx
276 .bit_ctx
277 .intersects(BitCtx::IsUpdateArg | BitCtx::IsCallee | BitCtx::IsExactLhsOfAssign)
278 {
279 return;
280 }
281
282 let me = match e {
283 Expr::Member(m) => m,
284 _ => return,
285 };
286
287 let key = match &me.prop {
288 MemberProp::Ident(prop) => prop,
289 _ => return,
290 };
291
292 let obj = match &mut *me.obj {
293 Expr::Object(o) => o,
294 _ => return,
295 };
296
297 let duplicate_prop = obj
298 .props
299 .iter()
300 .filter(|prop| match prop {
301 PropOrSpread::Spread(_) => false,
302 PropOrSpread::Prop(p) => match &**p {
303 Prop::Shorthand(p) => p.sym == key.sym,
304 Prop::KeyValue(p) => prop_name_eq(&p.key, &key.sym),
305 Prop::Assign(p) => p.key.sym == key.sym,
306 Prop::Getter(p) => prop_name_eq(&p.key, &key.sym),
307 Prop::Setter(p) => prop_name_eq(&p.key, &key.sym),
308 Prop::Method(p) => prop_name_eq(&p.key, &key.sym),
309 #[cfg(swc_ast_unknown)]
310 _ => panic!("unable to access unknown nodes"),
311 },
312 #[cfg(swc_ast_unknown)]
313 _ => panic!("unable to access unknown nodes"),
314 })
315 .count()
316 != 1;
317 if duplicate_prop {
318 return;
319 }
320
321 if obj.props.iter().any(|prop| match prop {
322 PropOrSpread::Spread(s) => self.should_preserve_property_access(
323 &s.expr,
324 PropertyAccessOpts {
325 allow_getter: false,
326 only_ident: false,
327 },
328 ),
329 PropOrSpread::Prop(p) => match &**p {
330 Prop::Shorthand(..) => false,
331 Prop::KeyValue(p) => {
332 p.key.is_computed()
333 || p.value.may_have_side_effects(self.ctx.expr_ctx)
334 || deeply_contains_this_expr(&p.value)
335 }
336 Prop::Assign(p) => {
337 p.value.may_have_side_effects(self.ctx.expr_ctx)
338 || deeply_contains_this_expr(&p.value)
339 }
340 Prop::Getter(p) => p.key.is_computed(),
341 Prop::Setter(p) => p.key.is_computed(),
342 Prop::Method(p) => p.key.is_computed(),
343 #[cfg(swc_ast_unknown)]
344 _ => panic!("unable to access unknown nodes"),
345 },
346 #[cfg(swc_ast_unknown)]
347 _ => panic!("unable to access unknown nodes"),
348 }) {
349 log_abort!("Property accesses should not be inlined to preserve side effects");
350 return;
351 }
352
353 for prop in &obj.props {
354 match prop {
355 PropOrSpread::Spread(_) => {}
356 PropOrSpread::Prop(p) => match &**p {
357 Prop::Shorthand(_) => {}
358 Prop::KeyValue(p) => {
359 if prop_name_eq(&p.key, &key.sym) {
360 report_change!(
361 "properties: Inlining a key-value property `{}`",
362 key.sym
363 );
364 self.changed = true;
365 *e = *p.value.clone();
366 return;
367 }
368 }
369 Prop::Assign(_) => {}
370 Prop::Getter(_) => {}
371 Prop::Setter(_) => {}
372 Prop::Method(_) => {}
373 #[cfg(swc_ast_unknown)]
374 _ => panic!("unable to access unknown nodes"),
375 },
376 #[cfg(swc_ast_unknown)]
377 _ => panic!("unable to access unknown nodes"),
378 }
379 }
380 }
381}