Skip to content

Commit af257da

Browse files
magic-akaricamc314Copilot
authored
fix(linter/no-unused-vars): false positive with member expressions in sequence expressions (#15190)
- Closes: #15174 --------- Signed-off-by: Cameron <[email protected]> Co-authored-by: Cameron <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent 410b3cb commit af257da

File tree

2 files changed

+30
-1
lines changed

2 files changed

+30
-1
lines changed

crates/oxc_linter/src/rules/eslint/no_unused_vars/tests/oxc.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -988,6 +988,24 @@ fn test_arguments() {
988988
",
989989
None,
990990
),
991+
// https://github.com/oxc-project/oxc/issues/15174
992+
// Sequence expressions with member expressions in operations with side effects
993+
994+
// UpdateExpression cases
995+
("items.reduce((acc, item) => (acc[item.action]++, acc), {})", None),
996+
("items.reduce((acc, item) => (acc[item.action]--, acc), {})", None),
997+
("items.reduce((acc, item) => (++acc[item.action], acc), {})", None),
998+
("items.reduce((acc, item) => (--acc[item.action], acc), {})", None),
999+
// AssignmentExpression cases
1000+
("items.reduce((acc, item) => (acc[item.action] = 1, acc), {})", None),
1001+
("items.reduce((acc, item) => (acc.x[item.action] = 1, acc), {})", None),
1002+
("items.reduce((acc, item) => (acc[item.action] += 1, acc), {})", None),
1003+
("items.reduce((acc, item) => (acc[item.action] ||= 1, acc), {})", None),
1004+
// Nested member expressions
1005+
("foo.bar((a, b, c) => (a[b.x][c.y]++, a))", None),
1006+
("foo.bar((a, b) => (a.foo[b.bar].baz++, a))", None),
1007+
// Multiple parameters used in sequence
1008+
("foo.bar((a, b, c) => (b[c.x]++, a[b.y]++, a))", None),
9911009
];
9921010
let fail = vec![
9931011
("function foo(a) {} foo()", None),

crates/oxc_linter/src/rules/eslint/no_unused_vars/usage.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -643,7 +643,18 @@ impl<'a> Symbol<'_, 'a> {
643643
}
644644
break;
645645
}
646-
(_, AstKind::CallExpression(_) | AstKind::NewExpression(_)) => break,
646+
(_, AstKind::CallExpression(_) | AstKind::NewExpression(_))
647+
| (
648+
// in `(acc[item.action]++, acc)`, reference to `item` should still be considered
649+
// used, even though it's not in the last position of the sequence.
650+
// However, in `(a++, 0)`, `a` should be considered discarded.
651+
// We detect this by checking if there's a MemberExpression in the parent chain.
652+
AstKind::ComputedMemberExpression(_)
653+
| AstKind::StaticMemberExpression(_)
654+
| AstKind::PrivateFieldExpression(_),
655+
// Note: AssignmentExpression is NOT needed here because we already handle it.
656+
AstKind::UpdateExpression(_),
657+
) => break,
647658
// (AstKind::FunctionBody(_), _) => return true,
648659
// in `(x = a, 0)`, reference to `a` should still be considered
649660
// used. Note that this branch must come before the sequence

0 commit comments

Comments
 (0)