swc_ecma_transforms_optimization/
json_parse.rs1use serde_json::Value;
2use swc_atoms::Wtf8Atom;
3use swc_common::{util::take::Take, Spanned, DUMMY_SP};
4use swc_ecma_ast::*;
5use swc_ecma_transforms_base::perf::Parallel;
6use swc_ecma_utils::{calc_literal_cost, member_expr, ExprFactory};
7use swc_ecma_visit::{noop_visit_mut_type, visit_mut_pass, VisitMut, VisitMutWith};
8
9pub fn json_parse(min_cost: usize) -> impl Pass {
32 visit_mut_pass(JsonParse { min_cost })
33}
34
35struct JsonParse {
36 pub min_cost: usize,
37}
38
39impl Parallel for JsonParse {
40 fn create(&self) -> Self {
41 JsonParse {
42 min_cost: self.min_cost,
43 }
44 }
45
46 fn merge(&mut self, _: Self) {}
47}
48
49impl Default for JsonParse {
50 fn default() -> Self {
51 JsonParse { min_cost: 1024 }
52 }
53}
54
55impl VisitMut for JsonParse {
56 noop_visit_mut_type!(fail);
57
58 fn visit_mut_expr(&mut self, expr: &mut Expr) {
60 if self.min_cost == usize::MAX {
61 return;
62 }
63
64 let e = match expr {
65 Expr::Array(..) | Expr::Object(..) => {
66 let (is_lit, cost) = calc_literal_cost(&*expr, false);
67 if is_lit && cost >= self.min_cost {
68 let value =
69 serde_json::to_string(&jsonify(expr.take())).unwrap_or_else(|err| {
70 unreachable!("failed to serialize serde_json::Value as json: {}", err)
71 });
72
73 *expr = CallExpr {
74 span: expr.span(),
75 callee: member_expr!(Default::default(), DUMMY_SP, JSON.parse).as_callee(),
76 args: vec![Lit::Str(Str {
77 span: DUMMY_SP,
78 raw: None,
79 value: value.into(),
80 })
81 .as_arg()],
82 ..Default::default()
83 }
84 .into();
85 return;
86 }
87
88 expr
89 }
90 _ => expr,
91 };
92
93 e.visit_mut_children_with(self)
94 }
95}
96
97fn wtf8_to_json_string(value: &Wtf8Atom) -> String {
99 if let Some(s) = value.as_str() {
100 return s.to_string();
102 }
103
104 let mut result = String::with_capacity(value.len());
106 for cp in value.as_wtf8().code_points() {
107 if let Some(ch) = cp.to_char() {
108 result.push(ch);
110 } else {
111 use std::fmt::Write;
113 write!(&mut result, "\\u{:04X}", cp.to_u32()).unwrap();
114 }
115 }
116 result
117}
118
119fn jsonify(e: Expr) -> Value {
120 match e {
121 Expr::Object(obj) => Value::Object(
122 obj.props
123 .into_iter()
124 .map(|v| match v {
125 PropOrSpread::Prop(p) if p.is_key_value() => p.key_value().unwrap(),
126 _ => unreachable!(),
127 })
128 .map(|p: KeyValueProp| {
129 let value = jsonify(*p.value);
130 let key = match p.key {
131 PropName::Str(s) => wtf8_to_json_string(&s.value),
132 PropName::Ident(id) => id.sym.to_string(),
133 PropName::Num(n) => format!("{}", n.value),
134 _ => unreachable!(),
135 };
136 (key, value)
137 })
138 .collect(),
139 ),
140 Expr::Array(arr) => Value::Array(
141 arr.elems
142 .into_iter()
143 .map(|v| jsonify(*v.unwrap().expr))
144 .collect(),
145 ),
146 Expr::Lit(Lit::Str(Str { value, .. })) => Value::String(wtf8_to_json_string(&value)),
147 Expr::Lit(Lit::Num(Number { value, .. })) => {
148 if value.fract() == 0.0 {
149 Value::Number((value as i64).into())
150 } else {
151 match serde_json::Number::from_f64(value) {
152 Some(n) => Value::Number(n),
153 None => Value::Number((value as i64).into()),
154 }
155 }
156 }
157 Expr::Lit(Lit::Null(..)) => Value::Null,
158 Expr::Lit(Lit::Bool(v)) => Value::Bool(v.value),
159 Expr::Tpl(Tpl { quasis, .. }) => Value::String(match quasis.first() {
160 Some(TplElement {
161 cooked: Some(value),
162 ..
163 }) => wtf8_to_json_string(value),
164 _ => String::new(),
165 }),
166 _ => unreachable!("jsonify: Expr {:?} cannot be converted to json", e),
167 }
168}
169
170#[cfg(test)]
171mod tests {
172 use swc_ecma_transforms_testing::test;
173
174 use super::*;
175
176 test!(
177 ::swc_ecma_parser::Syntax::default(),
178 |_| json_parse(0),
179 simple_object,
180 "let a = {b: 'foo'}"
181 );
182
183 test!(
184 ::swc_ecma_parser::Syntax::default(),
185 |_| json_parse(0),
186 simple_arr,
187 "let a = ['foo']"
188 );
189
190 test!(
191 ::swc_ecma_parser::Syntax::default(),
192 |_| json_parse(0),
193 empty_object,
194 "const a = {};"
195 );
196
197 test!(
198 ::swc_ecma_parser::Syntax::default(),
199 |_| json_parse(15),
200 min_cost_15,
201 "const a = { b: 1, c: 2 };"
202 );
203
204 test!(
205 ::swc_ecma_parser::Syntax::default(),
206 |_| json_parse(0),
207 min_cost_0,
208 "const a = { b: 1, c: 2 };"
209 );
210
211 test!(
212 ::swc_ecma_parser::Syntax::default(),
213 |_| json_parse(0),
214 spread,
215 "const a = { ...a, b: 1 };"
216 );
217
218 test!(
219 ::swc_ecma_parser::Syntax::default(),
220 |_| json_parse(0),
221 object_method,
222 "const a = {
223 method(arg) {
224 return arg;
225 },
226 b: 1
227 };"
228 );
229
230 test!(
231 ::swc_ecma_parser::Syntax::default(),
232 |_| json_parse(0),
233 computed_property,
234 r#"const a = { b : "b_val", ["c"]: "c_val" };"#
235 );
236
237 test!(
238 ::swc_ecma_parser::Syntax::default(),
239 |_| json_parse(0),
240 invalid_numeric_key,
241 r#"const a ={ 77777777777777777.1: "foo" };"#
242 );
243
244 test!(
245 ::swc_ecma_parser::Syntax::default(),
246 |_| json_parse(0),
247 string,
248 r#"const a = { b: "b_val" };"#
249 );
250
251 test!(
252 ::swc_ecma_parser::Syntax::default(),
253 |_| json_parse(0),
254 string_single_quote_1,
255 r#"const a = { b: "'abc'" };"#,
256 ok_if_code_eq
257 );
258
259 test!(
260 ::swc_ecma_parser::Syntax::default(),
261 |_| json_parse(0),
262 string_single_quote_2,
263 r#"const a = { b: "ab\'c" };"#,
264 ok_if_code_eq
265 );
266
267 test!(
268 ::swc_ecma_parser::Syntax::default(),
269 |_| json_parse(0),
270 number,
271 "const a = { b: 1 };"
272 );
273
274 test!(
275 ::swc_ecma_parser::Syntax::default(),
276 |_| json_parse(0),
277 decimal_number,
278 "const a = { b: 24.0197, c: 0.0, d: 1.0 };"
279 );
280
281 test!(
282 ::swc_ecma_parser::Syntax::default(),
283 |_| json_parse(0),
284 null,
285 "const a = { b: null };"
286 );
287
288 test!(
289 ::swc_ecma_parser::Syntax::default(),
290 |_| json_parse(0),
291 boolean,
292 "const a = { b: false };"
293 );
294
295 test!(
296 ::swc_ecma_parser::Syntax::default(),
297 |_| json_parse(0),
298 array,
299 "const a = { b: [1, 'b_val', null] };"
300 );
301
302 test!(
303 ::swc_ecma_parser::Syntax::default(),
304 |_| json_parse(0),
305 nested_array,
306 "const a = { b: [1, ['b_val', { a: 1 }], null] };"
307 );
308
309 test!(
310 ::swc_ecma_parser::Syntax::default(),
311 |_| json_parse(0),
312 object,
313 "const a = { b: { c: 1 } };"
314 );
315
316 test!(
317 ::swc_ecma_parser::Syntax::default(),
318 |_| json_parse(0),
319 object_numeric_keys,
320 r#"const a = { 1: "123", 23: 45, b: "b_val" };"#
321 );
322 test!(
323 ::swc_ecma_parser::Syntax::default(),
324 |_| json_parse(0),
325 tpl,
326 r"const a = [`\x22\x21\x224`];"
327 );
328 test!(
329 ::swc_ecma_parser::Syntax::default(),
330 |_| json_parse(0),
331 tpl2,
332 r#"const a = [`1${b}2`];"#
333 );
334 test!(
335 ::swc_ecma_parser::Syntax::default(),
336 |_| json_parse(0),
337 tpl3,
338 r#"const a = [`1${0}2`];"#
339 );
340}