Skip to content
NewBun 1.4.5 is out. Release notes
AphrodyBun

Linter · Rules

oxc/no-map-spread

Disallow object and array spreads in the return value of map and flatMap callbacks

tasks.map(task => ({ ...task, done: false })) copies every property of every item into a new object. On a large array, that's a lot of work and memory. If nothing else uses the old items, you can change them in place.

The rule looks at the object or array the callback returns, directly or through a variable. It doesn't look at what's being spread, so ids.map(id => ({ ...defaults, id })) is reported too.

--fix-suggestions rewrites { ...task, done: false } to Object.assign(task, { done: false }). That changes the original array's items.

Configure

Off by default. To turn it on:

bunfig.toml
[lint.rules]
"oxc/no-map-spread" = "error" # or "warn"

Every option, set to its default:

bunfig.toml
[lint.rules]
"oxc/no-map-spread" = ["error", {
  ignoreRereads = true,
  ignoreArgs = true,
}]

Examples

Reported:

function loadTasks() {
  const tasks = readTasks();
  return tasks.map(task => ({ ...task, done: false }));
}
const rows = readRows().map(row => [...row, total(row)]);

Not reported:

function loadTasks() {
  const tasks = readTasks();
  return tasks.map(task => Object.assign(task, { done: false }));
}
const rows = readRows();
for (const row of rows) {
  row.push(total(row));
}

An array that belongs to this is never reported, because changing its items would change the instance:

class TaskList {
  snapshot() {
    return this.tasks.map(task => ({ ...task }));
  }
}

Options

OptionDefaultWhat it does
ignoreRereadstrueAllow the spread if the array variable is read again after the call. In that case, the copy is probably on purpose.
ignoreArgstrueAllow the spread if the array is a function parameter. The caller might still use its items.

Allowed by default, because tasks is used afterwards:

const tasks = readTasks();
const drafts = tasks.map(task => ({ ...task, done: false }));
console.log(tasks.length, drafts.length);
const tasks = readTasks();
const drafts = tasks.map(task => ({ ...task, done: false }));
console.log(tasks.length, drafts.length);

Allowed by default, because tasks is a parameter:

function markDone(tasks) {
  return tasks.map(task => ({ ...task, done: true }));
}
function markDone(tasks) {
  return tasks.map(task => ({ ...task, done: true }));
}

When not to use it

If your code treats data as immutable, as React state and Redux stores require, the copy is the point. The cost also doesn't matter for small arrays.