@@ -262,30 +262,43 @@ 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`]. Computed properties are silently skipped (key unknown).
265+ /// [`FieldValue::NonStatic`].
266266///
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.
267+ /// Both spreads and computed-key properties invalidate all fields declared before
268+ /// them, because either may resolve to a key that overrides an earlier entry:
269+ ///
270+ /// ```js
271+ /// { a: 1, ...x, b: 2 } // a → NonStatic, b → Json(2)
272+ /// { a: 1, [key]: 2, b: 3 } // a → NonStatic, b → Json(3)
273+ /// ```
274+ ///
275+ /// Fields declared after such entries are safe (they explicitly override whatever
276+ /// the spread/computed-key produced). Unknown keys are never added to the map.
270277fn extract_object_fields (
271278 obj : & oxc_ast:: ast:: ObjectExpression < ' _ > ,
272279) -> FxHashMap < Box < str > , FieldValue > {
273280 let mut map = FxHashMap :: default ( ) ;
274281
282+ /// Mark every field accumulated so far as NonStatic.
283+ fn invalidate_previous ( map : & mut FxHashMap < Box < str > , FieldValue > ) {
284+ for value in map. values_mut ( ) {
285+ * value = FieldValue :: NonStatic ;
286+ }
287+ }
288+
275289 for prop in & obj. properties {
276290 if prop. is_spread ( ) {
277291 // A spread may override any field declared before it.
278- for value in map. values_mut ( ) {
279- * value = FieldValue :: NonStatic ;
280- }
292+ invalidate_previous ( & mut map) ;
281293 continue ;
282294 }
283295 let ObjectPropertyKind :: ObjectProperty ( prop) = prop else {
284296 continue ;
285297 } ;
286298
287299 let Some ( key) = prop. key . static_name ( ) else {
288- // Computed properties — keys are unknown at static analysis time
300+ // A computed key may equal any previously-seen key name.
301+ invalidate_previous ( & mut map) ;
289302 continue ;
290303 } ;
291304
@@ -722,18 +735,34 @@ mod tests {
722735 }
723736
724737 #[ test]
725- fn computed_properties_skipped ( ) {
738+ fn computed_key_unknown_not_in_map ( ) {
739+ // The computed key's resolved name is unknown — not added to the map.
740+ // Fields declared after it are safe (they explicitly win).
726741 let result = parse (
727742 r"
728743 const key = 'dynamic';
729744 export default { [key]: 'value', plain: 'ok' }
730745 " ,
731746 ) ;
732- // Computed key — not in map at all (key is unknown)
733747 assert ! ( !result. contains_key( "dynamic" ) ) ;
734748 assert_json ( & result, "plain" , serde_json:: json!( "ok" ) ) ;
735749 }
736750
751+ #[ test]
752+ fn computed_key_invalidates_previous_fields ( ) {
753+ // A computed key may resolve to any previously-seen name and override it.
754+ let result = parse (
755+ r"
756+ const key = 'run';
757+ export default { a: 1, run: { cacheScripts: true }, [key]: 'override', b: 2 }
758+ " ,
759+ ) ;
760+ assert_non_static ( & result, "a" ) ;
761+ assert_non_static ( & result, "run" ) ;
762+ assert ! ( !result. contains_key( "dynamic" ) ) ;
763+ assert_json ( & result, "b" , serde_json:: json!( 2 ) ) ;
764+ }
765+
737766 #[ test]
738767 fn non_static_array_with_spread ( ) {
739768 let result = parse (
0 commit comments