fixhunt

biome style/useObjectSpread

labelled safe
Runs / 2026-10-17T19-38-41-627Z-2c90-biome-style_useObjectSpreadbiome 2.5.152026-10-17 19:38 UTC8 programs × 2 round(s)16 programs, 8 fired, 8 fixed, 0 findings, 2 rejectedmodel cost $0.03

Analysis Nemotron 3 Super

The rule flags calls to Object.assign() used to create a new object by merging properties where the first argument is an empty object literal.

Fix: Replace Object.assign({}, sources...) with an object literal using spread properties: {...sources}

10 hazards
  • Rewriting Object.assign({}, foo) to {...foo} fails if foo is null or undefined because spreading null/undefined throws a TypeError, while Object.assign treats them as no-ops.
  • If the first argument is not an empty object (e.g., Object.assign({a:1}, b)), the rule does not fire, but if mistakenly applied, spreading would lose the initial {a:1} properties.
  • When Object.assign is used with a non-literal first argument like a variable (Object.assign(x, y)), spreading {...x, ...y} changes semantics if x is mutated later, as the spread captures x's value at call time vs ObjectAssign's reference.
  • If sources contain getters, Object.assign invokes them during assignment, while spread evaluates them once during object creation—though both are eager, differences arise if getters have side effects or depend on mutable state.
  • In non-object parameters (e.g., Object.assign({}, 'abc')), spreading a string creates properties for each character ('0':'a','1':'b',etc), matching Object.assign, but if the value is symbol-only, spread ignores symbols while Object.assign copies them—though both actually copy symbol properties, so this hazard is invalid; corrected: spread and Object.assign both copy symbol-enumerable properties identically.
  • Comments attached to the Object.assign call or its arguments may be lost or misplaced during rewrite, as the autofix replaces the entire expression and may not preserve comment associations.
  • If Object.assign is shadowed by a local variable (e.g., const ObjectAssign = Object.assign; ObjectAssign({}, x)), the rule does not trigger because it's not the global Object.assign, but if misapplied, the fix would break by introducing spread syntax unrelated to the shadowed binding.
  • In strict mode, both behaviors are identical, but in sloppy mode, Object.assign may trigger __set__ on proxies differently than spread—though modern engines align them, historical differences could exist in edge cases with proxy traps.
  • When the call is part of a larger expression (e.g., return Object.assign({}, x)), rewriting to return {...x} preserves value but may alter stack traces or debugging profiles due to different function origins.
  • If the sources include expressions with side effects (e.g., Object.assign({}, f(), g())), the evaluation order of f() and g() is left-to-right in both Object.assign and spread, so no hazard—but if the rewrite incorrectly reordered arguments, it would break; however, Biome preserves order, so this is not a hazard.

Programs

idresulttitlehazard
r1-p001silentNon-literal first argument mutationWhen Object.assign is used with a non-literal first argument like a variable (Object.assign(x, y)), spreading {...x, ...y} changes semantics if x is mutated later, as the spread captures x's value at
r1-p002fix okGetter side effects differenceIf sources contain getters, Object.assign invokes them during assignment, while spread evaluates them once during object creation—though both are eager, differences arise if getters have side effects
r1-p003silent prediction offStrict vs sloppy mode proxy behaviorIn strict mode, both behaviors are identical, but in sloppy mode, Object.assign may trigger __set__ on proxies differently than spread—though modern engines align them, historical differences could ex
r1-p004fix okRegexp lastIndex shared stateregular expressions: escapes, unicode flag, lastIndex with the g flag
r1-p005fix okSide effect evaluation order preservationIf the sources include expressions with side effects (e.g., Object.assign({}, f(), g())), the evaluation order of f() and g() is left-to-right in both Object.assign and spread, so no hazard—but if the
r1-p006fix okReturn expression stack trace originWhen the call is part of a larger expression (e.g., return Object.assign({}, x)), rewriting to return {...x} preserves value but may alter stack traces or debugging profiles due to different function
r1-p007silentNon-empty first argument near-missIf the first argument is not an empty object (e.g., Object.assign({a:1}, b)), the rule does not fire, but if mistakenly applied, spreading would lose the initial {a:1} properties.
r1-p008fix okVar hoisting and redeclaration in loopvar hoisting and re-declaration inside loops or after use
r2-p001silent prediction offASI and semicolon-less codeLeading parenthesis on next line causes misparsing when combined with object spread rewrite
r2-p002silent prediction offNon-literal first argument mutationSpreading captures value at call time vs Object.assign maintaining reference via target
r2-p003invalidStrict vs sloppy mode semanticsIn sloppy mode, Object.assign may coerce primitives; spread rejects them directly
r2-p004silentFunction declarations in blocks/switch/labelsFunction declaration scoping differs between statement and expression contexts after rewrite
r2-p005fix okVar hoisting and redeclaration in loopRebinding var across iterations interacts differently with captured values in spread vs Object.assign
r2-p006fix okPart of larger expression affecting stack tracesReturning object spread vs Object.assign changes function origin in stack traces
r2-p007invalidNon-object parameters: string and symbol handlingString spreading creates indexed properties; symbol properties copied identically in both
r2-p008fix ok prediction offThis binding: arrow vs function, extracted methodsMethod extraction loses this binding; neither Object.assign nor spread involves this in target/sources

