fix(linter/jest/no-conditional-expect): check function expression callbacks (#28055)

`jest/no-conditional-expect` now follows anonymous and named function expression callbacks to their enclosing test call, matching `eslint-plugin-jest`. Function declarations retain their existing reference-based test detection.

The shared fix also applies to Vitest. Regression cases cover async callbacks, test modifiers, `test.each`, and promise catches, alongside cases that should remain unreported.

Fixes #28054
This commit is contained in:
camc314 committed 2026-10-21 21:21:39 +00:00
1 parent dd6cb2be00
commit 4629bb294a
5 files changed
+105 -1

No files matched your search

@@ -305,6 +305,10 @@ fn test() {
});",
None,
),
("it('foo', function () { expect(1).toBe(1); });", None),
("it('foo', function callback() { expect(1).toBe(1); });", None),
("const callback = function () { if (x) { expect(1).toBe(1); } };", None),
("const callback = function named() { if (x) { expect(1).toBe(1); } };", None),
];
let fail = vec![
@@ -760,6 +764,12 @@ fn test() {
",
None,
),
("it('foo', function () { if (x) { expect(1).toBe(1); } });", None),
("it('foo', function callback() { if (x) { expect(1).toBe(1); } });", None),
("it('foo', async function () { if (x) { expect(1).toBe(1); } });", None),
("test.only('foo', function () { x && expect(1).toBe(1); });", None),
("test.each([1])('foo', function () { x ? expect(1).toBe(1) : noop(); });", None),
("it('foo', function () { promise.catch(function () { expect(1).toBe(1); }); });", None),
];
Tester::new(NoConditionalExpect::NAME, NoConditionalExpect::PLUGIN, pass, fail)
@@ -177,7 +177,7 @@ fn check_parents<'a>(
| AstKind::LogicalExpression(_) => {
return check_parents(parent_node, visited, InConditional(true), ctx);
}
AstKind::Function(function) => {
AstKind::Function(function) if function.is_declaration() => {
let Some(ident) = &function.id else {
return InConditional(false);
};
@@ -305,6 +305,10 @@ fn test() {
});",
None,
),
("it('foo', function () { expect(1).toBe(1); });", None),
("it('foo', function callback() { expect(1).toBe(1); });", None),
("const callback = function () { if (x) { expect(1).toBe(1); } };", None),
("const callback = function named() { if (x) { expect(1).toBe(1); } };", None),
];
let mut fail = vec![
@@ -760,6 +764,12 @@ fn test() {
",
None,
),
("it('foo', function () { if (x) { expect(1).toBe(1); } });", None),
("it('foo', function callback() { if (x) { expect(1).toBe(1); } });", None),
("it('foo', async function () { if (x) { expect(1).toBe(1); } });", None),
("test.only('foo', function () { x && expect(1).toBe(1); });", None),
("test.each([1])('foo', function () { x ? expect(1).toBe(1) : noop(); });", None),
("it('foo', function () { promise.catch(function () { expect(1).toBe(1); }); });", None),
];
let pass_vitest = vec![
@@ -415,3 +415,45 @@ source: crates/oxc_linter/src/tester.rs
8 │ }
╰────
help: Avoid calling `expect` conditionally
⚠ jest(no-conditional-expect): Unexpected conditional expect
╭─[no_conditional_expect.tsx:1:34]
1 │ it('foo', function () { if (x) { expect(1).toBe(1); } });
· ──────
╰────
help: Avoid calling `expect` conditionally
⚠ jest(no-conditional-expect): Unexpected conditional expect
╭─[no_conditional_expect.tsx:1:42]
1 │ it('foo', function callback() { if (x) { expect(1).toBe(1); } });
· ──────
╰────
help: Avoid calling `expect` conditionally
⚠ jest(no-conditional-expect): Unexpected conditional expect
╭─[no_conditional_expect.tsx:1:40]
1 │ it('foo', async function () { if (x) { expect(1).toBe(1); } });
· ──────
╰────
help: Avoid calling `expect` conditionally
⚠ jest(no-conditional-expect): Unexpected conditional expect
╭─[no_conditional_expect.tsx:1:37]
1 │ test.only('foo', function () { x && expect(1).toBe(1); });
· ──────
╰────
help: Avoid calling `expect` conditionally
⚠ jest(no-conditional-expect): Unexpected conditional expect
╭─[no_conditional_expect.tsx:1:41]
1 │ test.each([1])('foo', function () { x ? expect(1).toBe(1) : noop(); });
· ──────
╰────
help: Avoid calling `expect` conditionally
⚠ jest(no-conditional-expect): Unexpected conditional expect
╭─[no_conditional_expect.tsx:1:53]
1 │ it('foo', function () { promise.catch(function () { expect(1).toBe(1); }); });
· ──────
╰────
help: Avoid calling `expect` conditionally
@@ -416,6 +416,48 @@ source: crates/oxc_linter/src/tester.rs
╰────
help: Avoid calling `expect` conditionally
⚠ vitest(no-conditional-expect): Unexpected conditional expect
╭─[no_conditional_expect.tsx:1:34]
1 │ it('foo', function () { if (x) { expect(1).toBe(1); } });
· ──────
╰────
help: Avoid calling `expect` conditionally
⚠ vitest(no-conditional-expect): Unexpected conditional expect
╭─[no_conditional_expect.tsx:1:42]
1 │ it('foo', function callback() { if (x) { expect(1).toBe(1); } });
· ──────
╰────
help: Avoid calling `expect` conditionally
⚠ vitest(no-conditional-expect): Unexpected conditional expect
╭─[no_conditional_expect.tsx:1:40]
1 │ it('foo', async function () { if (x) { expect(1).toBe(1); } });
· ──────
╰────
help: Avoid calling `expect` conditionally
⚠ vitest(no-conditional-expect): Unexpected conditional expect
╭─[no_conditional_expect.tsx:1:37]
1 │ test.only('foo', function () { x && expect(1).toBe(1); });
· ──────
╰────
help: Avoid calling `expect` conditionally
⚠ vitest(no-conditional-expect): Unexpected conditional expect
╭─[no_conditional_expect.tsx:1:41]
1 │ test.each([1])('foo', function () { x ? expect(1).toBe(1) : noop(); });
· ──────
╰────
help: Avoid calling `expect` conditionally
⚠ vitest(no-conditional-expect): Unexpected conditional expect
╭─[no_conditional_expect.tsx:1:53]
1 │ it('foo', function () { promise.catch(function () { expect(1).toBe(1); }); });
· ──────
╰────
help: Avoid calling `expect` conditionally
⚠ vitest(no-conditional-expect): Unexpected conditional expect
╭─[no_conditional_expect.tsx:3:30]
2 │ it('foo', () => {