{"record":{"id":"3a5c5074b34ab618","repo":"oxc-project/oxc","slug":"enforce-using-each-rather-than-manual-loops","errorCode":null,"errorMessage":"Enforce using `each` rather than manual loops","messagePattern":"Enforce using `each` rather than manual loops","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"crates/oxc_linter/src/rules/shared/jest_vitest/prefer_each.rs","lineNumber":13,"sourceCode":"use oxc_ast::{AstKind, AstType};\nuse oxc_diagnostics::OxcDiagnostic;\nuse oxc_semantic::{AstNode, AstTypesBitset, NodeId};\nuse oxc_span::{GetSpan, Span};\nuse rustc_hash::FxHashSet;\n\nuse crate::{\n    context::{ContextHost, LintContext},\n    utils::{JestFnKind, JestGeneralFnKind, PossibleJestNode, parse_jest_fn_call},\n};\n\nfn use_prefer_each(span: Span, fn_name: &str) -> OxcDiagnostic {\n    OxcDiagnostic::warn(\"Enforce using `each` rather than manual loops\")\n        .with_help(format!(\"Prefer using `{fn_name}.each` rather than a manual loop.\"))\n        .with_label(span)\n}\n\n#[inline]\nfn is_in_test(ctx: &LintContext<'_>, id: NodeId) -> bool {\n    ctx.nodes().ancestors(id).any(|node| {\n        let AstKind::CallExpression(ancestor_call_expr) = node.kind() else { return false };\n        let Some(ancestor_member_expr) = ancestor_call_expr.callee.as_member_expression() else {\n            return false;\n        };\n        let Some(id) = ancestor_member_expr.object().get_identifier_reference() else {\n            return false;\n        };\n\n        matches!(JestFnKind::from(id.name.as_str()), JestFnKind::General(JestGeneralFnKind::Test))\n    })\n}","sourceCodeStart":1,"sourceCodeEnd":31,"githubUrl":"https://github.com/oxc-project/oxc/blob/e1e7af627c8843ab64044ed466b128fcc21a035b/crates/oxc_linter/src/rules/shared/jest_vitest/prefer_each.rs#L1-L31","documentation":"This is the oxlint `prefer-each` rule (jest/vitest plugin). It reports `for`/`for-in`/`for-of` loops whose body directly contains a Jest test call (`test`/`it`, or `describe`/hooks), because parameterized loops are better expressed with `test.each`/`describe.each`. The loop is skipped when it is itself nested inside a test callback (a runtime loop within one test is legitimate). The file-scope `should_run` gate skips files with no loop statement at all, so the rule is cheap.","triggerScenarios":"A `for (const item of items) { describe(item, ...) }`, `for (...) { it(...) }`, or `for-in` loop at describe/module scope that directly (not nested inside another call) contains a test-family jest call; the diagnostic spans from the loop keyword to the start of its body.","commonSituations":"Data-driven test suites written before `.each` syntax was known; loops over config objects generating a describe per entry; teams migrating from Mocha where loops were the only parameterization option.","solutions":["Replace the loop with `describe.each(items)('item %j', (item) => { ... })`.","For per-case tests use `it.each(items)('works for %j', (item) => { ... })` or the template-table form `it.each([[a,b],[c,d]])('%s + %s', ...)`.","Keep the loop only if each iteration must run inside a single test (e.g. assertions share state) — the rule does not fire for loops inside a test callback."],"exampleFix":"// before\nfor (const item of items) {\n  it(`handles ${item}`, () => {\n    expect(handle(item)).toBeTruthy();\n  });\n}\n\n// after\nit.each(items)('handles %s', (item) => {\n  expect(handle(item)).toBeTruthy();\n});","handlingStrategy":"validation","validationCode":"// .oxlintrc.json\n{ \"rules\": { \"jest/prefer-each\": \"error\" } }\n\nnpx oxlint tests/","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Reach for it.each/describe.each first when generating tests from data.","Keep loops only inside a single test body where iteration is runtime behavior.","Let CI flag generated describe-loops before they spread through a suite."],"tags":["jest","vitest","oxlint","testing","parameterized-tests","static-analysis"],"backgroundTag":"parameterized-tests","analyzedSha":"e1e7af627c8843ab64044ed466b128fcc21a035b","analyzedAt":"2026-08-20T07:01:07.079Z","contentChangedAt":"2026-08-20T07:01:07.079Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}