A function reassigns or mutates one of its own parameters. Callers pass a value expecting the function to compute a result from it, not to have their argument silently changed underneath them — and a function that only reads its parameters is trivially testable input→output, with no need to inspect or snapshot the argument afterward.
function withDiscount(price: number, rate: number) {
price = price - price * rate; // flagged — reassigns the `price` parameter
return price;
}
function tagAsProcessed(item: { status: string }) {
item.status = "processed"; // flagged — mutates the `item` parameter
return item;
}// fix: compute a new value instead of writing back into the parameter
function withDiscount(price: number, rate: number) {
const discounted = price - price * rate;
return discounted;
}
function tagAsProcessed(item: { status: string }) {
return { ...item, status: "processed" };
}Scoped to simple-identifier parameters only — destructured ({ a, b }) and rest (...rest) parameters aren't tracked in this first version. Only single-level member/index writes are detected (x.foo = ..., x[0] = ...); nested writes (x.a.b = ...) and delete x.foo are not. A nested function inherits its enclosing function's watched parameters, so a closure mutating an outer parameter still flags — but tracking is name-based, so a nested local that happens to share a parameter's name is also flagged as if it were that parameter, a known false-positive edge case for this first version.