@@ -13,7 +13,7 @@ use vite_path::AbsolutePath;
1313
1414/// The result of statically analyzing a single config field's value.
1515#[ derive( Debug , Clone , PartialEq , Eq ) ]
16- pub enum StaticFieldValue {
16+ pub enum FieldValue {
1717 /// The field value was successfully extracted as a JSON literal.
1818 Json ( serde_json:: Value ) ,
1919 /// The field exists but its value is not a pure JSON literal (e.g. contains
@@ -27,11 +27,11 @@ pub enum StaticFieldValue {
2727/// no `export default`, or the default export is not an object literal).
2828/// The caller should fall back to a runtime evaluation (e.g. NAPI).
2929/// - `Some(map)` — the default export object was successfully located.
30- /// - Key maps to [`StaticFieldValue ::Json`] — field value was extracted.
31- /// - Key maps to [`StaticFieldValue ::NonStatic`] — field exists but its value
30+ /// - Key maps to [`FieldValue ::Json`] — field value was extracted.
31+ /// - Key maps to [`FieldValue ::NonStatic`] — field exists but its value
3232/// cannot be represented as pure JSON.
3333/// - Key absent — the field does not exist in the object.
34- pub type StaticConfig = Option < FxHashMap < Box < str > , StaticFieldValue > > ;
34+ pub type StaticConfig = Option < FxHashMap < Box < str > , FieldValue > > ;
3535
3636/// Config file names to try, in priority order.
3737/// This matches Vite's `DEFAULT_CONFIG_FILES`:
@@ -84,11 +84,7 @@ pub fn resolve_static_config(dir: &AbsolutePath) -> StaticConfig {
8484fn parse_json_config ( source : & str ) -> StaticConfig {
8585 let value: serde_json:: Value = serde_json:: from_str ( source) . ok ( ) ?;
8686 let obj = value. as_object ( ) ?;
87- Some (
88- obj. iter ( )
89- . map ( |( k, v) | ( Box :: from ( k. as_str ( ) ) , StaticFieldValue :: Json ( v. clone ( ) ) ) )
90- . collect ( ) ,
91- )
87+ Some ( obj. iter ( ) . map ( |( k, v) | ( Box :: from ( k. as_str ( ) ) , FieldValue :: Json ( v. clone ( ) ) ) ) . collect ( ) )
9288}
9389
9490/// Parse a JS/TS config file, extracting the default export object's fields.
@@ -166,11 +162,11 @@ fn extract_config_from_expr(expr: &Expression<'_>) -> StaticConfig {
166162
167163/// Extract fields from an object expression, converting each value to JSON.
168164/// Fields whose values cannot be represented as pure JSON are recorded as
169- /// [`StaticFieldValue ::NonStatic`]. Spread elements and computed properties
165+ /// [`FieldValue ::NonStatic`]. Spread elements and computed properties
170166/// are not representable so they are silently skipped (their keys are unknown).
171167fn extract_object_fields (
172168 obj : & oxc_ast:: ast:: ObjectExpression < ' _ > ,
173- ) -> FxHashMap < Box < str > , StaticFieldValue > {
169+ ) -> FxHashMap < Box < str > , FieldValue > {
174170 let mut map = FxHashMap :: default ( ) ;
175171
176172 for prop in & obj. properties {
@@ -187,28 +183,25 @@ fn extract_object_fields(
187183 continue ;
188184 } ;
189185
190- let value =
191- expr_to_json ( & prop. value ) . map_or ( StaticFieldValue :: NonStatic , StaticFieldValue :: Json ) ;
186+ let value = expr_to_json ( & prop. value ) . map_or ( FieldValue :: NonStatic , FieldValue :: Json ) ;
192187 map. insert ( Box :: from ( key. as_ref ( ) ) , value) ;
193188 }
194189
195190 map
196191}
197192
198- /// Convert an f64 to a JSON value, preserving integers when possible .
199- # [ expect ( clippy :: cast_possible_truncation , clippy :: cast_precision_loss ) ]
193+ /// Convert an f64 to a JSON value following `JSON.stringify` semantics .
194+ /// `NaN`, `Infinity`, `-Infinity` become `null`; `-0` becomes `0`.
200195fn f64_to_json_number ( value : f64 ) -> serde_json:: Value {
201- // If the value is a whole number that fits in i64, use integer representation
196+ // fract() == 0.0 ensures the value is a whole number, so the cast is lossless.
197+ #[ expect( clippy:: cast_possible_truncation) ]
202198 if value. fract ( ) == 0.0
203- && value. is_finite ( )
204- && value >= i64:: MIN as f64
205- && value <= i64:: MAX as f64
199+ && let Ok ( i) = i64:: try_from ( value as i128 )
206200 {
207- serde_json:: Value :: Number ( serde_json:: Number :: from ( value as i64 ) )
208- } else if let Some ( n) = serde_json:: Number :: from_f64 ( value) {
209- serde_json:: Value :: Number ( n)
201+ serde_json:: Value :: from ( i)
210202 } else {
211- serde_json:: Value :: Null
203+ // From<f64> for Value: finite → Number, NaN/Infinity → Null
204+ serde_json:: Value :: from ( value)
212205 }
213206}
214207
@@ -285,24 +278,20 @@ mod tests {
285278
286279 /// Helper: parse JS/TS source, unwrap the `Some` (asserting it's analyzable),
287280 /// and return the field map.
288- fn parse ( source : & str ) -> FxHashMap < Box < str > , StaticFieldValue > {
281+ fn parse ( source : & str ) -> FxHashMap < Box < str > , FieldValue > {
289282 parse_js_ts_config ( source, "ts" ) . expect ( "expected analyzable config" )
290283 }
291284
292285 /// Shorthand for asserting a field extracted as JSON.
293- fn assert_json (
294- map : & FxHashMap < Box < str > , StaticFieldValue > ,
295- key : & str ,
296- expected : serde_json:: Value ,
297- ) {
298- assert_eq ! ( map. get( key) , Some ( & StaticFieldValue :: Json ( expected) ) ) ;
286+ fn assert_json ( map : & FxHashMap < Box < str > , FieldValue > , key : & str , expected : serde_json:: Value ) {
287+ assert_eq ! ( map. get( key) , Some ( & FieldValue :: Json ( expected) ) ) ;
299288 }
300289
301290 /// Shorthand for asserting a field is `NonStatic`.
302- fn assert_non_static ( map : & FxHashMap < Box < str > , StaticFieldValue > , key : & str ) {
291+ fn assert_non_static ( map : & FxHashMap < Box < str > , FieldValue > , key : & str ) {
303292 assert_eq ! (
304293 map. get( key) ,
305- Some ( & StaticFieldValue :: NonStatic ) ,
294+ Some ( & FieldValue :: NonStatic ) ,
306295 "expected field {key:?} to be NonStatic"
307296 ) ;
308297 }
@@ -459,6 +448,21 @@ mod tests {
459448 assert_json ( & result, "d" , serde_json:: json!( -1 ) ) ;
460449 }
461450
451+ #[ test]
452+ fn numeric_overflow_to_infinity_is_null ( ) {
453+ // 1e999 overflows f64 to Infinity; JSON.stringify(Infinity) === "null"
454+ let result = parse ( "export default { a: 1e999, b: -1e999 }" ) ;
455+ assert_json ( & result, "a" , serde_json:: Value :: Null ) ;
456+ assert_json ( & result, "b" , serde_json:: Value :: Null ) ;
457+ }
458+
459+ #[ test]
460+ fn negative_zero_is_zero ( ) {
461+ // JSON.stringify(-0) === "0"
462+ let result = parse ( "export default { a: -0 }" ) ;
463+ assert_json ( & result, "a" , serde_json:: json!( 0 ) ) ;
464+ }
465+
462466 #[ test]
463467 fn boolean_values ( ) {
464468 let result = parse ( "export default { a: true, b: false }" ) ;
0 commit comments