diff --git a/crates/oxc_linter/src/rules/vitest/prefer_each.rs b/crates/oxc_linter/src/rules/vitest/prefer_each.rs index e8bb6479acfb3..014884d43d4f1 100644 --- a/crates/oxc_linter/src/rules/vitest/prefer_each.rs +++ b/crates/oxc_linter/src/rules/vitest/prefer_each.rs @@ -1,3 +1,5 @@ +use std::collections::HashSet; + use oxc_ast::AstKind; use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; @@ -60,7 +62,16 @@ declare_oxc_lint!( ); impl Rule for PreferEach { - fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + fn run_once(&self, ctx: &LintContext<'_>) { + let mut skip = HashSet::::new(); + ctx.nodes().iter().for_each(|node| { + Self::run(node, ctx, &mut skip); + }); + } +} + +impl PreferEach { + fn run<'a>(node: &AstNode<'a>, ctx: &LintContext<'a>, skip: &mut HashSet) { let kind = node.kind(); let AstKind::CallExpression(call_expr) = kind else { return }; @@ -88,19 +99,35 @@ impl Rule for PreferEach { AstKind::ForStatement(_) | AstKind::ForInStatement(_) | AstKind::ForOfStatement(_) => { - if !is_in_test(ctx, parent_node.id()) { - let fn_name = if matches!( - vitest_fn_call.kind(), - JestFnKind::General(JestGeneralFnKind::Test) - ) { - "it" - } else { - "describe" - }; - - ctx.diagnostic(use_prefer_each(call_expr.callee.span(), fn_name)); + if skip.contains(&parent_node.id()) || is_in_test(ctx, parent_node.id()) { + return; } + let fn_name = if matches!( + vitest_fn_call.kind(), + JestFnKind::General(JestGeneralFnKind::Test) + ) { + "it" + } else { + "describe" + }; + + let span = match parent_node.kind() { + AstKind::ForStatement(statement) => { + Span::new(statement.span.start, statement.body.span().start) + } + AstKind::ForInStatement(statement) => { + Span::new(statement.span.start, statement.body.span().start) + } + AstKind::ForOfStatement(statement) => { + Span::new(statement.span.start, statement.body.span().start) + } + _ => unreachable!(), + }; + + skip.insert(parent_node.id()); + ctx.diagnostic(use_prefer_each(span, fn_name)); + break; } _ => {} diff --git a/crates/oxc_linter/src/snapshots/prefer_each.snap b/crates/oxc_linter/src/snapshots/prefer_each.snap index 4c05fbae29151..930894635a5ec 100644 --- a/crates/oxc_linter/src/snapshots/prefer_each.snap +++ b/crates/oxc_linter/src/snapshots/prefer_each.snap @@ -2,154 +2,119 @@ source: crates/oxc_linter/src/tester.rs --- ⚠ eslint-plugin-vitest(prefer-each): Enforce using `each` rather than manual loops - ╭─[prefer_each.tsx:2:10] + ╭─[prefer_each.tsx:1:3] 1 │ for (const [input, expected] of data) { + · ────────────────────────────────────── 2 │ it(`results in ${expected}`, () => { - · ── - 3 │ expect(fn(input)).toBe(expected) ╰──── help: Prefer using `it.each` rather than a manual loop. ⚠ eslint-plugin-vitest(prefer-each): Enforce using `each` rather than manual loops - ╭─[prefer_each.tsx:2:10] + ╭─[prefer_each.tsx:1:2] 1 │ for (const [input, expected] of data) { + · ────────────────────────────────────── 2 │ describe(`when the input is ${input}`, () => { - · ──────── - 3 │ it(`results in ${expected}`, () => { ╰──── help: Prefer using `describe.each` rather than a manual loop. ⚠ eslint-plugin-vitest(prefer-each): Enforce using `each` rather than manual loops - ╭─[prefer_each.tsx:2:10] + ╭─[prefer_each.tsx:1:1] 1 │ for (const [input, expected] of data) { + · ────────────────────────────────────── 2 │ describe(`when the input is ${input}`, () => { - · ──────── - 3 │ it(`results in ${expected}`, () => { ╰──── help: Prefer using `describe.each` rather than a manual loop. ⚠ eslint-plugin-vitest(prefer-each): Enforce using `each` rather than manual loops - ╭─[prefer_each.tsx:10:10] + ╭─[prefer_each.tsx:9:11] + 8 │ 9 │ for (const [input, expected] of data) { + · ────────────────────────────────────── 10 │ it.skip(`results in ${expected}`, () => { - · ─────── - 11 │ expect(fn(input)).toBe(expected) ╰──── help: Prefer using `it.each` rather than a manual loop. ⚠ eslint-plugin-vitest(prefer-each): Enforce using `each` rather than manual loops - ╭─[prefer_each.tsx:2:10] + ╭─[prefer_each.tsx:1:1] 1 │ for (const [input, expected] of data) { + · ────────────────────────────────────── 2 │ it.skip(`results in ${expected}`, () => { - · ─────── - 3 │ expect(fn(input)).toBe(expected) ╰──── help: Prefer using `it.each` rather than a manual loop. ⚠ eslint-plugin-vitest(prefer-each): Enforce using `each` rather than manual loops - ╭─[prefer_each.tsx:6:10] + ╭─[prefer_each.tsx:5:11] + 4 │ 5 │ for (const [input, expected] of data) { + · ────────────────────────────────────── 6 │ it.skip(`results in ${expected}`, () => { - · ─────── - 7 │ expect(fn(input)).toBe(expected) ╰──── help: Prefer using `it.each` rather than a manual loop. ⚠ eslint-plugin-vitest(prefer-each): Enforce using `each` rather than manual loops - ╭─[prefer_each.tsx:2:10] + ╭─[prefer_each.tsx:1:2] 1 │ for (const [input, expected] of data) { + · ────────────────────────────────────── 2 │ it.skip(`results in ${expected}`, () => { - · ─────── - 3 │ expect(fn(input)).toBe(expected) ╰──── help: Prefer using `it.each` rather than a manual loop. ⚠ eslint-plugin-vitest(prefer-each): Enforce using `each` rather than manual loops - ╭─[prefer_each.tsx:6:10] + ╭─[prefer_each.tsx:5:11] + 4 │ 5 │ for (const [input, expected] of data) { + · ────────────────────────────────────── 6 │ it.skip(`results in ${expected}`, () => { - · ─────── - 7 │ expect(fn(input)).toBe(expected) ╰──── help: Prefer using `it.each` rather than a manual loop. ⚠ eslint-plugin-vitest(prefer-each): Enforce using `each` rather than manual loops - ╭─[prefer_each.tsx:2:10] + ╭─[prefer_each.tsx:1:1] 1 │ for (const [input, expected] of data) { + · ────────────────────────────────────── 2 │ it(`results in ${expected}`, () => { - · ── - 3 │ expect(fn(input)).toBe(expected) - ╰──── - help: Prefer using `it.each` rather than a manual loop. - - ⚠ eslint-plugin-vitest(prefer-each): Enforce using `each` rather than manual loops - ╭─[prefer_each.tsx:6:10] - 5 │ - 6 │ it(`results in ${expected}`, () => { - · ── - 7 │ expect(fn(input)).toBe(expected) ╰──── help: Prefer using `it.each` rather than a manual loop. ⚠ eslint-plugin-vitest(prefer-each): Enforce using `each` rather than manual loops - ╭─[prefer_each.tsx:2:10] + ╭─[prefer_each.tsx:1:1] 1 │ for (const [input, expected] of data) { + · ────────────────────────────────────── 2 │ it(`results in ${expected}`, () => { - · ── - 3 │ expect(fn(input)).toBe(expected) ╰──── help: Prefer using `it.each` rather than a manual loop. ⚠ eslint-plugin-vitest(prefer-each): Enforce using `each` rather than manual loops - ╭─[prefer_each.tsx:8:10] + ╭─[prefer_each.tsx:7:11] + 6 │ 7 │ for (const [input, expected] of data) { + · ────────────────────────────────────── 8 │ it(`results in ${expected}`, () => { - · ── - 9 │ expect(fn(input)).toBe(expected) ╰──── help: Prefer using `it.each` rather than a manual loop. ⚠ eslint-plugin-vitest(prefer-each): Enforce using `each` rather than manual loops - ╭─[prefer_each.tsx:2:10] + ╭─[prefer_each.tsx:1:1] 1 │ for (const [input, expected] of data) { + · ────────────────────────────────────── 2 │ beforeEach(() => setupSomething(input)); - · ────────── - 3 │ ╰──── help: Prefer using `describe.each` rather than a manual loop. ⚠ eslint-plugin-vitest(prefer-each): Enforce using `each` rather than manual loops - ╭─[prefer_each.tsx:4:10] - 3 │ - 4 │ test(`results in ${expected}`, () => { - · ──── - 5 │ expect(doSomething()).toBe(expected) - ╰──── - help: Prefer using `it.each` rather than a manual loop. - - ⚠ eslint-plugin-vitest(prefer-each): Enforce using `each` rather than manual loops - ╭─[prefer_each.tsx:3:10] + ╭─[prefer_each.tsx:2:11] + 1 │ 2 │ for (const [input, expected] of data) { + · ────────────────────────────────────── 3 │ it("only returns numbers that are greater than seven", function () { - · ── - 4 │ const numbers = getNumbers(input); ╰──── help: Prefer using `it.each` rather than a manual loop. ⚠ eslint-plugin-vitest(prefer-each): Enforce using `each` rather than manual loops - ╭─[prefer_each.tsx:3:10] + ╭─[prefer_each.tsx:2:11] + 1 │ 2 │ for (const [input, expected] of data) { + · ────────────────────────────────────── 3 │ beforeEach(() => setupSomething(input)); - · ────────── - 4 │ ╰──── help: Prefer using `describe.each` rather than a manual loop. - - ⚠ eslint-plugin-vitest(prefer-each): Enforce using `each` rather than manual loops - ╭─[prefer_each.tsx:5:10] - 4 │ - 5 │ it("only returns numbers that are greater than seven", function () { - · ── - 6 │ const numbers = getNumbers(); - ╰──── - help: Prefer using `it.each` rather than a manual loop.