swc_ecma_minifier/util/
size.rs

1use num_bigint::{BigInt as BigIntValue, Sign};
2use swc_common::SyntaxContext;
3use swc_ecma_ast::*;
4
5/// Give a rough size for everything
6pub trait Size {
7    fn size(&self) -> usize;
8}
9
10impl Size for Lit {
11    fn size(&self) -> usize {
12        match self {
13            Lit::Str(s) => s.value.len() + 2,
14            // would be !0 or !1
15            Lit::Bool(_) => 2,
16            Lit::Null(_) => 4,
17            Lit::Num(num) => num.value.size(),
18            Lit::BigInt(i) => i.value.size(),
19            Lit::Regex(r) => r.exp.len(),
20            Lit::JSXText(s) => s.value.len(),
21            #[cfg(swc_ast_unknown)]
22            _ => TODO,
23        }
24    }
25}
26
27impl Size for UnaryOp {
28    fn size(&self) -> usize {
29        use UnaryOp::*;
30        match self {
31            Minus | Plus | Bang | Tilde => 1,
32            TypeOf => 7,
33            Void => 5,
34            Delete => 7,
35            #[cfg(swc_ast_unknown)]
36            _ => TODO,
37        }
38    }
39}
40
41impl Size for UpdateOp {
42    fn size(&self) -> usize {
43        2
44    }
45}
46
47impl Size for BinaryOp {
48    fn size(&self) -> usize {
49        use BinaryOp::*;
50        match self {
51            Lt | Gt | Add | Sub | Mul | Div | Mod | BitOr | BitXor | BitAnd | EqEq | NotEq
52            | LtEq | GtEq | LShift | RShift | LogicalOr | LogicalAnd | Exp | NullishCoalescing
53            | EqEqEq | NotEqEq | ZeroFillRShift => self.as_str().len(),
54
55            In => 4,
56            InstanceOf => 12,
57            #[cfg(swc_ast_unknown)]
58            _ => TODO,
59        }
60    }
61}
62
63impl Size for AssignOp {
64    fn size(&self) -> usize {
65        self.as_str().len()
66    }
67}
68
69impl Size for PrivateName {
70    fn size(&self) -> usize {
71        // priv name can be mangled
72        2
73    }
74}
75
76// TODO: optimize
77impl Size for f64 {
78    fn size(&self) -> usize {
79        if self.fract() == 0.0 {
80            self.log10().ceil() as usize + 1
81        } else {
82            self.to_string().len()
83        }
84    }
85}
86
87#[allow(clippy::bool_to_int_with_if)]
88impl Size for BigIntValue {
89    fn size(&self) -> usize {
90        let sign = if let Sign::Minus = self.sign() { 1 } else { 0 };
91        // bits is bascially log2
92        // use this until https://github.com/rust-num/num-bigint/issues/57
93        let value = ((self.bits() as f64) / 10.0_f64.log2()).ceil() as usize + 1;
94        sign + value + 1 // n
95    }
96}
97
98/// Give a rough size for everything
99pub trait SizeWithCtxt {
100    fn size(&self, unresolved: SyntaxContext) -> usize;
101}
102
103const TODO: usize = 10000;
104
105impl SizeWithCtxt for Expr {
106    fn size(&self, unresolved: SyntaxContext) -> usize {
107        match self {
108            Expr::Lit(lit) => lit.size(),
109            Expr::Ident(id) => id.size(unresolved),
110
111            Expr::Bin(BinExpr {
112                op, left, right, ..
113            }) => op.size() + left.size(unresolved) + right.size(unresolved),
114
115            Expr::Unary(UnaryExpr { arg, op, .. }) => op.size() + arg.size(unresolved),
116
117            Expr::Call(CallExpr { callee, args, .. }) => {
118                callee.size(unresolved) + args.size(unresolved) + 2
119            }
120
121            Expr::New(NewExpr { callee, args, .. }) => {
122                args.as_ref().map_or(0, |args| args.size(unresolved) + 2)
123                    + 4
124                    + callee.size(unresolved)
125            }
126
127            Expr::Member(m) => m.obj.size(unresolved) + m.prop.size(unresolved),
128            Expr::SuperProp(s) => 6 + s.prop.size(unresolved),
129            Expr::Update(e) => e.arg.size(unresolved) + e.op.size(),
130
131            Expr::Assign(AssignExpr {
132                op, left, right, ..
133            }) => left.size(unresolved) + op.size() + right.size(unresolved),
134
135            Expr::Seq(e) => {
136                e.exprs
137                    .iter()
138                    .map(|v| v.size(unresolved) + 1)
139                    .sum::<usize>()
140                    - 1
141            }
142
143            Expr::This(_) => 4,
144            Expr::Array(a) => 2 + a.elems.size(unresolved),
145            Expr::Object(o) => 2 + o.props.size(unresolved),
146
147            Expr::Yield(YieldExpr { arg, delegate, .. }) => {
148                6 + *delegate as usize + arg.as_ref().map_or(0, |a| a.size(unresolved))
149            }
150            Expr::Await(a) => 6 + a.arg.size(unresolved),
151            Expr::Cond(CondExpr {
152                test, cons, alt, ..
153            }) => test.size(unresolved) + 1 + cons.size(unresolved) + 1 + alt.size(unresolved),
154            Expr::Tpl(t) => t.size(unresolved),
155            Expr::TaggedTpl(t) => t.tag.size(unresolved) + t.tpl.size(unresolved),
156
157            Expr::Arrow(ArrowExpr {
158                params,
159                body,
160                is_async,
161                ..
162            }) => match &**body {
163                BlockStmtOrExpr::BlockStmt(_) => TODO,
164                BlockStmtOrExpr::Expr(e) => {
165                    let p = match &params[..] {
166                        [] => 2,
167                        [Pat::Ident(_)] => 1,
168                        _ => 2 + params.size(unresolved),
169                    };
170                    let a = if *is_async {
171                        5 + usize::from(params.len() != 1)
172                    } else {
173                        0
174                    };
175
176                    p + a + 2 + e.size(unresolved)
177                }
178                #[cfg(swc_ast_unknown)]
179                _ => TODO,
180            },
181            Expr::Fn(_) => TODO,
182            Expr::Class(_) => TODO,
183
184            Expr::MetaProp(m) => match m.kind {
185                MetaPropKind::NewTarget => 10,
186                MetaPropKind::ImportMeta => 11,
187                #[cfg(swc_ast_unknown)]
188                _ => TODO,
189            },
190            Expr::PrivateName(p) => p.size(),
191            Expr::OptChain(p) => match &*p.base {
192                OptChainBase::Member(m) => 1 + m.obj.size(unresolved) + m.prop.size(unresolved),
193                OptChainBase::Call(c) => {
194                    1 + c.callee.size(unresolved) + c.args.size(unresolved) + 2
195                }
196                #[cfg(swc_ast_unknown)]
197                _ => TODO,
198            },
199
200            Expr::Paren(p) => 2 + p.expr.size(unresolved),
201            Expr::Invalid(_) => 0,
202
203            Expr::JSXMember(_) => TODO,
204            Expr::JSXNamespacedName(_) => TODO,
205            Expr::JSXEmpty(_) => TODO,
206            Expr::JSXElement(_) => TODO,
207            Expr::JSXFragment(_) => TODO,
208            Expr::TsTypeAssertion(_) => TODO,
209            Expr::TsConstAssertion(_) => TODO,
210            Expr::TsNonNull(_) => TODO,
211            Expr::TsAs(_) => TODO,
212            Expr::TsInstantiation(_) => TODO,
213            Expr::TsSatisfies(_) => TODO,
214            #[cfg(swc_ast_unknown)]
215            _ => TODO,
216        }
217    }
218}
219
220impl SizeWithCtxt for Ident {
221    fn size(&self, unresolved: SyntaxContext) -> usize {
222        if self.ctxt == unresolved {
223            self.sym.len()
224        } else {
225            1
226        }
227    }
228}
229
230impl SizeWithCtxt for Callee {
231    fn size(&self, unresolved: SyntaxContext) -> usize {
232        match self {
233            Callee::Super(_) => 5,
234            Callee::Import(_) => 6,
235            Callee::Expr(e) => e.size(unresolved),
236            #[cfg(swc_ast_unknown)]
237            _ => TODO,
238        }
239    }
240}
241
242impl SizeWithCtxt for ExprOrSpread {
243    fn size(&self, unresolved: SyntaxContext) -> usize {
244        let mut c = 0;
245
246        if self.spread.is_some() {
247            c += 3;
248        }
249
250        c += self.expr.size(unresolved);
251
252        c
253    }
254}
255
256impl SizeWithCtxt for MemberProp {
257    fn size(&self, unresolved: SyntaxContext) -> usize {
258        match self {
259            MemberProp::Ident(id) => 1 + id.sym.len(),
260            MemberProp::PrivateName(priv_name) => 1 + priv_name.size(),
261            MemberProp::Computed(c) => 2 + c.expr.size(unresolved),
262            #[cfg(swc_ast_unknown)]
263            _ => TODO,
264        }
265    }
266}
267
268impl SizeWithCtxt for SuperProp {
269    fn size(&self, unresolved: SyntaxContext) -> usize {
270        match self {
271            SuperProp::Ident(id) => 1 + id.sym.len(),
272            SuperProp::Computed(c) => 2 + c.expr.size(unresolved),
273            #[cfg(swc_ast_unknown)]
274            _ => TODO,
275        }
276    }
277}
278
279impl SizeWithCtxt for Pat {
280    fn size(&self, unresolved: SyntaxContext) -> usize {
281        match self {
282            Pat::Ident(id) => id.size(unresolved),
283            Pat::Array(a) => 2 + a.elems.size(unresolved),
284            Pat::Rest(r) => 3 + r.arg.size(unresolved),
285            Pat::Object(o) => 2 + o.props.size(unresolved),
286            Pat::Assign(a) => a.left.size(unresolved) + 1 + a.right.size(unresolved),
287            Pat::Invalid(_) => 0,
288            Pat::Expr(e) => e.size(unresolved),
289            #[cfg(swc_ast_unknown)]
290            _ => TODO,
291        }
292    }
293}
294
295impl SizeWithCtxt for AssignTarget {
296    fn size(&self, unresolved: SyntaxContext) -> usize {
297        match self {
298            AssignTarget::Simple(e) => e.size(unresolved),
299            AssignTarget::Pat(p) => p.size(unresolved),
300            #[cfg(swc_ast_unknown)]
301            _ => TODO,
302        }
303    }
304}
305
306impl SizeWithCtxt for SimpleAssignTarget {
307    fn size(&self, unresolved: SyntaxContext) -> usize {
308        match self {
309            SimpleAssignTarget::Ident(e) => {
310                if e.ctxt == unresolved {
311                    e.sym.len()
312                } else {
313                    1
314                }
315            }
316            SimpleAssignTarget::Member(e) => e.obj.size(unresolved) + e.prop.size(unresolved),
317            SimpleAssignTarget::SuperProp(e) => 6 + e.prop.size(unresolved),
318            SimpleAssignTarget::Paren(e) => 2 + e.expr.size(unresolved),
319            SimpleAssignTarget::OptChain(e) => match &*e.base {
320                OptChainBase::Member(m) => 1 + m.obj.size(unresolved) + m.prop.size(unresolved),
321                OptChainBase::Call(c) => {
322                    1 + c.callee.size(unresolved) + c.args.size(unresolved) + 2
323                }
324                #[cfg(swc_ast_unknown)]
325                _ => TODO,
326            },
327            SimpleAssignTarget::TsAs(_)
328            | SimpleAssignTarget::TsSatisfies(_)
329            | SimpleAssignTarget::TsNonNull(_)
330            | SimpleAssignTarget::TsTypeAssertion(_)
331            | SimpleAssignTarget::TsInstantiation(_)
332            | SimpleAssignTarget::Invalid(_) => TODO,
333            #[cfg(swc_ast_unknown)]
334            _ => TODO,
335        }
336    }
337}
338
339impl SizeWithCtxt for AssignTargetPat {
340    fn size(&self, unresolved: SyntaxContext) -> usize {
341        match self {
342            AssignTargetPat::Array(a) => 2 + a.elems.size(unresolved),
343            AssignTargetPat::Object(o) => 2 + o.props.size(unresolved),
344            AssignTargetPat::Invalid(_) => unreachable!(),
345            #[cfg(swc_ast_unknown)]
346            _ => TODO,
347        }
348    }
349}
350
351impl SizeWithCtxt for PropName {
352    fn size(&self, unresolved: SyntaxContext) -> usize {
353        match self {
354            PropName::Ident(id) => id.sym.len(),
355            PropName::Str(s) => s.value.len(),
356            PropName::Num(n) => n.value.size(),
357            PropName::Computed(c) => 2 + c.expr.size(unresolved),
358            PropName::BigInt(n) => n.value.size(),
359            #[cfg(swc_ast_unknown)]
360            _ => TODO,
361        }
362    }
363}
364
365impl SizeWithCtxt for ObjectPatProp {
366    fn size(&self, unresolved: SyntaxContext) -> usize {
367        match self {
368            ObjectPatProp::KeyValue(k) => k.key.size(unresolved) + 1 + k.value.size(unresolved),
369            ObjectPatProp::Assign(a) => {
370                a.key.sym.len() + a.value.as_ref().map_or(0, |v| v.size(unresolved) + 1)
371            }
372            ObjectPatProp::Rest(r) => 3 + r.arg.size(unresolved),
373            #[cfg(swc_ast_unknown)]
374            _ => TODO,
375        }
376    }
377}
378
379impl SizeWithCtxt for PropOrSpread {
380    fn size(&self, unresolved: SyntaxContext) -> usize {
381        match self {
382            PropOrSpread::Spread(s) => 3 + s.expr.size(unresolved),
383            PropOrSpread::Prop(p) => match &**p {
384                Prop::Shorthand(s) => s.sym.len(),
385                Prop::KeyValue(KeyValueProp { key, value }) => {
386                    key.size(unresolved) + 1 + value.size(unresolved)
387                }
388                // where is Prop::Assign valid?
389                Prop::Assign(_) => TODO,
390                Prop::Getter(_) => TODO,
391                Prop::Setter(_) => TODO,
392                Prop::Method(_) => TODO,
393                #[cfg(swc_ast_unknown)]
394                _ => TODO,
395            },
396            #[cfg(swc_ast_unknown)]
397            _ => TODO,
398        }
399    }
400}
401
402impl SizeWithCtxt for Tpl {
403    fn size(&self, unresolved: SyntaxContext) -> usize {
404        let Self { exprs, quasis, .. } = self;
405        let expr_len: usize = exprs.iter().map(|e| e.size(unresolved) + 3).sum();
406        let str_len: usize = quasis.iter().map(|q| q.raw.len()).sum();
407        2 + expr_len + str_len
408    }
409}
410
411impl<T: SizeWithCtxt> SizeWithCtxt for Vec<T> {
412    fn size(&self, unresolved: SyntaxContext) -> usize {
413        let mut c = 0;
414
415        for item in self.iter() {
416            c += item.size(unresolved) + 1; // comma
417        }
418
419        if !self.is_empty() {
420            c -= 1;
421        }
422
423        c
424    }
425}
426
427impl<T: SizeWithCtxt> SizeWithCtxt for Vec<Option<T>> {
428    fn size(&self, unresolved: SyntaxContext) -> usize {
429        let mut c = 0;
430
431        for item in self.iter() {
432            c += 1; // comma
433            if let Some(item) = item {
434                c += item.size(unresolved); // extra comma
435            }
436        }
437
438        c -= match self.last() {
439            // if empty or last is none, no need to remove dangling comma
440            None | Some(None) => 0,
441            _ => 1,
442        };
443
444        c
445    }
446}