fix(linter/eslint/no-param-reassign): report rest parameter writes (#27765)

Report assignments such as `function f(...args) { args = []; }`, matching ESLint. Rest parameters now use the same reference and property checks as regular parameters, with regression coverage for destructuring, nested scopes, and property-ignore options.

Addresses #27746, report 11 (`eslint/no-param-reassign`).
This commit is contained in:
camc314 committed 2026-10-15 20:20:18 +00:00
1 parent 8ea47bbdb5
commit d85923f968
4 files changed
+145 -6

No files matched your search

+4 -2
View File
@@ -933,8 +933,10 @@ impl RuleRunner for crate::rules::eslint::no_object_constructor::NoObjectConstru
}
impl RuleRunner for crate::rules::eslint::no_param_reassign::NoParamReassign {
const NODE_TYPES: Option<&AstTypesBitset> =
Some(&AstTypesBitset::from_types(&[AstType::FormalParameter]));
const NODE_TYPES: Option<&AstTypesBitset> = Some(&AstTypesBitset::from_types(&[
AstType::FormalParameter,
AstType::FormalParameterRest,
]));
const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run;
}
@@ -92,12 +92,14 @@ impl Rule for NoParamReassign {
}
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
let AstKind::FormalParameter(param) = node.kind() else {
return;
let pattern = match node.kind() {
AstKind::FormalParameter(param) => &param.pattern,
AstKind::FormalParameterRest(param) => &param.rest.argument,
_ => return,
};
let symbol_table = ctx.scoping();
param.pattern.bound_names(&mut |ident| {
pattern.bound_names(&mut |ident| {
let symbol_id = ident.symbol_id();
let declaration_id = symbol_table.symbol_declaration(symbol_id);
@@ -256,6 +258,25 @@ fn test() {
"function foo<T>(this: Record<PropertyKey, T>, key: PropertyKey, value: T) { this[key] = value; }",
Some(serde_json::json!([{ "props": true }])),
),
("function f(...args) { return args.reverse(); }", None),
("function f(...args) { args[0] = 1; }", None),
(
"function f(...args) { args[0] = 1; }",
Some(
serde_json::json!([{ "props": true, "ignorePropertyModificationsFor": ["args"] }]),
),
),
(
"function f(...args) { delete args[0]; }",
Some(
serde_json::json!([{ "props": true, "ignorePropertyModificationsForRegex": ["^arg"] }]),
),
),
(
"function f(...args) { function g() { let args; args = []; } return args; }",
Some(serde_json::json!([{ "props": true }])),
),
("const [...args] = []; args = [];", Some(serde_json::json!([{ "props": true }]))),
];
let fail = vec![
@@ -333,6 +354,29 @@ fn test() {
("function foo(a) { a.b &&= c; }", Some(serde_json::json!([{ "props": true }]))), // { "ecmaVersion": 2021 },
("function foo(a) { a.b.c ||= d; }", Some(serde_json::json!([{ "props": true }]))), // { "ecmaVersion": 2021 },
("function foo(a) { a[b] ??= c; }", Some(serde_json::json!([{ "props": true }]))), // { "ecmaVersion": 2021 }
(
"export function f(...args) { args = args.reverse(); return args; }
export function g(x) { x = 1; return x; }",
None,
),
("const f = (...args) => args = [];", None),
("const f = function(...args) { args++; };", None),
("const obj = { f(...args) { args = []; } };", None),
("class C { f(...args) { args = []; } }", None),
("function f(...[arg]) { arg = 1; }", None),
("function f(...{length}) { length = 0; }", None),
(
"function f(...args) { args[0] = 1; delete args[1]; }",
Some(serde_json::json!([{ "props": true }])),
),
(
"function f(...args) { args = []; }",
Some(
serde_json::json!([{ "props": true, "ignorePropertyModificationsFor": ["args"] }]),
),
),
("function f({a, ...args}) { args = {}; }", None),
("function f(...args) { function g() { args = []; } }", None),
];
Tester::new(NoParamReassign::NAME, NoParamReassign::PLUGIN, pass, fail).test_and_snapshot();
@@ -344,3 +344,96 @@ source: crates/oxc_linter/src/tester.rs
· ─
╰────
help: Consider using a different variable to avoid unintended side effects on the parameter's properties.
⚠ eslint(no-param-reassign): Assignment to function parameter 'args'.
╭─[no_param_reassign.tsx:1:30]
1 │ export function f(...args) { args = args.reverse(); return args; }
· ────
2 │ export function g(x) { x = 1; return x; }
╰────
help: Consider using a different variable to avoid unintended side effects on the parameter.
⚠ eslint(no-param-reassign): Assignment to function parameter 'x'.
╭─[no_param_reassign.tsx:2:37]
1 │ export function f(...args) { args = args.reverse(); return args; }
2 │ export function g(x) { x = 1; return x; }
· ─
╰────
help: Consider using a different variable to avoid unintended side effects on the parameter.
⚠ eslint(no-param-reassign): Assignment to function parameter 'args'.
╭─[no_param_reassign.tsx:1:24]
1 │ const f = (...args) => args = [];
· ────
╰────
help: Consider using a different variable to avoid unintended side effects on the parameter.
⚠ eslint(no-param-reassign): Assignment to function parameter 'args'.
╭─[no_param_reassign.tsx:1:31]
1 │ const f = function(...args) { args++; };
· ────
╰────
help: Consider using a different variable to avoid unintended side effects on the parameter.
⚠ eslint(no-param-reassign): Assignment to function parameter 'args'.
╭─[no_param_reassign.tsx:1:28]
1 │ const obj = { f(...args) { args = []; } };
· ────
╰────
help: Consider using a different variable to avoid unintended side effects on the parameter.
⚠ eslint(no-param-reassign): Assignment to function parameter 'args'.
╭─[no_param_reassign.tsx:1:24]
1 │ class C { f(...args) { args = []; } }
· ────
╰────
help: Consider using a different variable to avoid unintended side effects on the parameter.
⚠ eslint(no-param-reassign): Assignment to function parameter 'arg'.
╭─[no_param_reassign.tsx:1:24]
1 │ function f(...[arg]) { arg = 1; }
· ───
╰────
help: Consider using a different variable to avoid unintended side effects on the parameter.
⚠ eslint(no-param-reassign): Assignment to function parameter 'length'.
╭─[no_param_reassign.tsx:1:27]
1 │ function f(...{length}) { length = 0; }
· ──────
╰────
help: Consider using a different variable to avoid unintended side effects on the parameter.
⚠ eslint(no-param-reassign): Assignment to property of function parameter 'args'.
╭─[no_param_reassign.tsx:1:23]
1 │ function f(...args) { args[0] = 1; delete args[1]; }
· ────
╰────
help: Consider using a different variable to avoid unintended side effects on the parameter's properties.
⚠ eslint(no-param-reassign): Assignment to property of function parameter 'args'.
╭─[no_param_reassign.tsx:1:43]
1 │ function f(...args) { args[0] = 1; delete args[1]; }
· ────
╰────
help: Consider using a different variable to avoid unintended side effects on the parameter's properties.
⚠ eslint(no-param-reassign): Assignment to function parameter 'args'.
╭─[no_param_reassign.tsx:1:23]
1 │ function f(...args) { args = []; }
· ────
╰────
help: Consider using a different variable to avoid unintended side effects on the parameter.
⚠ eslint(no-param-reassign): Assignment to function parameter 'args'.
╭─[no_param_reassign.tsx:1:28]
1 │ function f({a, ...args}) { args = {}; }
· ────
╰────
help: Consider using a different variable to avoid unintended side effects on the parameter.
⚠ eslint(no-param-reassign): Assignment to function parameter 'args'.
╭─[no_param_reassign.tsx:1:38]
1 │ function f(...args) { function g() { args = []; } }
· ────
╰────
help: Consider using a different variable to avoid unintended side effects on the parameter.
@@ -100,7 +100,7 @@ eslint/no-new-wrappers | 1421
eslint/no-nonoctal-decimal-escape | 62460
eslint/no-obj-calls | 64027
eslint/no-object-constructor | 64027
eslint/no-param-reassign | 24806
eslint/no-param-reassign | 24902
eslint/no-plusplus | 769
eslint/no-promise-executor-return | 1421
eslint/no-proto | 95297