swc_ecma_transforms_optimization/
json_parse.rs

1use serde_json::Value;
2use swc_common::{util::take::Take, Spanned, DUMMY_SP};
3use swc_ecma_ast::*;
4use swc_ecma_transforms_base::perf::Parallel;
5use swc_ecma_utils::{calc_literal_cost, member_expr, ExprFactory};
6use swc_ecma_visit::{noop_visit_mut_type, visit_mut_pass, VisitMut, VisitMutWith};
7
8/// Transform to optimize performance of literals.
9///
10///
11/// This transform converts pure object literals like
12///
13/// ```js
14/// {a: 1, b: 2}
15/// ```
16///
17/// to
18///
19/// ```js
20/// JSON.parse('{"a":1, "b"}')
21/// ```
22///
23/// # Conditions
24/// If any of the conditions below is matched, pure object literal is converter
25/// to `JSON.parse`
26///
27///   - Object literal is deeply nested (threshold: )
28///
29/// See https://github.com/swc-project/swc/issues/409
30pub fn json_parse(min_cost: usize) -> impl Pass {
31    visit_mut_pass(JsonParse { min_cost })
32}
33
34struct JsonParse {
35    pub min_cost: usize,
36}
37
38impl Parallel for JsonParse {
39    fn create(&self) -> Self {
40        JsonParse {
41            min_cost: self.min_cost,
42        }
43    }
44
45    fn merge(&mut self, _: Self) {}
46}
47
48impl Default for JsonParse {
49    fn default() -> Self {
50        JsonParse { min_cost: 1024 }
51    }
52}
53
54impl VisitMut for JsonParse {
55    noop_visit_mut_type!(fail);
56
57    /// Handles parent expressions before child expressions.
58    fn visit_mut_expr(&mut self, expr: &mut Expr) {
59        if self.min_cost == usize::MAX {
60            return;
61        }
62
63        let e = match expr {
64            Expr::Array(..) | Expr::Object(..) => {
65                let (is_lit, cost) = calc_literal_cost(&*expr, false);
66                if is_lit && cost >= self.min_cost {
67                    let value =
68                        serde_json::to_string(&jsonify(expr.take())).unwrap_or_else(|err| {
69                            unreachable!("failed to serialize serde_json::Value as json: {}", err)
70                        });
71
72                    *expr = CallExpr {
73                        span: expr.span(),
74                        callee: member_expr!(Default::default(), DUMMY_SP, JSON.parse).as_callee(),
75                        args: vec![Lit::Str(Str {
76                            span: DUMMY_SP,
77                            raw: None,
78                            value: value.into(),
79                        })
80                        .as_arg()],
81                        ..Default::default()
82                    }
83                    .into();
84                    return;
85                }
86
87                expr
88            }
89            _ => expr,
90        };
91
92        e.visit_mut_children_with(self)
93    }
94}
95
96fn jsonify(e: Expr) -> Value {
97    match e {
98        Expr::Object(obj) => Value::Object(
99            obj.props
100                .into_iter()
101                .map(|v| match v {
102                    PropOrSpread::Prop(p) if p.is_key_value() => p.key_value().unwrap(),
103                    _ => unreachable!(),
104                })
105                .map(|p: KeyValueProp| {
106                    let value = jsonify(*p.value);
107                    let key = match p.key {
108                        PropName::Str(s) => s.value.to_string(),
109                        PropName::Ident(id) => id.sym.to_string(),
110                        PropName::Num(n) => format!("{}", n.value),
111                        _ => unreachable!(),
112                    };
113                    (key, value)
114                })
115                .collect(),
116        ),
117        Expr::Array(arr) => Value::Array(
118            arr.elems
119                .into_iter()
120                .map(|v| jsonify(*v.unwrap().expr))
121                .collect(),
122        ),
123        Expr::Lit(Lit::Str(Str { value, .. })) => Value::String(value.to_string()),
124        Expr::Lit(Lit::Num(Number { value, .. })) => Value::Number((value as i64).into()),
125        Expr::Lit(Lit::Null(..)) => Value::Null,
126        Expr::Lit(Lit::Bool(v)) => Value::Bool(v.value),
127        Expr::Tpl(Tpl { quasis, .. }) => Value::String(match quasis.first() {
128            Some(TplElement {
129                cooked: Some(value),
130                ..
131            }) => value.to_string(),
132            _ => String::new(),
133        }),
134        _ => unreachable!("jsonify: Expr {:?} cannot be converted to json", e),
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use swc_ecma_transforms_testing::test;
141
142    use super::*;
143
144    test!(
145        ::swc_ecma_parser::Syntax::default(),
146        |_| json_parse(0),
147        simple_object,
148        "let a = {b: 'foo'}"
149    );
150
151    test!(
152        ::swc_ecma_parser::Syntax::default(),
153        |_| json_parse(0),
154        simple_arr,
155        "let a = ['foo']"
156    );
157
158    test!(
159        ::swc_ecma_parser::Syntax::default(),
160        |_| json_parse(0),
161        empty_object,
162        "const a = {};"
163    );
164
165    test!(
166        ::swc_ecma_parser::Syntax::default(),
167        |_| json_parse(15),
168        min_cost_15,
169        "const a = { b: 1, c: 2 };"
170    );
171
172    test!(
173        ::swc_ecma_parser::Syntax::default(),
174        |_| json_parse(0),
175        min_cost_0,
176        "const a = { b: 1, c: 2 };"
177    );
178
179    test!(
180        ::swc_ecma_parser::Syntax::default(),
181        |_| json_parse(0),
182        spread,
183        "const a = { ...a, b: 1 };"
184    );
185
186    test!(
187        ::swc_ecma_parser::Syntax::default(),
188        |_| json_parse(0),
189        object_method,
190        "const a = {
191        method(arg) {
192          return arg;
193        },
194        b: 1
195      };"
196    );
197
198    test!(
199        ::swc_ecma_parser::Syntax::default(),
200        |_| json_parse(0),
201        computed_property,
202        r#"const a = { b : "b_val", ["c"]: "c_val" };"#
203    );
204
205    test!(
206        ::swc_ecma_parser::Syntax::default(),
207        |_| json_parse(0),
208        invalid_numeric_key,
209        r#"const a ={ 77777777777777777.1: "foo" };"#
210    );
211
212    test!(
213        ::swc_ecma_parser::Syntax::default(),
214        |_| json_parse(0),
215        string,
216        r#"const a = { b: "b_val" };"#
217    );
218
219    test!(
220        ::swc_ecma_parser::Syntax::default(),
221        |_| json_parse(0),
222        string_single_quote_1,
223        r#"const a = { b: "'abc'" };"#,
224        ok_if_code_eq
225    );
226
227    test!(
228        ::swc_ecma_parser::Syntax::default(),
229        |_| json_parse(0),
230        string_single_quote_2,
231        r#"const a = { b: "ab\'c" };"#,
232        ok_if_code_eq
233    );
234
235    test!(
236        ::swc_ecma_parser::Syntax::default(),
237        |_| json_parse(0),
238        number,
239        "const a = { b: 1 };"
240    );
241
242    test!(
243        ::swc_ecma_parser::Syntax::default(),
244        |_| json_parse(0),
245        null,
246        "const a = { b: null };"
247    );
248
249    test!(
250        ::swc_ecma_parser::Syntax::default(),
251        |_| json_parse(0),
252        boolean,
253        "const a = { b: false };"
254    );
255
256    test!(
257        ::swc_ecma_parser::Syntax::default(),
258        |_| json_parse(0),
259        array,
260        "const a = { b: [1, 'b_val', null] };"
261    );
262
263    test!(
264        ::swc_ecma_parser::Syntax::default(),
265        |_| json_parse(0),
266        nested_array,
267        "const a = { b: [1, ['b_val', { a: 1 }], null] };"
268    );
269
270    test!(
271        ::swc_ecma_parser::Syntax::default(),
272        |_| json_parse(0),
273        object,
274        "const a = { b: { c: 1 } };"
275    );
276
277    test!(
278        ::swc_ecma_parser::Syntax::default(),
279        |_| json_parse(0),
280        object_numeric_keys,
281        r#"const a = { 1: "123", 23: 45, b: "b_val" };"#
282    );
283    test!(
284        ::swc_ecma_parser::Syntax::default(),
285        |_| json_parse(0),
286        tpl,
287        r"const a = [`\x22\x21\x224`];"
288    );
289    test!(
290        ::swc_ecma_parser::Syntax::default(),
291        |_| json_parse(0),
292        tpl2,
293        r#"const a = [`1${b}2`];"#
294    );
295    test!(
296        ::swc_ecma_parser::Syntax::default(),
297        |_| json_parse(0),
298        tpl3,
299        r#"const a = [`1${0}2`];"#
300    );
301}