A callback passed to .map/.filter that mutates something outside itself (an outer-scope variable) or calls a known side-effecting function (console.*), instead of purely computing and returning a value — a loop wearing a map costume.
const seen: number[] = [];
const doubled = items.map((item) => {
seen.push(item); // mutates outer-scope `seen`
return item * 2;
});// fix — separate the accumulation from the transform
const doubled = items.map((item) => item * 2);
items.forEach((item) => seen.push(item));Not flagged — the callback only mutates/declares locals of its own:
const doubled = items.map((item) => {
const scaled = item * 2;
return scaled;
});Also flagged — writing into an outer-scope object property or array index, not just calling an array-mutating method:
const state = { count: 0 };
const withCount = items.map((item) => {
state.count += 1; // writes into outer-scope `state`
return item;
});Not flagged — a .map/.filter callback whose return value happens to be discarded, with no other side effect, is a distinct "use .forEach instead" concern this check doesn't cover (v1 scope, per the ticket):
items.map((item) => process(item));Scope: .forEach itself is never inspected — a side effect there is the whole point. Only the callback's own body is inspected; a nested function or arrow declared inside the callback is a separate scope and isn't descended into. console.* is currently the only side-effecting-call denylist entry. Member/index assignment targets (x.y = ..., x[y] = ...) are only checked at the root receiver — a destructured local (const { acc } = ctx;) isn't recognized as local, a known name-based limitation.