use std::iter::repeat_with;
use swc_common::{util::take::Take, DUMMY_SP};
use swc_ecma_ast::*;
use swc_ecma_utils::{
find_pat_ids, is_valid_prop_ident,
number::{parse_canonical_index, ToJsString},
private_ident,
};
use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith};
use super::Optimizer;
use crate::compress::optimize::is_left_access_to_arguments;
impl Optimizer<'_> {
pub(super) fn optimize_str_access_to_arguments(&mut self, e: &mut Expr) {
if !self.options.arguments {
return;
}
match e {
Expr::Member(MemberExpr { prop, .. }) => {
if let MemberProp::Computed(c) = prop {
if let Expr::Lit(Lit::Str(s)) = &mut *c.expr {
let Some(value) = s.value.as_str() else {
return;
};
if !value.starts_with(|c: char| c.is_ascii_alphabetic()) {
return;
}
if !is_valid_prop_ident(value) {
return;
}
self.changed = true;
report_change!("arguments: Optimizing computed access to arguments");
let name = s.take().value;
*prop = MemberProp::Ident(IdentName {
span: s.span,
sym: name.try_into_atom().unwrap(),
})
}
}
}
Expr::SuperProp(SuperPropExpr { prop, .. }) => {
if let SuperProp::Computed(c) = prop {
if let Expr::Lit(Lit::Str(s)) = &mut *c.expr {
let Some(value) = s.value.as_str() else {
return;
};
if !value.starts_with(|c: char| c.is_ascii_alphabetic()) {
return;
}
if !is_valid_prop_ident(value) {
return;
}
self.changed = true;
report_change!("arguments: Optimizing computed access to arguments");
let name = s.take().value;
*prop = SuperProp::Ident(IdentName {
span: s.span,
sym: name.try_into_atom().unwrap(),
})
}
}
}
_ => (),
};
}
pub(super) fn optimize_usage_of_arguments(&mut self, f: &mut Function) {
if !self.options.arguments {
return;
}
if f.params.iter().any(|param| match ¶m.pat {
Pat::Ident(BindingIdent {
id: Ident { sym, .. },
..
}) if &**sym == "arguments" => true,
Pat::Ident(i) => self
.data
.vars
.get(&i.id.to_id())
.map(|v| v.declared_count >= 2)
.unwrap_or(false),
_ => true,
}) {
return;
}
{
let data: Vec<Id> = find_pat_ids(&f.body);
if data.iter().any(|id| {
if id.0 == "arguments" {
return true;
}
false
}) {
return;
}
}
let mut v = ArgReplacer {
params: &mut f.params,
changed: false,
keep_fargs: self.options.keep_fargs,
prevent: false,
};
f.body.visit_mut_children_with(&mut v);
f.body.visit_mut_children_with(&mut v);
self.changed |= v.changed;
}
}
struct ArgReplacer<'a> {
params: &'a mut Vec<Param>,
changed: bool,
keep_fargs: bool,
prevent: bool,
}
impl ArgReplacer<'_> {
fn inject_params_if_required(&mut self, idx: usize) {
if idx < self.params.len() || self.keep_fargs {
return;
}
let new_args = idx + 1 - self.params.len();
self.changed = true;
report_change!("arguments: Injecting {} parameters", new_args);
let mut start = self.params.len();
self.params.extend(
repeat_with(|| {
let p = Param {
span: DUMMY_SP,
decorators: Default::default(),
pat: private_ident!(format!("argument_{}", start)).into(),
};
start += 1;
p
})
.take(new_args),
)
}
}
impl VisitMut for ArgReplacer<'_> {
noop_visit_mut_type!(fail);
fn visit_mut_arrow_expr(&mut self, _: &mut ArrowExpr) {}
fn visit_mut_assign_expr(&mut self, n: &mut AssignExpr) {
n.visit_mut_children_with(self);
if is_left_access_to_arguments(&n.left) {
self.prevent = true;
}
}
fn visit_mut_expr(&mut self, n: &mut Expr) {
if self.prevent {
return;
}
n.visit_mut_children_with(self);
let Some(idx) = argument_access_index(n) else {
return;
};
self.inject_params_if_required(idx);
if let Some(param) = self.params.get(idx) {
if let Pat::Ident(i) = ¶m.pat {
self.changed = true;
report_change!("arguments: Replacing access to arguments to normal reference");
*n = i.id.clone().into();
}
}
}
fn visit_mut_function(&mut self, _: &mut Function) {}
fn visit_mut_member_expr(&mut self, n: &mut MemberExpr) {
if self.prevent {
return;
}
n.obj.visit_mut_with(self);
if let MemberProp::Computed(c) = &mut n.prop {
c.visit_mut_with(self);
}
}
fn visit_mut_super_prop_expr(&mut self, n: &mut SuperPropExpr) {
if self.prevent {
return;
}
if let SuperProp::Computed(c) = &mut n.prop {
c.visit_mut_with(self);
}
}
}
fn argument_access_index(expr: &Expr) -> Option<usize> {
let Expr::Member(MemberExpr {
obj,
prop: MemberProp::Computed(computed),
..
}) = expr
else {
return None;
};
let Expr::Ident(Ident { sym, .. }) = &**obj else {
return None;
};
if &**sym != "arguments" {
return None;
}
match &*computed.expr {
Expr::Lit(Lit::Str(Str { value, .. })) => parse_canonical_index(value.as_str()?),
Expr::Lit(Lit::Num(Number { value, .. })) => parse_canonical_index(&value.to_js_string()),
_ => None,
}
}