swc_ecma_compat_es2015/
duplicate_keys.rs

1use rustc_hash::FxHashSet;
2use swc_atoms::{Atom, Wtf8Atom};
3use swc_common::Spanned;
4use swc_ecma_ast::*;
5use swc_ecma_transforms_base::perf::Parallel;
6use swc_ecma_utils::quote_str;
7use swc_ecma_visit::{noop_visit_mut_type, visit_mut_pass, VisitMut, VisitMutWith};
8use swc_trace_macro::swc_trace;
9
10pub fn duplicate_keys() -> impl Pass {
11    visit_mut_pass(DuplicateKeys)
12}
13
14struct DuplicateKeys;
15
16impl Parallel for DuplicateKeys {
17    fn merge(&mut self, _: Self) {}
18
19    fn create(&self) -> Self {
20        Self
21    }
22}
23
24#[swc_trace]
25impl VisitMut for DuplicateKeys {
26    noop_visit_mut_type!(fail);
27
28    fn visit_mut_expr(&mut self, expr: &mut Expr) {
29        expr.visit_mut_children_with(self);
30
31        if let Expr::Object(ObjectLit { props, .. }) = expr {
32            let mut folder = PropFolder::default();
33            props.visit_mut_with(&mut folder);
34        }
35    }
36}
37
38#[derive(Default)]
39struct PropFolder {
40    getter_props: FxHashSet<Atom>,
41    setter_props: FxHashSet<Atom>,
42}
43
44#[inline]
45fn atom_from_wtf8(value: &Wtf8Atom) -> Atom {
46    value
47        .as_str()
48        .map(Atom::from)
49        .unwrap_or_else(|| Atom::from(value.to_string_lossy()))
50}
51
52#[swc_trace]
53impl VisitMut for PropFolder {
54    noop_visit_mut_type!(fail);
55
56    /// Noop
57    fn visit_mut_expr(&mut self, _: &mut Expr) {}
58
59    fn visit_mut_prop(&mut self, prop: &mut Prop) {
60        match prop {
61            Prop::Shorthand(ident) => {
62                //
63                if !self.getter_props.insert(ident.sym.clone())
64                    || !self.setter_props.insert(ident.sym.clone())
65                {
66                    *prop = Prop::KeyValue(KeyValueProp {
67                        key: PropName::Computed(ComputedPropName {
68                            span: ident.span,
69                            expr: quote_str!(ident.sym.clone()).into(),
70                        }),
71                        value: ident.clone().into(),
72                    })
73                }
74            }
75
76            Prop::Assign(..) => {}
77
78            Prop::Getter(..) => prop.visit_mut_children_with(&mut PropNameFolder {
79                props: &mut self.getter_props,
80            }),
81            Prop::Setter(..) => prop.visit_mut_children_with(&mut PropNameFolder {
82                props: &mut self.setter_props,
83            }),
84            _ => {
85                prop.visit_mut_children_with(&mut PropNameFolder {
86                    props: &mut self.getter_props,
87                });
88                prop.visit_mut_children_with(&mut PropNameFolder {
89                    props: &mut self.setter_props,
90                })
91            }
92        }
93    }
94}
95
96struct PropNameFolder<'a> {
97    props: &'a mut FxHashSet<Atom>,
98}
99
100#[swc_trace]
101impl VisitMut for PropNameFolder<'_> {
102    noop_visit_mut_type!(fail);
103
104    fn visit_mut_expr(&mut self, _: &mut Expr) {}
105
106    fn visit_mut_prop_name(&mut self, name: &mut PropName) {
107        let span = name.span();
108
109        match name {
110            PropName::Ident(ident) => {
111                if !self.props.insert(ident.sym.clone()) {
112                    *name = PropName::Computed(ComputedPropName {
113                        span,
114                        expr: Lit::Str(Str {
115                            span,
116                            raw: None,
117                            value: ident.sym.clone().into(),
118                        })
119                        .into(),
120                    })
121                }
122            }
123            PropName::Str(s) => {
124                if !self.props.insert(atom_from_wtf8(&s.value)) {
125                    *name = PropName::Computed(ComputedPropName {
126                        span: s.span,
127                        expr: s.clone().into(),
128                    })
129                }
130            }
131            PropName::Computed(ComputedPropName { expr, .. }) => {
132                // Computed property might collide
133                if let Expr::Lit(Lit::Str(Str { ref value, .. })) = &**expr {
134                    self.props.insert(atom_from_wtf8(value));
135                }
136            }
137            _ => {}
138        }
139    }
140}