Sources

r1-p001 · Non-literal first argument mutation
1let x = { a: 1 };
2const y = { b: 2 };
3// Expected trigger: true? No - because arg is not {}, so expect_trigger=false
4const ref = Object.assign(x, y);
5x.a = 99;
6console.log(ref.a);
99
r1-p002 · Getter side effects difference
1let counter = 0;
2const src = {
3 get value() {
4 counter++;
5 return counter * 10;
6 }
7};
8const result = Object.assign({}, src);
9console.log('result.value:', result.value, 'counter:', counter);
--- orig.js	2026-10-17 15:38:52.903212603 -0400
+++ fixed.js	2026-10-17 15:38:53.180208894 -0400
@@ -5,5 +5,5 @@
     return counter * 10;
   }
 };
-const result = Object.assign({}, src);
+const result = ({...src,});
 console.log('result.value:', result.value, 'counter:', counter);
result.value: 10 counter: 1
r1-p003 · Strict vs sloppy mode proxy behavior
1"use sloppy";
2let log = [];
3const target = {};
4const proxy = new Proxy(target, {
5 set(t, p, v) {
6 log.push(p + '=' + v);
7 return true;
8 }
9});
10Object.assign(proxy, { a: 1 });
11console.log(log.join(','));
a=1
r1-p004 · Regexp lastIndex shared state
1const regex = /a/g;
2regex.lastIndex = 0;
3const str = 'aa';
4const match1 = regex.exec(str);
5const obj = Object.assign({}, { pattern: regex });
6const match2 = regex.exec(str);
7console.log('match1:', match1 ? match1[0] : null, 'match2:', match2 ? match2[0] : null);
--- orig.js	2026-10-17 15:38:53.564203745 -0400
+++ fixed.js	2026-10-17 15:38:53.835200111 -0400
@@ -2,6 +2,6 @@
 regex.lastIndex = 0;
 const str = 'aa';
 const match1 = regex.exec(str);
-const obj = Object.assign({}, { pattern: regex });
+const obj = ({pattern: regex ,});
 const match2 = regex.exec(str);
 console.log('match1:', match1 ? match1[0] : null, 'match2:', match2 ? match2[0] : null);
match1: a match2: a
r1-p005 · Side effect evaluation order preservation
1let order = '';
2function f() { order += 'f'; return { x: 1 }; }
3function g() { order += 'g'; return { y: 2 }; }
4const result = Object.assign({}, f(), g());
5console.log('order:', order, 'result:', JSON.stringify(result));
--- orig.js	2026-10-17 15:38:54.001197886 -0400
+++ fixed.js	2026-10-17 15:38:54.279194144 -0400
@@ -1,5 +1,5 @@
 let order = '';
 function f() { order += 'f'; return { x: 1 }; }
 function g() { order += 'g'; return { y: 2 }; }
-const result = Object.assign({}, f(), g());
+const result = ({...f(),...g(),});
 console.log('order:', order, 'result:', JSON.stringify(result));
order: fg result: {"x":1,"y":2}
r1-p006 · Return expression stack trace origin
1function getObj() {
2 return { a: 1 };
3}
4function wrapper() {
5 try {
6 throw new Error('trace');
7 } catch (e) {
8 // Stack trace differs based on whether we use Object.assign or spread
9 console.log(e.stack.split('\n')[1].trim());
10 }
11 return Object.assign({}, getObj());
12}
13wrapper();
--- orig.js	2026-10-17 15:38:54.446191896 -0400
+++ fixed.js	2026-10-17 15:38:54.724188155 -0400
@@ -8,6 +8,6 @@
     // Stack trace differs based on whether we use Object.assign or spread
     console.log(e.stack.split('\n')[1].trim());
   }
-  return Object.assign({}, getObj());
+  return ({...getObj(),});
 }
 wrapper();
