Skip to content

Commit ad12974

Browse files
committed
fix(linter): no-unused-vars: allow updates in for loop test/update (#14766)
- Fixes #14765 Allows update expressions when they are within a for-loop update/test.
1 parent 92b4302 commit ad12974

File tree

2 files changed

+37
-5
lines changed

2 files changed

+37
-5
lines changed

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1287,16 +1287,24 @@ fn test_ts_in_assignment() {
12871287

12881288
#[test]
12891289
fn test_loops() {
1290-
let pass: Vec<&str> = vec![];
1290+
let pass: Vec<&str> = vec![
1291+
"for (let len = 10; len-- > 0;) {}",
1292+
"for (let len = 10; len < 0; len++) {}",
1293+
"for (let len = 10; len;) {}",
1294+
"for (let len = 10;; len++) {}",
1295+
"for (let len = 10; len < 0; len += 1) {}",
1296+
"for (const _unused of []) {}",
1297+
];
12911298

1292-
let fail: Vec<&str> = vec![];
1299+
let fail: Vec<&str> = vec!["for (let len = 10;;) {}"];
12931300
let fix = vec![
12941301
("for (const unused of arr) {}", "for (const _unused of arr) {}"),
12951302
("for (const unused in arr) {}", "for (const _unused in arr) {}"),
12961303
(
12971304
"for (const foo of arr) { console.log(foo); const unused = 1; }",
12981305
"for (const foo of arr) { console.log(foo); }",
12991306
),
1307+
("for (let len = 10;;) {}", "for (;;) {}"),
13001308
];
13011309

13021310
Tester::new(NoUnusedVars::NAME, NoUnusedVars::PLUGIN, pass, fail).expect_fix(fix).test();

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

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -424,9 +424,12 @@ impl<'a> Symbol<'_, 'a> {
424424
// When symbol is being assigned a new value, we flag the reference
425425
// as only affecting itself until proven otherwise.
426426
AstKind::UpdateExpression(UpdateExpression { argument, .. }) => {
427-
// `a.b++` or `a[b] + 1` are not reassignment of `a`
428-
if !argument.is_member_expression() {
429-
is_used_by_others = false;
427+
// `for (let x = 0; x++; ) {}` is valid usage, as the loop body running is a side-effect
428+
if !self.is_in_for_loop_test_or_update(node.id(), ref_span) {
429+
// `a.b++` or `a[b] + 1` are not reassignment of `a`
430+
if !argument.is_member_expression() {
431+
is_used_by_others = false;
432+
}
430433
}
431434
}
432435
// RHS usage when LHS != reference's symbol is definitely used by
@@ -543,6 +546,27 @@ impl<'a> Symbol<'_, 'a> {
543546
!is_used_by_others
544547
}
545548

549+
/// Check if a [`AstNode`] is within the test or update section of a for loop.
550+
fn is_in_for_loop_test_or_update(&self, node_id: NodeId, node_span: Span) -> bool {
551+
for parent in self.iter_relevant_parents_of(node_id).map(AstNode::kind) {
552+
match parent {
553+
AstKind::ForStatement(for_stmt) => {
554+
return for_stmt
555+
.test
556+
.as_ref()
557+
.is_some_and(|test| test.span().contains_inclusive(node_span))
558+
|| for_stmt
559+
.update
560+
.as_ref()
561+
.is_some_and(|update| update.span().contains_inclusive(node_span));
562+
}
563+
x if x.is_statement() => return false,
564+
_ => {}
565+
}
566+
}
567+
false
568+
}
569+
546570
/// Check if a [`AstNode`] is within a return statement or implicit return.
547571
fn is_in_return_statement(&self, node_id: NodeId) -> bool {
548572
for parent in self.iter_relevant_parents_of(node_id).map(AstNode::kind) {

0 commit comments

Comments
 (0)