@@ -262,16 +262,22 @@ fn count_returns_in_stmt(stmt: &Statement<'_>) -> usize {
262262
263263/// Extract fields from an object expression, converting each value to JSON.
264264/// Fields whose values cannot be represented as pure JSON are recorded as
265- /// [`FieldValue::NonStatic`]. Spread elements and computed properties
266- /// are not representable so they are silently skipped (their keys are unknown).
265+ /// [`FieldValue::NonStatic`]. Computed properties are silently skipped (key unknown).
266+ ///
267+ /// Spreads invalidate all fields declared before them: `{ a: 1, ...x, b: 2 }` yields
268+ /// `a: NonStatic` (spread may override it) and `b: Json(2)` (declared after, wins).
269+ /// Unknown keys introduced by the spread are not added to the map.
267270fn extract_object_fields (
268271 obj : & oxc_ast:: ast:: ObjectExpression < ' _ > ,
269272) -> FxHashMap < Box < str > , FieldValue > {
270273 let mut map = FxHashMap :: default ( ) ;
271274
272275 for prop in & obj. properties {
273276 if prop. is_spread ( ) {
274- // Spread elements — keys are unknown at static analysis time
277+ // A spread may override any field declared before it.
278+ for value in map. values_mut ( ) {
279+ * value = FieldValue :: NonStatic ;
280+ }
275281 continue ;
276282 }
277283 let ObjectPropertyKind :: ObjectProperty ( prop) = prop else {
@@ -686,14 +692,31 @@ mod tests {
686692 }
687693
688694 #[ test]
689- fn spread_in_top_level_skipped ( ) {
695+ fn spread_unknown_keys_not_in_map ( ) {
696+ // Keys introduced by the spread are unknown — not added to the map.
697+ // Fields declared after the spread are safe (they win over the spread).
690698 let result = parse (
691699 r"
692700 const base = { x: 1 };
693701 export default { ...base, b: 'ok' }
694702 " ,
695703 ) ;
696- // Spread at top level — keys unknown, so not in map at all
704+ assert ! ( !result. contains_key( "x" ) ) ;
705+ assert_json ( & result, "b" , serde_json:: json!( "ok" ) ) ;
706+ }
707+
708+ #[ test]
709+ fn spread_invalidates_previous_fields ( ) {
710+ // Fields declared before a spread become NonStatic — the spread may override them.
711+ // Fields declared after the spread are unaffected.
712+ let result = parse (
713+ r"
714+ const base = { x: 1 };
715+ export default { a: 1, run: { cacheScripts: true }, ...base, b: 'ok' }
716+ " ,
717+ ) ;
718+ assert_non_static ( & result, "a" ) ;
719+ assert_non_static ( & result, "run" ) ;
697720 assert ! ( !result. contains_key( "x" ) ) ;
698721 assert_json ( & result, "b" , serde_json:: json!( "ok" ) ) ;
699722 }
0 commit comments