biome style/useObjectSpread
labelled safe
Analysis Nemotron 3 Super
The rule flags uses of Object.assign() where the first argument is an empty object literal {}, suggesting replacement with object spread syntax.
Fix: Replace Object.assign({}, ...) with an object literal using spread properties: {...args} where args are all arguments after the first empty object.
10 hazards
- If the first argument is not a literal {} but a variable or expression that evaluates to an empty object, the fix incorrectly assumes it is a literal and removes it, altering behavior.
- If Object.assign is called via a different reference (e.g., const assign = Object.assign; assign({}, foo)), the fix does not apply and may leave unsafe code unchanged, but if misapplied, it would break due to missing context.
- If the first argument is {} but followed by non-object primitives (e.g., Object.assign({}, null, foo)), the spread fix may throw at runtime because spreading null/undefined is invalid, whereas Object.assign coerces them.
- If the Object.assign call is inside a larger expression and the fix removes parentheses needed for grouping (e.g., return Object.assign({}, foo); → return {...foo}; is safe, but (Object.assign({}, foo)) → ({...foo}) may be needed in some contexts like JSX props), the fix might omit necessary parentheses.
- If the code relies on the side effect of Object.assign defining properties on the first argument (even if it's {}), and that object is later used elsewhere, the fix changes when the object is created (in-place vs new), potentially causing aliasing issues.
- If the first argument {} is actually a destructured or computed property in an object literal (e.g., {[k]: Object.assign({}, foo)}), the fix could produce invalid syntax or change evaluation order.
- If the Object.assign call is in a loop or frequently executed path and the environment lacks spread support (e.g., IE without transpilation), the fix may introduce syntax errors in older environments.
- If the arguments after the first include getters or have side effects, the evaluation order of spreading vs Object.assign is the same (left-to-right), so no issue, but if the fix were to reorder args (it doesn't), it would matter—however, Biome's fix preserves order.
- If the code contains trailing comments on the Object.assign line, the fix might discard them if not properly preserved during replacement.
- If the first argument {} is actually part of a larger object literal spread (e.g., {...other, ...Object.assign({}, foo)}), replacing the inner call could break nesting or change the resulting object structure due to duplicate key handling.
Programs
| id | result | title | hazard |
|---|---|---|---|
| r1-p001 | fix ok | ASI and semicolon-less code | ASI and semicolon-less code, leading ( or [ on the next line |
| r1-p002 | fix ok | comments attached to removed/moved code | comments attached to the removed or moved code |
| r1-p003 | invalid | exceptions inside rewritten code | exceptions thrown inside the code the fix removes or rewrites |
| r1-p004 | fix ok | var hoisting and re-declaration | var hoisting and re-declaration inside loops or after use |
| r1-p005 | fix ok | name collisions after move (shadowing) | name collisions across scopes after a move (shadowing) |
| r1-p006 | fix ok | preserved arg order with side effects | If the arguments after the first include getters or have side effects, the evaluation order of spreading vs Object.assign is the same (left-to-right), so no issue, but if the fix were to reorder args |
| r1-p007 | fix ok | class fields, static blocks, private names, super calls | class fields, static blocks, private names, super calls |
| r1-p008 | fix ok | function declarations inside blocks, switch cases, labeled statements | function declarations inside blocks, switch cases, labeled statements |
| r2-p001 | fix ok | Getter setter defineProperty side effects | getters, setters and Object.defineProperty side effects |
| r2-p002 | fix ok | Sparse array array-like side effects | sparse arrays, holes, Array(n) vs [n], array-likes |
| r2-p003 | silent | Misapplied fix via alias reference | If Object.assign is called via a different reference (e.g., const assign = Object.assign; assign({}, foo)), the fix does not apply and may leave unsafe code unchanged, but if misapplied, it would brea |
| r2-p004 | fix ok | Computed property key in object literal | If the first argument {} is actually a destructured or computed property in an object literal (e.g., {[k]: Object.assign({}, foo)}), the fix could produce invalid syntax or change evaluation order. |
| r2-p005 | silent prediction off | Aliasing issue due to changed object creation timing | If the code relies on the side effect of Object.assign defining properties on the first argument (even if it's {}), and that object is later used elsewhere, the fix changes when the object is created |
| r2-p006 | fix ok | Arguments object and rest parameters interaction | arguments object, function.length, default parameters, rest parameters |
| r2-p007 | fix ok | Name collision after move shadowing | name collisions across scopes after a move (shadowing) |
| r2-p008 | fix ok | Exception inside rewritten code | exceptions thrown inside the code the fix removes or rewrites |
Sources
r1-p001 · ASI and semicolon-less code
1let result
2(() => {
3 const r = Object.assign({}, {x: 1})
4 return r.x
5})().then(v => {
6 console.log('result:', v)
7}).catch(e => {
8 console.error('error:', e.message)
9});
10[].forEach.call([], () => {})
--- orig.js 2026-10-17 17:06:51.040241668 -0400 +++ fixed.js 2026-10-17 17:06:51.319244699 -0400 @@ -1,6 +1,6 @@ let result (() => { - const r = Object.assign({}, {x: 1}) + const r = ({x: 1,}) return r.x })().then(v => { console.log('result:', v)
r1-p002 · comments attached to removed/moved code
1// Setup
2const input = {a: 1}
3// Transform
4const out = /* comment */Object.assign(/* another */{}, input)// end
5console.log(out.a)
--- orig.js 2026-10-17 17:06:51.488246536 -0400 +++ fixed.js 2026-10-17 17:06:51.772249622 -0400 @@ -1,5 +1,5 @@ // Setup const input = {a: 1} // Transform -const out = /* comment */Object.assign(/* another */{}, input)// end +const out = /* comment */({...input,})// end console.log(out.a)
1
r1-p003 · exceptions inside rewritten code (generated program does not parse: /home/sun/dev/diaghunt/runs/2026-10-17T21-06-4)
1try {
2 const o = Object.assign({}, {get x(){throw new Error('boom')}})
3} catch (e) {
4 console.log(e.name + ': ' + e.message)
5}
6try {
7 const p = {/*spread*/}{get x(){throw new Error('boom')}}
8} catch (e) {
9 console.log(e.name + ': ' + e.message)
10}
r1-p004 · var hoisting and re-declaration
1function f() {
2 for (var i = 0; i < 2; i++) {
3 var tmp = Object.assign({}, {val: i})
4 console.log(tmp.val)
5 }
6 console.log(typeof tmp)
7}
8f()
--- orig.js 2026-10-17 17:06:51.978251860 -0400 +++ fixed.js 2026-10-17 17:06:52.252254837 -0400 @@ -1,6 +1,6 @@ function f() { for (var i = 0; i < 2; i++) { - var tmp = Object.assign({}, {val: i}) + var tmp = ({val: i,}) console.log(tmp.val) } console.log(typeof tmp)
0 1 object
r1-p005 · name collisions after move (shadowing)
1let data = {prop: 'outer'}
2{
3 let data = Object.assign({}, {inner: 'value'})
4 console.log(data.prop, data.inner)
5}
6console.log(data.prop)
--- orig.js 2026-10-17 17:06:52.418256640 -0400 +++ fixed.js 2026-10-17 17:06:52.675259432 -0400 @@ -1,6 +1,6 @@ let data = {prop: 'outer'} { - let data = Object.assign({}, {inner: 'value'}) + let data = ({inner: 'value',}) console.log(data.prop, data.inner) } console.log(data.prop)
undefined value outer
r1-p006 · preserved arg order with side effects
1let seq = []
2const obj1 = {get a(){seq.push(1); return 1}}
3const obj2 = {get b(){seq.push(2); return 2}}
4const res = Object.assign({}, obj1, obj2)
5console.log('seq:', seq.join(', '), 'res:', JSON.stringify(res))
--- orig.js 2026-10-17 17:06:52.844261268 -0400 +++ fixed.js 2026-10-17 17:06:53.121264276 -0400 @@ -1,5 +1,5 @@ let seq = [] const obj1 = {get a(){seq.push(1); return 1}} const obj2 = {get b(){seq.push(2); return 2}} -const res = Object.assign({}, obj1, obj2) +const res = ({...obj1,...obj2,}) console.log('seq:', seq.join(', '), 'res:', JSON.stringify(res))
seq: 1, 2 res: {"a":1,"b":2}r1-p007 · class fields, static blocks, private names, super calls
1class Base {
2 static baseProp = 'base'
3}
4class Derived extends Base {
5 static staticProp = Object.assign({}, {derived: true})
6 static {
7 console.log(Base.baseProp, Derived.staticProp.derived)
8 }
9 #private = Object.assign({}, {secret: 42})
10 constructor() {
11 super()
12 console.log(this.#private.secret)
13 }
14}
15new Derived()
--- orig.js 2026-10-17 17:06:53.297266188 -0400 +++ fixed.js 2026-10-17 17:06:53.576269218 -0400 @@ -2,11 +2,11 @@ static baseProp = 'base' } class Derived extends Base { - static staticProp = Object.assign({}, {derived: true}) + static staticProp = ({derived: true,}) static { console.log(Base.baseProp, Derived.staticProp.derived) } - #private = Object.assign({}, {secret: 42}) + #private = ({secret: 42,}) constructor() { super() console.log(this.#private.secret)
base true 42
r1-p008 · function declarations inside blocks, switch cases, labeled statements
1start: {
2 if (true) {
3 function helper() {
4 return Object.assign({}, {flag: true})
5 }
6 const h = helper()
7 console.log(h.flag)
8 break start
9 }
10 switch (0) {
11 case 0:
12 function caseFn() {
13 return Object.assign({}, {case: 0})
14 }
15 console.log(caseFn().case)
16 break
17 }
18}
--- orig.js 2026-10-17 17:06:53.749271097 -0400 +++ fixed.js 2026-10-17 17:06:54.033274181 -0400 @@ -1,7 +1,7 @@ start: { if (true) { function helper() { - return Object.assign({}, {flag: true}) + return ({flag: true,}) } const h = helper() console.log(h.flag) @@ -10,7 +10,7 @@ switch (0) { case 0: function caseFn() { - return Object.assign({}, {case: 0}) + return ({case: 0,}) } console.log(caseFn().case) break
true
r2-p001 · Getter setter defineProperty side effects
1const target = {};
2Object.defineProperty(target, 'prop', {
3 get() { console.log('GET'); return 42; },
4 configurable: true
5});
6console.log('before:', typeof target.prop);
7Object.assign({}, target); // triggers rule
8console.log('after:', typeof target.prop);
--- orig.js 2026-10-17 17:06:59.358331965 -0400 +++ fixed.js 2026-10-17 17:06:59.630334914 -0400 @@ -4,5 +4,5 @@ configurable: true }); console.log('before:', typeof target.prop); -Object.assign({}, target); // triggers rule +({...target,}); // triggers rule console.log('after:', typeof target.prop);
GET before: number GET after: number
r2-p002 · Sparse array array-like side effects
1const arr = Array(3);
2arr[1] = 'present';
3console.log('sparse length:', arr.length);
4console.log('sparse [1]:', arr[1]);
5const copy = Object.assign({}, arr); // triggers rule
6console.log('copy length:', copy.length);
7console.log('copy [1]:', copy[1]);
--- orig.js 2026-10-17 17:06:59.800336757 -0400 +++ fixed.js 2026-10-17 17:07:00.080339793 -0400 @@ -2,6 +2,6 @@ arr[1] = 'present'; console.log('sparse length:', arr.length); console.log('sparse [1]:', arr[1]); -const copy = Object.assign({}, arr); // triggers rule +const copy = ({...arr,}); // triggers rule console.log('copy length:', copy.length); console.log('copy [1]:', copy[1]);
sparse length: 3 sparse [1]: present copy length: undefined copy [1]: present
r2-p003 · Misapplied fix via alias reference
1const assign = Object.assign;
2const result = assign({}, {x: 1}); // not triggered by rule
3console.log('result.x:', result.x);
4// Biome will NOT suggest fix here because it's not direct Object.assign call
result.x: 1
r2-p004 · Computed property key in object literal
1let key = 'dynamic';
2const obj = {
3 [key]: Object.assign({}, {a: 1}) // triggers rule
4};
5console.log(obj.dynamic.a);
6key = 'changed';
7console.log(obj.changed === undefined ? 'not present' : 'present');
--- orig.js 2026-10-17 17:07:00.481344141 -0400 +++ fixed.js 2026-10-17 17:07:00.740346948 -0400 @@ -1,6 +1,6 @@ let key = 'dynamic'; const obj = { - [key]: Object.assign({}, {a: 1}) // triggers rule + [key]: ({a: 1,}) // triggers rule }; console.log(obj.dynamic.a); key = 'changed';
1 not present
r2-p005 · Aliasing issue due to changed object creation timing
1let shared = {};
2function getTarget() { return shared; }
3const obj1 = Object.assign(getTarget(), {x: 1}); // triggers rule
4console.log('shared.x after assign:', shared.x);
5shared = {};
6const obj2 = Object.assign(getTarget(), {y: 2}); // triggers rule
7console.log('shared.y after second assign:', shared.y);
shared.x after assign: 1 shared.y after second assign: 2
r2-p006 · Arguments object and rest parameters interaction
1function test(a, b, c) {
2 console.log('args length:', arguments.length);
3 const copied = Object.assign({}, arguments); // triggers rule
4 console.log('copied 0:', copied[0]);
5 return copied;
6}
7const result = test(10, 20, 30);
8console.log('result 2:', result[2]);
--- orig.js 2026-10-17 17:07:01.190351826 -0400 +++ fixed.js 2026-10-17 17:07:01.460354753 -0400 @@ -1,6 +1,6 @@ function test(a, b, c) { console.log('args length:', arguments.length); - const copied = Object.assign({}, arguments); // triggers rule + const copied = ({...arguments,}); // triggers rule console.log('copied 0:', copied[0]); return copied; }
args length: 3 copied 0: 10 result 2: 30
r2-p007 · Name collision after move shadowing
1let obj = {x: 1};
2if (true) {
3 let obj = {y: 2}; // inner obj
4 Object.assign({}, obj); // triggers rule, copies inner {y:2}
5 console.log('inner:', obj.y);
6}
7console.log('outer:', obj.x); // outer obj unchanged
--- orig.js 2026-10-17 17:07:01.626356552 -0400 +++ fixed.js 2026-10-17 17:07:01.907359597 -0400 @@ -1,7 +1,7 @@ let obj = {x: 1}; if (true) { let obj = {y: 2}; // inner obj - Object.assign({}, obj); // triggers rule, copies inner {y:2} + ({...obj,}); // triggers rule, copies inner {y:2} console.log('inner:', obj.y); } console.log('outer:', obj.x); // outer obj unchanged
inner: 2 outer: 1
r2-p008 · Exception inside rewritten code
1function risky() {
2 throw new TypeError('intentional');
3}
4try {
5 Object.assign({}, {a: risky()}); // triggers rule, risky() throws
6} catch (e) {
7 console.log('caught:', e.name, e.message);
8}
9console.log('continued');
--- orig.js 2026-10-17 17:07:02.074361407 -0400 +++ fixed.js 2026-10-17 17:07:02.348364376 -0400 @@ -2,7 +2,7 @@ throw new TypeError('intentional'); } try { - Object.assign({}, {a: risky()}); // triggers rule, risky() throws + ({a: risky(),}); // triggers rule, risky() throws } catch (e) { console.log('caught:', e.name, e.message); }
caught: TypeError intentional continued