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, .. })) => {
125            if value.fract() == 0.0 {
126                Value::Number((value as i64).into())
127            } else {
128                match serde_json::Number::from_f64(value) {
129                    Some(n) => Value::Number(n),
130                    None => Value::Number((value as i64).into()),
131                }
132            }
133        }
134        Expr::Lit(Lit::Null(..)) => Value::Null,
135        Expr::Lit(Lit::Bool(v)) => Value::Bool(v.value),
136        Expr::Tpl(Tpl { quasis, .. }) => Value::String(match quasis.first() {
137            Some(TplElement {
138                cooked: Some(value),
139                ..
140            }) => value.to_string(),
141            _ => String::new(),
142        }),
143        _ => unreachable!("jsonify: Expr {:?} cannot be converted to json", e),
144    }
145}
146
147#[cfg(test)]
148mod tests {
149    use swc_ecma_transforms_testing::test;
150
151    use super::*;
152
153    test!(
154        ::swc_ecma_parser::Syntax::default(),
155        |_| json_parse(0),
156        simple_object,
157        "let a = {b: 'foo'}"
158    );
159
160    test!(
161        ::swc_ecma_parser::Syntax::default(),
162        |_| json_parse(0),
163        simple_arr,
164        "let a = ['foo']"
165    );
166
167    test!(
168        ::swc_ecma_parser::Syntax::default(),
169        |_| json_parse(0),
170        empty_object,
171        "const a = {};"
172    );
173
174    test!(
175        ::swc_ecma_parser::Syntax::default(),
176        |_| json_parse(15),
177        min_cost_15,
178        "const a = { b: 1, c: 2 };"
179    );
180
181    test!(
182        ::swc_ecma_parser::Syntax::default(),
183        |_| json_parse(0),
184        min_cost_0,
185        "const a = { b: 1, c: 2 };"
186    );
187
188    test!(
189        ::swc_ecma_parser::Syntax::default(),
190        |_| json_parse(0),
191        spread,
192        "const a = { ...a, b: 1 };"
193    );
194
195    test!(
196        ::swc_ecma_parser::Syntax::default(),
197        |_| json_parse(0),
198        object_method,
199        "const a = {
200        method(arg) {
201          return arg;
202        },
203        b: 1
204      };"
205    );
206
207    test!(
208        ::swc_ecma_parser::Syntax::default(),
209        |_| json_parse(0),
210        computed_property,
211        r#"const a = { b : "b_val", ["c"]: "c_val" };"#
212    );
213
214    test!(
215        ::swc_ecma_parser::Syntax::default(),
216        |_| json_parse(0),
217        invalid_numeric_key,
218        r#"const a ={ 77777777777777777.1: "foo" };"#
219    );
220
221    test!(
222        ::swc_ecma_parser::Syntax::default(),
223        |_| json_parse(0),
224        string,
225        r#"const a = { b: "b_val" };"#
226    );
227
228    test!(
229        ::swc_ecma_parser::Syntax::default(),
230        |_| json_parse(0),
231        string_single_quote_1,
232        r#"const a = { b: "'abc'" };"#,
233        ok_if_code_eq
234    );
235
236    test!(
237        ::swc_ecma_parser::Syntax::default(),
238        |_| json_parse(0),
239        string_single_quote_2,
240        r#"const a = { b: "ab\'c" };"#,
241        ok_if_code_eq
242    );
243
244    test!(
245        ::swc_ecma_parser::Syntax::default(),
246        |_| json_parse(0),
247        number,
248        "const a = { b: 1 };"
249    );
250
251    test!(
252        ::swc_ecma_parser::Syntax::default(),
253        |_| json_parse(0),
254        decimal_number,
255        "const a = { b: 24.0197, c: 0.0, d: 1.0 };"
256    );
257
258    test!(
259        ::swc_ecma_parser::Syntax::default(),
260        |_| json_parse(0),
261        null,
262        "const a = { b: null };"
263    );
264
265    test!(
266        ::swc_ecma_parser::Syntax::default(),
267        |_| json_parse(0),
268        boolean,
269        "const a = { b: false };"
270    );
271
272    test!(
273        ::swc_ecma_parser::Syntax::default(),
274        |_| json_parse(0),
275        array,
276        "const a = { b: [1, 'b_val', null] };"
277    );
278
279    test!(
280        ::swc_ecma_parser::Syntax::default(),
281        |_| json_parse(0),
282        nested_array,
283        "const a = { b: [1, ['b_val', { a: 1 }], null] };"
284    );
285
286    test!(
287        ::swc_ecma_parser::Syntax::default(),
288        |_| json_parse(0),
289        object,
290        "const a = { b: { c: 1 } };"
291    );
292
293    test!(
294        ::swc_ecma_parser::Syntax::default(),
295        |_| json_parse(0),
296        object_numeric_keys,
297        r#"const a = { 1: "123", 23: 45, b: "b_val" };"#
298    );
299    test!(
300        ::swc_ecma_parser::Syntax::default(),
301        |_| json_parse(0),
302        tpl,
303        r"const a = [`\x22\x21\x224`];"
304    );
305    test!(
306        ::swc_ecma_parser::Syntax::default(),
307        |_| json_parse(0),
308        tpl2,
309        r#"const a = [`1${b}2`];"#
310    );
311    test!(
312        ::swc_ecma_parser::Syntax::default(),
313        |_| json_parse(0),
314        tpl3,
315        r#"const a = [`1${0}2`];"#
316    );
317}