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:
[lint.rules]
"oxc/no-map-spread" = "error" # or "warn"
Every option, set to its default:
[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
| Option | Default | What it does |
|---|---|---|
ignoreRereads | true | Allow the spread if the array variable is read again after the call. In that case, the copy is probably on purpose. |
ignoreArgs | true | Allow 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.