mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
fix(linter/eslint/no-useless-assignment): report dead stores to function parameters (#27735)
Track function parameter bindings so `function f(p) { p = 5; p = 6; return p; }` reports `p = 5`, matching ESLint. This also covers defaulted, destructured, and rest parameters.
Keep writes during parameter initialization excluded: the CFG does not model skipped defaults or their destructuring evaluation order reliably. Reads in defaults still count as uses.
Fixes #27720
This commit is contained in:
1 parent
ee5e730ca9
commit
9ff25c3a14
2 files changed
+200
-21
No files matched your search
@@ -159,22 +159,30 @@ impl Rule for NoUselessAssignment {
|
||||
|
||||
for symbol_id in ctx.scoping().symbol_ids() {
|
||||
let decl_node = ctx.symbol_declaration(symbol_id);
|
||||
let AstKind::VariableDeclarator(var_decl) = decl_node.kind() else { continue };
|
||||
if let AstKind::VariableDeclaration(var_declaration) =
|
||||
ctx.nodes().parent_node(decl_node.id()).kind()
|
||||
&& var_declaration.kind == VariableDeclarationKind::Const
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if matches!(
|
||||
&var_decl.init,
|
||||
Some(Expression::FunctionExpression(_) | Expression::ArrowFunctionExpression(_))
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
let var_decl = match decl_node.kind() {
|
||||
AstKind::VariableDeclarator(var_decl) => Some(var_decl),
|
||||
AstKind::FormalParameter(_) | AstKind::FormalParameterRest(_) => None,
|
||||
_ => continue,
|
||||
};
|
||||
if let Some(var_decl) = var_decl {
|
||||
if let AstKind::VariableDeclaration(var_declaration) =
|
||||
ctx.nodes().parent_node(decl_node.id()).kind()
|
||||
&& var_declaration.kind == VariableDeclarationKind::Const
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if matches!(
|
||||
&var_decl.init,
|
||||
Some(
|
||||
Expression::FunctionExpression(_) | Expression::ArrowFunctionExpression(_)
|
||||
)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if Self::is_exported(ctx, symbol_id, decl_node) {
|
||||
continue;
|
||||
if Self::is_exported(ctx, symbol_id, decl_node) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
#[expect(clippy::cast_possible_truncation)]
|
||||
@@ -187,7 +195,7 @@ impl Rule for NoUselessAssignment {
|
||||
});
|
||||
|
||||
// Collect ops for this symbol (formerly Pass 2)
|
||||
if var_decl.init.is_some() {
|
||||
if var_decl.is_some_and(|var_decl| var_decl.init.is_some()) {
|
||||
Self::push_op(
|
||||
ctx,
|
||||
graph,
|
||||
@@ -533,7 +541,7 @@ impl NoUselessAssignment {
|
||||
cfg_ops: &mut CfgOps,
|
||||
reference: &Reference,
|
||||
compact_idx: u32,
|
||||
var_decl: &oxc_ast::ast::VariableDeclarator,
|
||||
var_decl: Option<&oxc_ast::ast::VariableDeclarator>,
|
||||
decl_node: &oxc_semantic::AstNode,
|
||||
tracked_symbol: &mut TrackedSymbol,
|
||||
previous_value_read: bool,
|
||||
@@ -555,6 +563,17 @@ impl NoUselessAssignment {
|
||||
}
|
||||
|
||||
if reference.is_write() {
|
||||
// Parameter initialization lacks conditional CFG edges and ordered destructuring writes.
|
||||
if var_decl.is_none()
|
||||
&& ctx
|
||||
.nodes()
|
||||
.parent_node(decl_node.id())
|
||||
.span()
|
||||
.contains_inclusive(ctx.nodes().get_node(op_node).span())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if previous_value_read && !reference.is_read() {
|
||||
// Model RHS self-reads before the deferred assignment write.
|
||||
// This keeps the previous value live without making nested
|
||||
@@ -562,10 +581,12 @@ impl NoUselessAssignment {
|
||||
Self::push_op(ctx, graph, cfg_ops, op_node, Operation::Read, compact_idx);
|
||||
}
|
||||
|
||||
if matches!(
|
||||
&var_decl.id,
|
||||
BindingPattern::ObjectPattern(_) | BindingPattern::ArrayPattern(_)
|
||||
) && decl_node
|
||||
if var_decl.is_some_and(|var_decl| {
|
||||
matches!(
|
||||
&var_decl.id,
|
||||
BindingPattern::ObjectPattern(_) | BindingPattern::ArrayPattern(_)
|
||||
)
|
||||
}) && decl_node
|
||||
.span()
|
||||
.contains_inclusive(ctx.nodes().get_node(reference.node_id()).span())
|
||||
{
|
||||
@@ -1520,6 +1541,18 @@ function useResource(unsafe: (resource: { readonly release: () => void }) => voi
|
||||
published = (local = 1);
|
||||
continue;
|
||||
}",
|
||||
"function f(p) { p = 1; return p; }",
|
||||
"function f(p) { p = 1; }",
|
||||
"function f(p = 0) { p = 1; return p; }",
|
||||
"function f({p = 0}) { p = 1; return p; }",
|
||||
"function f([p = 0]) { p = 1; return p; }",
|
||||
"function f(...p) { p = []; return p; }",
|
||||
"function f(p) { p = 1; p = 2; return () => p; }",
|
||||
"function f(p) { p = p + 1; return p; }",
|
||||
"function f(p) { p = 1; if (condition) { use(p); } p = 2; return p; }",
|
||||
"function f(p, q = (p = 1), r = (p = 2)) { return p; }",
|
||||
"function f(p, { [p = 1]: q } = (p = 2, {})) { return p; }",
|
||||
"function f(p, { a = (p = 1) } = (p = 2, {})) { return p; }",
|
||||
];
|
||||
|
||||
let fail = vec![
|
||||
@@ -1864,6 +1897,24 @@ function useResource(unsafe: (resource: { readonly release: () => void }) => voi
|
||||
"let local = 0;
|
||||
export let exported = (local = 1);
|
||||
use(local);",
|
||||
"export function reassignedParam(p) { p = 5; p = 6; return p; }
|
||||
export function reassignedLocal() { let x = 1; x = 2; return x; }",
|
||||
"const f = function(p) { p = 1; p = 2; return p; };",
|
||||
"const f = p => { p = 1; p = 2; return p; };",
|
||||
"const obj = { f(p) { p = 1; p = 2; return p; } };",
|
||||
"class C { f(p) { p = 1; p = 2; return p; } }",
|
||||
"function f(p = 0) { p = 1; p = 2; return p; }",
|
||||
"function f({p}) { p = 1; p = 2; return p; }",
|
||||
"function f([p]) { p = 1; p = 2; return p; }",
|
||||
"function f({nested: [p = 0]}) { p = 1; p = 2; return p; }",
|
||||
"function f(...p) { p = []; p = [1]; return p; }",
|
||||
"function f(...[p]) { p = 1; p = 2; return p; }",
|
||||
"function f({a, ...p}) { p = {}; p = {a}; return p; }",
|
||||
"export const p = 0; function f(p) { p = 1; p = 2; return p; }",
|
||||
"function f(p) { p++; p = 2; return p; }",
|
||||
"function f(p) { use(p); p = 1; }",
|
||||
"function f(p, q = p) { p = 1; return q; }",
|
||||
"function f(cb = p => { p = 1; p = 2; return p; }) { return cb; }",
|
||||
];
|
||||
|
||||
Tester::new(NoUselessAssignment::NAME, NoUselessAssignment::PLUGIN, pass, fail)
|
||||
|
||||
@@ -532,3 +532,131 @@ source: crates/oxc_linter/src/tester.rs
|
||||
2 │ export let exported = (local = 1);
|
||||
╰────
|
||||
help: Consider removing or reusing the assigned value.
|
||||
|
||||
⚠ eslint(no-useless-assignment): This assigned value is not used in subsequent statements.
|
||||
╭─[no_useless_assignment.tsx:2:50]
|
||||
1 │ export function reassignedParam(p) { p = 5; p = 6; return p; }
|
||||
2 │ export function reassignedLocal() { let x = 1; x = 2; return x; }
|
||||
· ─
|
||||
╰────
|
||||
help: Consider removing or reusing the assigned value.
|
||||
|
||||
⚠ eslint(no-useless-assignment): This assigned value is not used in subsequent statements.
|
||||
╭─[no_useless_assignment.tsx:1:38]
|
||||
1 │ export function reassignedParam(p) { p = 5; p = 6; return p; }
|
||||
· ─
|
||||
2 │ export function reassignedLocal() { let x = 1; x = 2; return x; }
|
||||
╰────
|
||||
help: Consider removing or reusing the assigned value.
|
||||
|
||||
⚠ eslint(no-useless-assignment): This assigned value is not used in subsequent statements.
|
||||
╭─[no_useless_assignment.tsx:1:25]
|
||||
1 │ const f = function(p) { p = 1; p = 2; return p; };
|
||||
· ─
|
||||
╰────
|
||||
help: Consider removing or reusing the assigned value.
|
||||
|
||||
⚠ eslint(no-useless-assignment): This assigned value is not used in subsequent statements.
|
||||
╭─[no_useless_assignment.tsx:1:18]
|
||||
1 │ const f = p => { p = 1; p = 2; return p; };
|
||||
· ─
|
||||
╰────
|
||||
help: Consider removing or reusing the assigned value.
|
||||
|
||||
⚠ eslint(no-useless-assignment): This assigned value is not used in subsequent statements.
|
||||
╭─[no_useless_assignment.tsx:1:22]
|
||||
1 │ const obj = { f(p) { p = 1; p = 2; return p; } };
|
||||
· ─
|
||||
╰────
|
||||
help: Consider removing or reusing the assigned value.
|
||||
|
||||
⚠ eslint(no-useless-assignment): This assigned value is not used in subsequent statements.
|
||||
╭─[no_useless_assignment.tsx:1:18]
|
||||
1 │ class C { f(p) { p = 1; p = 2; return p; } }
|
||||
· ─
|
||||
╰────
|
||||
help: Consider removing or reusing the assigned value.
|
||||
|
||||
⚠ eslint(no-useless-assignment): This assigned value is not used in subsequent statements.
|
||||
╭─[no_useless_assignment.tsx:1:21]
|
||||
1 │ function f(p = 0) { p = 1; p = 2; return p; }
|
||||
· ─
|
||||
╰────
|
||||
help: Consider removing or reusing the assigned value.
|
||||
|
||||
⚠ eslint(no-useless-assignment): This assigned value is not used in subsequent statements.
|
||||
╭─[no_useless_assignment.tsx:1:19]
|
||||
1 │ function f({p}) { p = 1; p = 2; return p; }
|
||||
· ─
|
||||
╰────
|
||||
help: Consider removing or reusing the assigned value.
|
||||
|
||||
⚠ eslint(no-useless-assignment): This assigned value is not used in subsequent statements.
|
||||
╭─[no_useless_assignment.tsx:1:19]
|
||||
1 │ function f([p]) { p = 1; p = 2; return p; }
|
||||
· ─
|
||||
╰────
|
||||
help: Consider removing or reusing the assigned value.
|
||||
|
||||
⚠ eslint(no-useless-assignment): This assigned value is not used in subsequent statements.
|
||||
╭─[no_useless_assignment.tsx:1:33]
|
||||
1 │ function f({nested: [p = 0]}) { p = 1; p = 2; return p; }
|
||||
· ─
|
||||
╰────
|
||||
help: Consider removing or reusing the assigned value.
|
||||
|
||||
⚠ eslint(no-useless-assignment): This assigned value is not used in subsequent statements.
|
||||
╭─[no_useless_assignment.tsx:1:20]
|
||||
1 │ function f(...p) { p = []; p = [1]; return p; }
|
||||
· ─
|
||||
╰────
|
||||
help: Consider removing or reusing the assigned value.
|
||||
|
||||
⚠ eslint(no-useless-assignment): This assigned value is not used in subsequent statements.
|
||||
╭─[no_useless_assignment.tsx:1:22]
|
||||
1 │ function f(...[p]) { p = 1; p = 2; return p; }
|
||||
· ─
|
||||
╰────
|
||||
help: Consider removing or reusing the assigned value.
|
||||
|
||||
⚠ eslint(no-useless-assignment): This assigned value is not used in subsequent statements.
|
||||
╭─[no_useless_assignment.tsx:1:25]
|
||||
1 │ function f({a, ...p}) { p = {}; p = {a}; return p; }
|
||||
· ─
|
||||
╰────
|
||||
help: Consider removing or reusing the assigned value.
|
||||
|
||||
⚠ eslint(no-useless-assignment): This assigned value is not used in subsequent statements.
|
||||
╭─[no_useless_assignment.tsx:1:37]
|
||||
1 │ export const p = 0; function f(p) { p = 1; p = 2; return p; }
|
||||
· ─
|
||||
╰────
|
||||
help: Consider removing or reusing the assigned value.
|
||||
|
||||
⚠ eslint(no-useless-assignment): This assigned value is not used in subsequent statements.
|
||||
╭─[no_useless_assignment.tsx:1:17]
|
||||
1 │ function f(p) { p++; p = 2; return p; }
|
||||
· ─
|
||||
╰────
|
||||
help: Consider removing or reusing the assigned value.
|
||||
|
||||
⚠ eslint(no-useless-assignment): This assigned value is not used in subsequent statements.
|
||||
╭─[no_useless_assignment.tsx:1:25]
|
||||
1 │ function f(p) { use(p); p = 1; }
|
||||
· ─
|
||||
╰────
|
||||
help: Consider removing or reusing the assigned value.
|
||||
|
||||
⚠ eslint(no-useless-assignment): This assigned value is not used in subsequent statements.
|
||||
╭─[no_useless_assignment.tsx:1:24]
|
||||
1 │ function f(p, q = p) { p = 1; return q; }
|
||||
· ─
|
||||
╰────
|
||||
help: Consider removing or reusing the assigned value.
|
||||
|
||||
⚠ eslint(no-useless-assignment): This assigned value is not used in subsequent statements.
|
||||
╭─[no_useless_assignment.tsx:1:24]
|
||||
1 │ function f(cb = p => { p = 1; p = 2; return p; }) { return cb; }
|
||||
· ─
|
||||
╰────
|
||||
help: Consider removing or reusing the assigned value.
|
||||
Reference in new issue
Block a user