diff --git a/crates/oxc_linter/src/rules/jest/valid_describe_callback.rs b/crates/oxc_linter/src/rules/jest/valid_describe_callback.rs index 5777e702b6611..80abd7c39ce84 100644 --- a/crates/oxc_linter/src/rules/jest/valid_describe_callback.rs +++ b/crates/oxc_linter/src/rules/jest/valid_describe_callback.rs @@ -10,15 +10,20 @@ use crate::{ context::LintContext, rule::Rule, utils::{ - collect_possible_jest_call_node, parse_general_jest_fn_call, JestFnKind, JestGeneralFnKind, - PossibleJestNode, + collect_possible_jest_call_node, get_test_plugin_name, parse_general_jest_fn_call, + JestFnKind, JestGeneralFnKind, PossibleJestNode, TestPluginName, }, }; -fn valid_describe_callback_diagnostic(x0: &str, x1: &str, span2: Span) -> OxcDiagnostic { - OxcDiagnostic::warn(format!("eslint-plugin-jest(valid-describe-callback): {x0:?}")) - .with_help(format!("{x1:?}")) - .with_label(span2) +fn valid_describe_callback_diagnostic( + x0: TestPluginName, + x1: &str, + x2: &str, + span3: Span, +) -> OxcDiagnostic { + OxcDiagnostic::warn(format!("{x0}(valid-describe-callback): {x1:?}")) + .with_help(format!("{x2:?}")) + .with_label(span3) } #[derive(Debug, Default, Clone)] @@ -58,6 +63,17 @@ declare_oxc_lint!( /// expect(myFunction()).toBeTruthy(); /// })); /// ``` + /// + /// This rule is compatible with [eslint-plugin-vitest](https://github.com/veritem/eslint-plugin-vitest/blob/main/docs/rules/valid-describe-callback.md), + /// to use it, add the following configuration to your `.eslintrc.json`: + /// + /// ```json + /// { + /// "rules": { + /// "vitest/valid-describe-callback": "error" + /// } + /// } + /// ``` ValidDescribeCallback, correctness ); @@ -151,7 +167,8 @@ fn find_first_return_stmt_span(function_body: &FunctionBody) -> Option { fn diagnostic(ctx: &LintContext, span: Span, message: Message) { let (error, help) = message.details(); - ctx.diagnostic(valid_describe_callback_diagnostic(error, help, span)); + let plugin_name = get_test_plugin_name(ctx); + ctx.diagnostic(valid_describe_callback_diagnostic(plugin_name, error, help, span)); } #[derive(Clone, Copy)] @@ -192,7 +209,7 @@ impl Message { fn test() { use crate::tester::Tester; - let pass = vec![ + let mut pass = vec![ ("describe.each([1, 2, 3])('%s', (a, b) => {});", None), ("describe('foo', function() {})", None), ("describe('foo', () => {})", None), @@ -235,7 +252,7 @@ fn test() { ), ]; - let fail = vec![ + let mut fail = vec![ ("describe.each()()", None), ("describe['each']()()", None), ("describe.each(() => {})()", None), @@ -331,5 +348,157 @@ fn test() { ("describe('foo', async function (done) {})", None), ]; - Tester::new(ValidDescribeCallback::NAME, pass, fail).with_jest_plugin(true).test_and_snapshot(); + let pass_vitest = vec![ + ("describe.each([1, 2, 3])(\"%s\", (a, b) => {});", None), + ("describe(\"foo\", function() {})", None), + ("describe(\"foo\", () => {})", None), + ("describe(`foo`, () => {})", None), + ("xdescribe(\"foo\", () => {})", None), + ("fdescribe(\"foo\", () => {})", None), + ("describe.only(\"foo\", () => {})", None), + ("describe.skip(\"foo\", () => {})", None), + ("describe.todo(\"runPrettierFormat\");", None), + ( + " + describe('foo', () => { + it('bar', () => { + return Promise.resolve(42).then(value => { + expect(value).toBe(42) + }) + }) + }) + ", + None, + ), + ( + " + describe('foo', () => { + it('bar', async () => { + expect(await Promise.resolve(42)).toBe(42) + }) + }) + ", + None, + ), + ( + " + if (hasOwnProperty(obj, key)) { + } + ", + None, + ), + ( + " + describe.each` + foo | foe + ${1} | ${2} + `('$something', ({ foo, foe }) => {}); + ", + None, + ), + ]; + + let fail_vitest = vec![ + ("describe.each()()", None), + ("describe[\"each\"]()()", None), + ("describe.each(() => {})()", None), + ("describe.each(() => {})(\"foo\")", None), + ("describe.each()(() => {})", None), + ("describe[\"each\"]()(() => {})", None), + ("describe.each(\"foo\")(() => {})", None), + ("describe.only.each(\"foo\")(() => {})", None), + ("describe(() => {})", None), + ("describe(\"foo\")", None), + ("describe(\"foo\", \"foo2\")", None), + ("describe()", None), + ("describe(\"foo\", async () => {})", None), + ("describe(\"foo\", async function () {})", None), + ("xdescribe(\"foo\", async function () {})", None), + ("fdescribe(\"foo\", async function () {})", None), + ("describe.only(\"foo\", async function () {})", None), + ("describe.skip(\"foo\", async function () {})", None), + ( + " + describe('sample case', () => { + it('works', () => { + expect(true).toEqual(true); + }); + describe('async', async () => { + await new Promise(setImmediate); + it('breaks', () => { + throw new Error('Fail'); + }); + }); + }); + ", + None, + ), + ( + " + describe('foo', function () { + return Promise.resolve().then(() => { + it('breaks', () => { + throw new Error('Fail') + }) + }) + }) + ", + None, + ), + ( + " + describe('foo', () => { + return Promise.resolve().then(() => { + it('breaks', () => { + throw new Error('Fail') + }) + }) + describe('nested', () => { + return Promise.resolve().then(() => { + it('breaks', () => { + throw new Error('Fail') + }) + }) + }) + }) + ", + None, + ), + ( + " + describe('foo', async () => { + await something() + it('does something') + describe('nested', () => { + return Promise.resolve().then(() => { + it('breaks', () => { + throw new Error('Fail') + }) + }) + }) + }) + ", + None, + ), + ( + " + describe('foo', () => + test('bar', () => {}) + ) + ", + None, + ), + ("describe(\"foo\", done => {})", None), + ("describe(\"foo\", function (done) {})", None), + ("describe(\"foo\", function (one, two, three) {})", None), + ("describe(\"foo\", async function (done) {})", None), + ]; + + pass.extend(pass_vitest); + fail.extend(fail_vitest); + + Tester::new(ValidDescribeCallback::NAME, pass, fail) + .with_jest_plugin(true) + .with_vitest_plugin(true) + .test_and_snapshot(); } diff --git a/crates/oxc_linter/src/snapshots/valid_describe_callback.snap b/crates/oxc_linter/src/snapshots/valid_describe_callback.snap index 14447cf61b746..c1eb53935c64d 100644 --- a/crates/oxc_linter/src/snapshots/valid_describe_callback.snap +++ b/crates/oxc_linter/src/snapshots/valid_describe_callback.snap @@ -1,5 +1,6 @@ --- source: crates/oxc_linter/src/tester.rs +assertion_line: 216 --- ⚠ eslint-plugin-jest(valid-describe-callback): "Describe requires name and callback arguments" ╭─[valid_describe_callback.tsx:1:1] @@ -256,3 +257,252 @@ source: crates/oxc_linter/src/tester.rs · ──────────────────────── ╰──── help: "Remove argument(s) of describe callback" + + ⚠ eslint-plugin-jest(valid-describe-callback): "Describe requires name and callback arguments" + ╭─[valid_describe_callback.tsx:1:1] + 1 │ describe.each()() + · ───────────────── + ╰──── + help: "Add name as first argument and callback as second argument" + + ⚠ eslint-plugin-jest(valid-describe-callback): "Describe requires name and callback arguments" + ╭─[valid_describe_callback.tsx:1:1] + 1 │ describe["each"]()() + · ──────────────────── + ╰──── + help: "Add name as first argument and callback as second argument" + + ⚠ eslint-plugin-jest(valid-describe-callback): "Describe requires name and callback arguments" + ╭─[valid_describe_callback.tsx:1:1] + 1 │ describe.each(() => {})() + · ───────────────────────── + ╰──── + help: "Add name as first argument and callback as second argument" + + ⚠ eslint-plugin-jest(valid-describe-callback): "Describe requires name and callback arguments" + ╭─[valid_describe_callback.tsx:1:25] + 1 │ describe.each(() => {})("foo") + · ───── + ╰──── + help: "Add name as first argument and callback as second argument" + + ⚠ eslint-plugin-jest(valid-describe-callback): "Describe requires name and callback arguments" + ╭─[valid_describe_callback.tsx:1:17] + 1 │ describe.each()(() => {}) + · ──────── + ╰──── + help: "Add name as first argument and callback as second argument" + + ⚠ eslint-plugin-jest(valid-describe-callback): "Describe requires name and callback arguments" + ╭─[valid_describe_callback.tsx:1:20] + 1 │ describe["each"]()(() => {}) + · ──────── + ╰──── + help: "Add name as first argument and callback as second argument" + + ⚠ eslint-plugin-jest(valid-describe-callback): "Describe requires name and callback arguments" + ╭─[valid_describe_callback.tsx:1:22] + 1 │ describe.each("foo")(() => {}) + · ──────── + ╰──── + help: "Add name as first argument and callback as second argument" + + ⚠ eslint-plugin-jest(valid-describe-callback): "Describe requires name and callback arguments" + ╭─[valid_describe_callback.tsx:1:27] + 1 │ describe.only.each("foo")(() => {}) + · ──────── + ╰──── + help: "Add name as first argument and callback as second argument" + + ⚠ eslint-plugin-jest(valid-describe-callback): "Describe requires name and callback arguments" + ╭─[valid_describe_callback.tsx:1:10] + 1 │ describe(() => {}) + · ──────── + ╰──── + help: "Add name as first argument and callback as second argument" + + ⚠ eslint-plugin-jest(valid-describe-callback): "Describe requires name and callback arguments" + ╭─[valid_describe_callback.tsx:1:10] + 1 │ describe("foo") + · ───── + ╰──── + help: "Add name as first argument and callback as second argument" + + ⚠ eslint-plugin-jest(valid-describe-callback): "Second argument must be a function" + ╭─[valid_describe_callback.tsx:1:17] + 1 │ describe("foo", "foo2") + · ────── + ╰──── + help: "Replace second argument with a function" + + ⚠ eslint-plugin-jest(valid-describe-callback): "Describe requires name and callback arguments" + ╭─[valid_describe_callback.tsx:1:1] + 1 │ describe() + · ────────── + ╰──── + help: "Add name as first argument and callback as second argument" + + ⚠ eslint-plugin-jest(valid-describe-callback): "No async describe callback" + ╭─[valid_describe_callback.tsx:1:17] + 1 │ describe("foo", async () => {}) + · ────────────── + ╰──── + help: "Remove `async` keyword" + + ⚠ eslint-plugin-jest(valid-describe-callback): "No async describe callback" + ╭─[valid_describe_callback.tsx:1:17] + 1 │ describe("foo", async function () {}) + · ──────────────────── + ╰──── + help: "Remove `async` keyword" + + ⚠ eslint-plugin-jest(valid-describe-callback): "No async describe callback" + ╭─[valid_describe_callback.tsx:1:18] + 1 │ xdescribe("foo", async function () {}) + · ──────────────────── + ╰──── + help: "Remove `async` keyword" + + ⚠ eslint-plugin-jest(valid-describe-callback): "No async describe callback" + ╭─[valid_describe_callback.tsx:1:18] + 1 │ fdescribe("foo", async function () {}) + · ──────────────────── + ╰──── + help: "Remove `async` keyword" + + ⚠ eslint-plugin-jest(valid-describe-callback): "No async describe callback" + ╭─[valid_describe_callback.tsx:1:22] + 1 │ describe.only("foo", async function () {}) + · ──────────────────── + ╰──── + help: "Remove `async` keyword" + + ⚠ eslint-plugin-jest(valid-describe-callback): "No async describe callback" + ╭─[valid_describe_callback.tsx:1:22] + 1 │ describe.skip("foo", async function () {}) + · ──────────────────── + ╰──── + help: "Remove `async` keyword" + + ⚠ eslint-plugin-jest(valid-describe-callback): "No async describe callback" + ╭─[valid_describe_callback.tsx:6:39] + 5 │ }); + 6 │ ╭─▶ describe('async', async () => { + 7 │ │ await new Promise(setImmediate); + 8 │ │ it('breaks', () => { + 9 │ │ throw new Error('Fail'); + 10 │ │ }); + 11 │ ╰─▶ }); + 12 │ }); + ╰──── + help: "Remove `async` keyword" + + ⚠ eslint-plugin-jest(valid-describe-callback): "Unexpected return statement in describe callback" + ╭─[valid_describe_callback.tsx:3:21] + 2 │ describe('foo', function () { + 3 │ ╭─▶ return Promise.resolve().then(() => { + 4 │ │ it('breaks', () => { + 5 │ │ throw new Error('Fail') + 6 │ │ }) + 7 │ ╰─▶ }) + 8 │ }) + ╰──── + help: "Remove return statement in your describe callback" + + ⚠ eslint-plugin-jest(valid-describe-callback): "Unexpected return statement in describe callback" + ╭─[valid_describe_callback.tsx:9:25] + 8 │ describe('nested', () => { + 9 │ ╭─▶ return Promise.resolve().then(() => { + 10 │ │ it('breaks', () => { + 11 │ │ throw new Error('Fail') + 12 │ │ }) + 13 │ ╰─▶ }) + 14 │ }) + ╰──── + help: "Remove return statement in your describe callback" + + ⚠ eslint-plugin-jest(valid-describe-callback): "Unexpected return statement in describe callback" + ╭─[valid_describe_callback.tsx:3:21] + 2 │ describe('foo', () => { + 3 │ ╭─▶ return Promise.resolve().then(() => { + 4 │ │ it('breaks', () => { + 5 │ │ throw new Error('Fail') + 6 │ │ }) + 7 │ ╰─▶ }) + 8 │ describe('nested', () => { + ╰──── + help: "Remove return statement in your describe callback" + + ⚠ eslint-plugin-jest(valid-describe-callback): "Unexpected return statement in describe callback" + ╭─[valid_describe_callback.tsx:6:25] + 5 │ describe('nested', () => { + 6 │ ╭─▶ return Promise.resolve().then(() => { + 7 │ │ it('breaks', () => { + 8 │ │ throw new Error('Fail') + 9 │ │ }) + 10 │ ╰─▶ }) + 11 │ }) + ╰──── + help: "Remove return statement in your describe callback" + + ⚠ eslint-plugin-jest(valid-describe-callback): "No async describe callback" + ╭─[valid_describe_callback.tsx:2:33] + 1 │ + 2 │ ╭─▶ describe('foo', async () => { + 3 │ │ await something() + 4 │ │ it('does something') + 5 │ │ describe('nested', () => { + 6 │ │ return Promise.resolve().then(() => { + 7 │ │ it('breaks', () => { + 8 │ │ throw new Error('Fail') + 9 │ │ }) + 10 │ │ }) + 11 │ │ }) + 12 │ ╰─▶ }) + 13 │ + ╰──── + help: "Remove `async` keyword" + + ⚠ eslint-plugin-jest(valid-describe-callback): "Unexpected return statement in describe callback" + ╭─[valid_describe_callback.tsx:3:21] + 2 │ describe('foo', () => + 3 │ test('bar', () => {}) + · ───────────────────── + 4 │ ) + ╰──── + help: "Remove return statement in your describe callback" + + ⚠ eslint-plugin-jest(valid-describe-callback): "Unexpected argument(s) in describe callback" + ╭─[valid_describe_callback.tsx:1:17] + 1 │ describe("foo", done => {}) + · ────────── + ╰──── + help: "Remove argument(s) of describe callback" + + ⚠ eslint-plugin-jest(valid-describe-callback): "Unexpected argument(s) in describe callback" + ╭─[valid_describe_callback.tsx:1:17] + 1 │ describe("foo", function (done) {}) + · ────────────────── + ╰──── + help: "Remove argument(s) of describe callback" + + ⚠ eslint-plugin-jest(valid-describe-callback): "Unexpected argument(s) in describe callback" + ╭─[valid_describe_callback.tsx:1:17] + 1 │ describe("foo", function (one, two, three) {}) + · ───────────────────────────── + ╰──── + help: "Remove argument(s) of describe callback" + + ⚠ eslint-plugin-jest(valid-describe-callback): "No async describe callback" + ╭─[valid_describe_callback.tsx:1:17] + 1 │ describe("foo", async function (done) {}) + · ──────────────────────── + ╰──── + help: "Remove `async` keyword" + + ⚠ eslint-plugin-jest(valid-describe-callback): "Unexpected argument(s) in describe callback" + ╭─[valid_describe_callback.tsx:1:17] + 1 │ describe("foo", async function (done) {}) + · ──────────────────────── + ╰──── + help: "Remove argument(s) of describe callback" diff --git a/crates/oxc_linter/src/utils/mod.rs b/crates/oxc_linter/src/utils/mod.rs index 4804b791db7df..331c67ad05ecc 100644 --- a/crates/oxc_linter/src/utils/mod.rs +++ b/crates/oxc_linter/src/utils/mod.rs @@ -22,6 +22,7 @@ pub fn is_jest_rule_adapted_to_vitest(rule_name: &str) -> bool { "no-focused-tests", "no-test-prefixes", "prefer-hooks-in-order", + "valid-describe-callback", "valid-expect", ];