|
1 | | -use serde_json::Value; |
| 1 | +use std::{collections::BTreeMap, fmt::Write}; |
| 2 | + |
| 3 | +use serde::{ |
| 4 | + ser::{SerializeMap, SerializeSeq}, |
| 5 | + Serialize, Serializer, |
| 6 | +}; |
| 7 | +use serde_json::value::RawValue; |
2 | 8 | use swc_atoms::Wtf8Atom; |
3 | | -use swc_common::{util::take::Take, Spanned, DUMMY_SP}; |
| 9 | +use swc_common::{Spanned, DUMMY_SP}; |
4 | 10 | use swc_ecma_ast::*; |
5 | 11 | use swc_ecma_transforms_base::perf::Parallel; |
6 | 12 | use swc_ecma_utils::{calc_literal_cost, member_expr, number::ToJsString, ExprFactory}; |
@@ -65,13 +71,11 @@ impl VisitMut for JsonParse { |
65 | 71 | Expr::Array(..) | Expr::Object(..) => { |
66 | 72 | let (is_lit, cost) = calc_literal_cost(&*expr, false); |
67 | 73 | 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 | | - }); |
| 74 | + let span = expr.span(); |
| 75 | + let value = jsonify(expr); |
72 | 76 |
|
73 | 77 | *expr = CallExpr { |
74 | | - span: expr.span(), |
| 78 | + span, |
75 | 79 | callee: member_expr!(Default::default(), DUMMY_SP, JSON.parse).as_callee(), |
76 | 80 | args: vec![Lit::Str(Str { |
77 | 81 | span: DUMMY_SP, |
@@ -109,61 +113,105 @@ fn wtf8_to_json_string(value: &Wtf8Atom) -> String { |
109 | 113 | result.push(ch); |
110 | 114 | } else { |
111 | 115 | // Lone surrogate - escape as \uXXXX |
112 | | - use std::fmt::Write; |
113 | 116 | write!(&mut result, "\\u{:04X}", cp.to_u32()).unwrap(); |
114 | 117 | } |
115 | 118 | } |
116 | 119 | result |
117 | 120 | } |
118 | 121 |
|
119 | | -fn 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) => n.value.to_js_string(), |
134 | | - _ => unreachable!(), |
| 122 | +/// Converts a finite ECMAScript number without using a saturating integer cast. |
| 123 | +fn json_number(value: f64) -> serde_json::Number { |
| 124 | + let is_i64 = value.fract() == 0.0 && value >= i64::MIN as f64 && value < i64::MAX as f64; |
| 125 | + let preserves_zero_sign = value != 0.0 || value.is_sign_positive(); |
| 126 | + |
| 127 | + if is_i64 && preserves_zero_sign { |
| 128 | + return (value as i64).into(); |
| 129 | + } |
| 130 | + |
| 131 | + serde_json::Number::from_f64(value) |
| 132 | + .unwrap_or_else(|| unreachable!("non-finite numbers require raw JSON serialization")) |
| 133 | +} |
| 134 | + |
| 135 | +/// Converts a property name to the string key created by an object literal. |
| 136 | +fn json_key(key: &PropName) -> String { |
| 137 | + match key { |
| 138 | + PropName::Str(s) => wtf8_to_json_string(&s.value), |
| 139 | + PropName::Ident(id) => id.sym.to_string(), |
| 140 | + PropName::Num(n) => n.value.to_js_string(), |
| 141 | + _ => unreachable!(), |
| 142 | + } |
| 143 | +} |
| 144 | + |
| 145 | +/// Serializes a literal expression to JSON text while preserving ECMAScript |
| 146 | +/// numeric values that are not representable by `serde_json::Value`. |
| 147 | +fn jsonify(e: &Expr) -> String { |
| 148 | + serde_json::to_string(&JsonLiteral(e)) |
| 149 | + .unwrap_or_else(|err| unreachable!("failed to serialize literal as JSON: {err}")) |
| 150 | +} |
| 151 | + |
| 152 | +/// Adapts an ECMAScript literal expression to Serde's data model. |
| 153 | +struct JsonLiteral<'a>(&'a Expr); |
| 154 | + |
| 155 | +impl Serialize for JsonLiteral<'_> { |
| 156 | + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> |
| 157 | + where |
| 158 | + S: Serializer, |
| 159 | + { |
| 160 | + match self.0 { |
| 161 | + Expr::Object(obj) => { |
| 162 | + let mut values = BTreeMap::new(); |
| 163 | + for prop in &obj.props { |
| 164 | + let PropOrSpread::Prop(prop) = prop else { |
| 165 | + unreachable!() |
| 166 | + }; |
| 167 | + let Prop::KeyValue(prop) = &**prop else { |
| 168 | + unreachable!() |
135 | 169 | }; |
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()), |
| 170 | + values.insert(json_key(&prop.key), &*prop.value); |
154 | 171 | } |
| 172 | + |
| 173 | + let mut map = serializer.serialize_map(Some(values.len()))?; |
| 174 | + for (key, value) in values { |
| 175 | + map.serialize_entry(&key, &JsonLiteral(value))?; |
| 176 | + } |
| 177 | + map.end() |
| 178 | + } |
| 179 | + Expr::Array(arr) => { |
| 180 | + let mut seq = serializer.serialize_seq(Some(arr.elems.len()))?; |
| 181 | + for value in &arr.elems { |
| 182 | + let value = value.as_ref().unwrap(); |
| 183 | + seq.serialize_element(&JsonLiteral(value.expr.as_ref()))?; |
| 184 | + } |
| 185 | + seq.end() |
| 186 | + } |
| 187 | + Expr::Lit(Lit::Str(Str { value, .. })) => { |
| 188 | + wtf8_to_json_string(value).serialize(serializer) |
| 189 | + } |
| 190 | + Expr::Lit(Lit::Num(Number { value, .. })) if value.is_infinite() => { |
| 191 | + let value = if value.is_sign_positive() { |
| 192 | + "2e308" |
| 193 | + } else { |
| 194 | + "-2e308" |
| 195 | + }; |
| 196 | + RawValue::from_string(value.into()) |
| 197 | + .unwrap() |
| 198 | + .serialize(serializer) |
| 199 | + } |
| 200 | + Expr::Lit(Lit::Num(Number { value, .. })) => json_number(*value).serialize(serializer), |
| 201 | + Expr::Lit(Lit::Null(..)) => serializer.serialize_none(), |
| 202 | + Expr::Lit(Lit::Bool(v)) => serializer.serialize_bool(v.value), |
| 203 | + Expr::Tpl(Tpl { quasis, .. }) => { |
| 204 | + let value = match quasis.first() { |
| 205 | + Some(TplElement { |
| 206 | + cooked: Some(value), |
| 207 | + .. |
| 208 | + }) => wtf8_to_json_string(value), |
| 209 | + _ => String::new(), |
| 210 | + }; |
| 211 | + value.serialize(serializer) |
155 | 212 | } |
| 213 | + _ => unreachable!("jsonify: Expr {:?} cannot be converted to json", self.0), |
156 | 214 | } |
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 | 215 | } |
168 | 216 | } |
169 | 217 |
|
|
0 commit comments