swc_ecma_minifier/compress/optimize/
arguments.rs

1use std::iter::repeat_with;
2
3use swc_common::{util::take::Take, DUMMY_SP};
4use swc_ecma_ast::*;
5use swc_ecma_utils::{find_pat_ids, is_valid_prop_ident, private_ident};
6use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith};
7
8use super::Optimizer;
9use crate::compress::optimize::is_left_access_to_arguments;
10
11/// Methods related to the option `arguments`.
12impl Optimizer<'_> {
13    ///
14    /// - `arguments['foo']` => `arguments.foo`
15    pub(super) fn optimize_str_access_to_arguments(&mut self, e: &mut Expr) {
16        if !self.options.arguments {
17            return;
18        }
19
20        match e {
21            Expr::Member(MemberExpr { prop, .. }) => {
22                if let MemberProp::Computed(c) = prop {
23                    if let Expr::Lit(Lit::Str(s)) = &mut *c.expr {
24                        let Some(value) = s.value.as_str() else {
25                            return;
26                        };
27
28                        if !value.starts_with(|c: char| c.is_ascii_alphabetic()) {
29                            return;
30                        }
31
32                        if !is_valid_prop_ident(value) {
33                            return;
34                        }
35
36                        self.changed = true;
37                        report_change!("arguments: Optimizing computed access to arguments");
38
39                        let name = s.take().value;
40                        *prop = MemberProp::Ident(IdentName {
41                            span: s.span,
42                            // SAFETY: s.value is guaranteed to be valid UTF-8 sequence from above.
43                            sym: name.try_into_atom().unwrap(),
44                        })
45                    }
46                }
47            }
48
49            Expr::SuperProp(SuperPropExpr { prop, .. }) => {
50                if let SuperProp::Computed(c) = prop {
51                    if let Expr::Lit(Lit::Str(s)) = &mut *c.expr {
52                        let Some(value) = s.value.as_str() else {
53                            return;
54                        };
55                        if !value.starts_with(|c: char| c.is_ascii_alphabetic()) {
56                            return;
57                        }
58
59                        if !is_valid_prop_ident(value) {
60                            return;
61                        }
62
63                        self.changed = true;
64                        report_change!("arguments: Optimizing computed access to arguments");
65
66                        let name = s.take().value;
67                        *prop = SuperProp::Ident(IdentName {
68                            span: s.span,
69                            // SAFETY: s.value is guaranteed to be valid UTF-8 sequence from above.
70                            sym: name.try_into_atom().unwrap(),
71                        })
72                    }
73                }
74            }
75
76            _ => (),
77        };
78    }
79
80    pub(super) fn optimize_usage_of_arguments(&mut self, f: &mut Function) {
81        if !self.options.arguments {
82            return;
83        }
84
85        if f.params.iter().any(|param| match &param.pat {
86            Pat::Ident(BindingIdent {
87                id: Ident { sym, .. },
88                ..
89            }) if &**sym == "arguments" => true,
90            Pat::Ident(i) => self
91                .data
92                .vars
93                .get(&i.id.to_id())
94                .map(|v| v.declared_count >= 2)
95                .unwrap_or(false),
96            _ => true,
97        }) {
98            return;
99        }
100
101        {
102            // If a function has a variable named `arguments`, we abort.
103            let data: Vec<Id> = find_pat_ids(&f.body);
104            if data.iter().any(|id| {
105                if id.0 == "arguments" {
106                    return true;
107                }
108                false
109            }) {
110                return;
111            }
112        }
113
114        let mut v = ArgReplacer {
115            params: &mut f.params,
116            changed: false,
117            keep_fargs: self.options.keep_fargs,
118            prevent: false,
119        };
120
121        // We visit body two time, to use simpler logic in `inject_params_if_required`
122        f.body.visit_mut_children_with(&mut v);
123        f.body.visit_mut_children_with(&mut v);
124
125        self.changed |= v.changed;
126    }
127}
128
129struct ArgReplacer<'a> {
130    params: &'a mut Vec<Param>,
131    changed: bool,
132    keep_fargs: bool,
133    prevent: bool,
134}
135
136impl ArgReplacer<'_> {
137    fn inject_params_if_required(&mut self, idx: usize) {
138        if idx < self.params.len() || self.keep_fargs {
139            return;
140        }
141        let new_args = idx + 1 - self.params.len();
142
143        self.changed = true;
144        report_change!("arguments: Injecting {} parameters", new_args);
145        let mut start = self.params.len();
146        self.params.extend(
147            repeat_with(|| {
148                let p = Param {
149                    span: DUMMY_SP,
150                    decorators: Default::default(),
151                    pat: private_ident!(format!("argument_{}", start)).into(),
152                };
153                start += 1;
154                p
155            })
156            .take(new_args),
157        )
158    }
159}
160
161impl VisitMut for ArgReplacer<'_> {
162    noop_visit_mut_type!(fail);
163
164    /// Noop.
165    fn visit_mut_arrow_expr(&mut self, _: &mut ArrowExpr) {}
166
167    fn visit_mut_assign_expr(&mut self, n: &mut AssignExpr) {
168        n.visit_mut_children_with(self);
169
170        if is_left_access_to_arguments(&n.left) {
171            self.prevent = true;
172        }
173    }
174
175    fn visit_mut_expr(&mut self, n: &mut Expr) {
176        if self.prevent {
177            return;
178        }
179
180        n.visit_mut_children_with(self);
181
182        if let Expr::Member(MemberExpr {
183            obj,
184            prop: MemberProp::Computed(c),
185            ..
186        }) = n
187        {
188            match &**obj {
189                Expr::Ident(Ident { sym, .. }) if &**sym == "arguments" => {
190                    match &*c.expr {
191                        Expr::Lit(Lit::Str(Str { value, .. })) => {
192                            let Some(value) = value.as_str() else {
193                                return;
194                            };
195                            let idx = value.parse::<usize>();
196                            let idx = match idx {
197                                Ok(v) => v,
198                                _ => return,
199                            };
200
201                            self.inject_params_if_required(idx);
202
203                            if let Some(param) = self.params.get(idx) {
204                                if let Pat::Ident(i) = &param.pat {
205                                    self.changed = true;
206                                    report_change!(
207                                        "arguments: Replacing access to arguments to normal \
208                                         reference"
209                                    );
210                                    *n = i.id.clone().into();
211                                }
212                            }
213                        }
214                        Expr::Lit(Lit::Num(Number { value, .. })) => {
215                            if value.fract() != 0.0 {
216                                // We ignores non-integer values.
217                                return;
218                            }
219
220                            let idx = value.round() as i64 as usize;
221
222                            self.inject_params_if_required(idx);
223
224                            //
225                            if let Some(param) = self.params.get(idx) {
226                                if let Pat::Ident(i) = &param.pat {
227                                    report_change!(
228                                        "arguments: Replacing access to arguments to normal \
229                                         reference"
230                                    );
231                                    self.changed = true;
232                                    *n = i.id.clone().into();
233                                }
234                            }
235                        }
236                        _ => {}
237                    }
238                }
239                _ => (),
240            }
241        }
242    }
243
244    /// Noop.
245    fn visit_mut_function(&mut self, _: &mut Function) {}
246
247    fn visit_mut_member_expr(&mut self, n: &mut MemberExpr) {
248        if self.prevent {
249            return;
250        }
251
252        n.obj.visit_mut_with(self);
253
254        if let MemberProp::Computed(c) = &mut n.prop {
255            c.visit_mut_with(self);
256        }
257    }
258
259    fn visit_mut_super_prop_expr(&mut self, n: &mut SuperPropExpr) {
260        if self.prevent {
261            return;
262        }
263
264        if let SuperProp::Computed(c) = &mut n.prop {
265            c.visit_mut_with(self);
266        }
267    }
268}