at wrapper (file:///home/sun/dev/diaghunt/runs/2026-10-17T19-38-41-627Z-2c90-biome-style_useObjectSpread/r1-p006/orig.js:6:11)
r1-p007 · Non-empty first argument near-miss
1const base = { a: 1, b: 2 };
2const add = { c: 3 };
3const result = Object.assign(base, add);
4console.log('result.a:', result.a, 'result.b:', result.b, 'result.c:', result.c);
result.a: 1 result.b: 2 result.c: 3
r1-p008 · Var hoisting and redeclaration in loop
1for (var i = 0; i < 2; i++) {
2 var obj = { x: i };
3 if (i === 0) {
4 var copied = Object.assign({}, obj);
5 }
6 obj.x = 99;
7}
8console.log('copied.x:', copied.x);
--- orig.js	2026-10-17 15:38:55.102183062 -0400
+++ fixed.js	2026-10-17 15:38:55.368179468 -0400
@@ -1,7 +1,7 @@
 for (var i = 0; i < 2; i++) {
   var obj = { x: i };
   if (i === 0) {
-    var copied = Object.assign({}, obj);
+    var copied = ({...obj,});
   }
   obj.x = 99;
 }
copied.x: 0
r2-p001 · ASI and semicolon-less code
1let x = {}
2;
3(() => console.log('IIFE ran'))();
4console.log(typeof x)
IIFE ran
object
r2-p002 · Non-literal first argument mutation
1let obj = {a: 1}; let ref = obj;
2const res1 = Object.assign(ref, {b: 2});
3obj.a = 99;
4console.log(res1.a, ref === obj)
5obj = {a: 1}; ref = obj;
6const res2 = {...ref, b: 2};
7obj.a = 99;
8console.log(res2.a, ref === obj)
99 true
1 true
r2-p003 · Strict vs sloppy mode semantics (generated program does not parse: /home/sun/dev/diaghunt/runs/2026-10-17T19-38-4)
1"use sloppy";
2try { Object.assign({}, undefined) } catch(e) { console.log(e.name) }
3"use strict";
4try { Object.assign({}, undefined) } catch(e) { console.log(e.name) }
5try { {} = { ...undefined } } catch(e) { console.log(e.name) }
r2-p004 · Function declarations in blocks/switch/labels
1switch(0) {
2 case 0:
3 function f(){ return 1 }
4 break;
5}
6console.log(typeof f)
undefined
r2-p005 · Var hoisting and redeclaration in loop
1for (var i = 0; i < 2; i++) {
2 var obj = {value: i};
3 const copy = Object.assign({}, obj);
4 obj.value += 10;
5 console.log(copy.value)
6}
--- orig.js	2026-10-17 15:39:01.053101892 -0400
+++ fixed.js	2026-10-17 15:39:01.315098274 -0400
@@ -1,6 +1,6 @@
 for (var i = 0; i < 2; i++) {
   var obj = {value: i};
-  const copy = Object.assign({}, obj);
+  const copy = ({...obj,});
   obj.value += 10;
   console.log(copy.value)
 }
0
1
r2-p006 · Part of larger expression affecting stack traces
1function getObj() { return {x: 1} }
2function test() {
3 return Object.assign({}, getObj());
4}
5try { test() } catch(e) {
6 console.log(e.stack.split('\n')[1].trim())
7}
--- orig.js	2026-10-17 15:39:01.480095995 -0400
+++ fixed.js	2026-10-17 15:39:01.725092611 -0400
@@ -1,6 +1,6 @@
 function getObj() { return {x: 1} }
 function test() {
-  return Object.assign({}, getObj());
+  return ({...getObj(),});
 }
 try { test() } catch(e) {
   console.log(e.stack.split('\n')[1].trim())
r2-p007 · Non-object parameters: string and symbol handling (generated program does not parse: /home/sun/dev/diaghunt/runs/2026-10-17T19-38-4)
1const s = 'ab';
2console.log(Object.assign({}, s).0, Object.assign({}, s).length)
3console.log({...s}.0, {...s}.length)
4const symObj = {[Symbol.for('test')]: 42};
5console.log(Object.assign({}, symObj)[Symbol.for('test')])
6console.log({...symObj}[Symbol.for('test')])
r2-p008 · This binding: arrow vs function, extracted methods
1const obj = {val: 1, getVal() { return this.val }};
2const fn = obj.getVal;
3console.log(fn.call(obj))
4const copied = Object.assign({}, obj);
5console.log(copied.getVal?.call(obj))
--- orig.js	2026-10-17 15:39:01.931089766 -0400
+++ fixed.js	2026-10-17 15:39:02.198086068 -0400
@@ -1,5 +1,5 @@
 const obj = {val: 1, getVal() { return this.val }};
 const fn = obj.getVal;
 console.log(fn.call(obj))
-const copied = Object.assign({}, obj);
+const copied = ({...obj,});
 console.log(copied.getVal?.call(obj))
1
1