diff --git a/crates/oxc_linter/src/generated/rule_runner_impls.rs b/crates/oxc_linter/src/generated/rule_runner_impls.rs index eb7a4b3da7fff..dc6c369bd3041 100644 --- a/crates/oxc_linter/src/generated/rule_runner_impls.rs +++ b/crates/oxc_linter/src/generated/rule_runner_impls.rs @@ -4299,6 +4299,11 @@ impl RuleRunner for crate::rules::vitest::require_local_test_context_for_concurr const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::RunOnJestNode; } +impl RuleRunner for crate::rules::vitest::valid_expect::ValidExpect { + const NODE_TYPES: Option<&AstTypesBitset> = None; + const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::RunOnJestNode; +} + impl RuleRunner for crate::rules::vitest::warn_todo::WarnTodo { const NODE_TYPES: Option<&AstTypesBitset> = None; const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::RunOnJestNode; diff --git a/crates/oxc_linter/src/generated/rules_enum.rs b/crates/oxc_linter/src/generated/rules_enum.rs index 1dd33290fd77a..178a576a1b58c 100644 --- a/crates/oxc_linter/src/generated/rules_enum.rs +++ b/crates/oxc_linter/src/generated/rules_enum.rs @@ -688,6 +688,7 @@ pub use crate::rules::vitest::prefer_to_be_falsy::PreferToBeFalsy as VitestPrefe pub use crate::rules::vitest::prefer_to_be_object::PreferToBeObject as VitestPreferToBeObject; pub use crate::rules::vitest::prefer_to_be_truthy::PreferToBeTruthy as VitestPreferToBeTruthy; pub use crate::rules::vitest::require_local_test_context_for_concurrent_snapshots::RequireLocalTestContextForConcurrentSnapshots as VitestRequireLocalTestContextForConcurrentSnapshots; +pub use crate::rules::vitest::valid_expect::ValidExpect as VitestValidExpect; pub use crate::rules::vitest::warn_todo::WarnTodo as VitestWarnTodo; pub use crate::rules::vue::define_emits_declaration::DefineEmitsDeclaration as VueDefineEmitsDeclaration; pub use crate::rules::vue::define_props_declaration::DefinePropsDeclaration as VueDefinePropsDeclaration; @@ -1394,6 +1395,7 @@ pub enum RuleEnum { VitestRequireLocalTestContextForConcurrentSnapshots( VitestRequireLocalTestContextForConcurrentSnapshots, ), + VitestValidExpect(VitestValidExpect), VitestWarnTodo(VitestWarnTodo), NodeGlobalRequire(NodeGlobalRequire), NodeNoExportsAssign(NodeNoExportsAssign), @@ -3783,6 +3785,7 @@ impl RuleEnum { Self::VitestRequireLocalTestContextForConcurrentSnapshots(_) => { VitestRequireLocalTestContextForConcurrentSnapshots::NAME } + Self::VitestValidExpect(_) => VitestValidExpect::NAME, Self::VitestWarnTodo(_) => VitestWarnTodo::NAME, Self::NodeGlobalRequire(_) => NodeGlobalRequire::NAME, Self::NodeNoExportsAssign(_) => NodeNoExportsAssign::NAME, @@ -4625,6 +4628,7 @@ impl RuleEnum { Self::VitestRequireLocalTestContextForConcurrentSnapshots(_) => { VitestRequireLocalTestContextForConcurrentSnapshots::CATEGORY } + Self::VitestValidExpect(_) => VitestValidExpect::CATEGORY, Self::VitestWarnTodo(_) => VitestWarnTodo::CATEGORY, Self::NodeGlobalRequire(_) => NodeGlobalRequire::CATEGORY, Self::NodeNoExportsAssign(_) => NodeNoExportsAssign::CATEGORY, @@ -5426,6 +5430,7 @@ impl RuleEnum { Self::VitestRequireLocalTestContextForConcurrentSnapshots(_) => { VitestRequireLocalTestContextForConcurrentSnapshots::FIX } + Self::VitestValidExpect(_) => VitestValidExpect::FIX, Self::VitestWarnTodo(_) => VitestWarnTodo::FIX, Self::NodeGlobalRequire(_) => NodeGlobalRequire::FIX, Self::NodeNoExportsAssign(_) => NodeNoExportsAssign::FIX, @@ -6421,6 +6426,7 @@ impl RuleEnum { Self::VitestRequireLocalTestContextForConcurrentSnapshots(_) => { VitestRequireLocalTestContextForConcurrentSnapshots::documentation() } + Self::VitestValidExpect(_) => VitestValidExpect::documentation(), Self::VitestWarnTodo(_) => VitestWarnTodo::documentation(), Self::NodeGlobalRequire(_) => NodeGlobalRequire::documentation(), Self::NodeNoExportsAssign(_) => NodeNoExportsAssign::documentation(), @@ -8368,6 +8374,8 @@ impl RuleEnum { VitestRequireLocalTestContextForConcurrentSnapshots::schema(generator) }) } + Self::VitestValidExpect(_) => VitestValidExpect::config_schema(generator) + .or_else(|| VitestValidExpect::schema(generator)), Self::VitestWarnTodo(_) => VitestWarnTodo::config_schema(generator) .or_else(|| VitestWarnTodo::schema(generator)), Self::NodeGlobalRequire(_) => NodeGlobalRequire::config_schema(generator) @@ -9110,6 +9118,7 @@ impl RuleEnum { Self::VitestPreferToBeObject(_) => "vitest", Self::VitestPreferToBeTruthy(_) => "vitest", Self::VitestRequireLocalTestContextForConcurrentSnapshots(_) => "vitest", + Self::VitestValidExpect(_) => "vitest", Self::VitestWarnTodo(_) => "vitest", Self::NodeGlobalRequire(_) => "node", Self::NodeNoExportsAssign(_) => "node", @@ -11309,6 +11318,9 @@ impl RuleEnum { VitestRequireLocalTestContextForConcurrentSnapshots::from_configuration(value)?, )) } + Self::VitestValidExpect(_) => { + Ok(Self::VitestValidExpect(VitestValidExpect::from_configuration(value)?)) + } Self::VitestWarnTodo(_) => { Ok(Self::VitestWarnTodo(VitestWarnTodo::from_configuration(value)?)) } @@ -12061,6 +12073,7 @@ impl RuleEnum { Self::VitestRequireLocalTestContextForConcurrentSnapshots(rule) => { rule.to_configuration() } + Self::VitestValidExpect(rule) => rule.to_configuration(), Self::VitestWarnTodo(rule) => rule.to_configuration(), Self::NodeGlobalRequire(rule) => rule.to_configuration(), Self::NodeNoExportsAssign(rule) => rule.to_configuration(), @@ -12763,6 +12776,7 @@ impl RuleEnum { Self::VitestPreferToBeObject(rule) => rule.run(node, ctx), Self::VitestPreferToBeTruthy(rule) => rule.run(node, ctx), Self::VitestRequireLocalTestContextForConcurrentSnapshots(rule) => rule.run(node, ctx), + Self::VitestValidExpect(rule) => rule.run(node, ctx), Self::VitestWarnTodo(rule) => rule.run(node, ctx), Self::NodeGlobalRequire(rule) => rule.run(node, ctx), Self::NodeNoExportsAssign(rule) => rule.run(node, ctx), @@ -13465,6 +13479,7 @@ impl RuleEnum { Self::VitestPreferToBeObject(rule) => rule.run_once(ctx), Self::VitestPreferToBeTruthy(rule) => rule.run_once(ctx), Self::VitestRequireLocalTestContextForConcurrentSnapshots(rule) => rule.run_once(ctx), + Self::VitestValidExpect(rule) => rule.run_once(ctx), Self::VitestWarnTodo(rule) => rule.run_once(ctx), Self::NodeGlobalRequire(rule) => rule.run_once(ctx), Self::NodeNoExportsAssign(rule) => rule.run_once(ctx), @@ -14267,6 +14282,7 @@ impl RuleEnum { Self::VitestRequireLocalTestContextForConcurrentSnapshots(rule) => { rule.run_on_jest_node(jest_node, ctx) } + Self::VitestValidExpect(rule) => rule.run_on_jest_node(jest_node, ctx), Self::VitestWarnTodo(rule) => rule.run_on_jest_node(jest_node, ctx), Self::NodeGlobalRequire(rule) => rule.run_on_jest_node(jest_node, ctx), Self::NodeNoExportsAssign(rule) => rule.run_on_jest_node(jest_node, ctx), @@ -14969,6 +14985,7 @@ impl RuleEnum { Self::VitestPreferToBeObject(rule) => rule.should_run(ctx), Self::VitestPreferToBeTruthy(rule) => rule.should_run(ctx), Self::VitestRequireLocalTestContextForConcurrentSnapshots(rule) => rule.should_run(ctx), + Self::VitestValidExpect(rule) => rule.should_run(ctx), Self::VitestWarnTodo(rule) => rule.should_run(ctx), Self::NodeGlobalRequire(rule) => rule.should_run(ctx), Self::NodeNoExportsAssign(rule) => rule.should_run(ctx), @@ -15963,6 +15980,7 @@ impl RuleEnum { Self::VitestRequireLocalTestContextForConcurrentSnapshots(_) => { VitestRequireLocalTestContextForConcurrentSnapshots::IS_TSGOLINT_RULE } + Self::VitestValidExpect(_) => VitestValidExpect::IS_TSGOLINT_RULE, Self::VitestWarnTodo(_) => VitestWarnTodo::IS_TSGOLINT_RULE, Self::NodeGlobalRequire(_) => NodeGlobalRequire::IS_TSGOLINT_RULE, Self::NodeNoExportsAssign(_) => NodeNoExportsAssign::IS_TSGOLINT_RULE, @@ -16834,6 +16852,7 @@ impl RuleEnum { Self::VitestRequireLocalTestContextForConcurrentSnapshots(_) => { VitestRequireLocalTestContextForConcurrentSnapshots::HAS_CONFIG } + Self::VitestValidExpect(_) => VitestValidExpect::HAS_CONFIG, Self::VitestWarnTodo(_) => VitestWarnTodo::HAS_CONFIG, Self::NodeGlobalRequire(_) => NodeGlobalRequire::HAS_CONFIG, Self::NodeNoExportsAssign(_) => NodeNoExportsAssign::HAS_CONFIG, @@ -17538,6 +17557,7 @@ impl RuleEnum { Self::VitestPreferToBeObject(rule) => rule.types_info(), Self::VitestPreferToBeTruthy(rule) => rule.types_info(), Self::VitestRequireLocalTestContextForConcurrentSnapshots(rule) => rule.types_info(), + Self::VitestValidExpect(rule) => rule.types_info(), Self::VitestWarnTodo(rule) => rule.types_info(), Self::NodeGlobalRequire(rule) => rule.types_info(), Self::NodeNoExportsAssign(rule) => rule.types_info(), @@ -18240,6 +18260,7 @@ impl RuleEnum { Self::VitestPreferToBeObject(rule) => rule.run_info(), Self::VitestPreferToBeTruthy(rule) => rule.run_info(), Self::VitestRequireLocalTestContextForConcurrentSnapshots(rule) => rule.run_info(), + Self::VitestValidExpect(rule) => rule.run_info(), Self::VitestWarnTodo(rule) => rule.run_info(), Self::NodeGlobalRequire(rule) => rule.run_info(), Self::NodeNoExportsAssign(rule) => rule.run_info(), @@ -19060,6 +19081,7 @@ pub static RULES: std::sync::LazyLock> = std::sync::LazyLock::new( RuleEnum::VitestRequireLocalTestContextForConcurrentSnapshots( VitestRequireLocalTestContextForConcurrentSnapshots::default(), ), + RuleEnum::VitestValidExpect(VitestValidExpect::default()), RuleEnum::VitestWarnTodo(VitestWarnTodo::default()), RuleEnum::NodeGlobalRequire(NodeGlobalRequire::default()), RuleEnum::NodeNoExportsAssign(NodeNoExportsAssign::default()), diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 7d61594c08432..04fce29f8d5a9 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -717,6 +717,7 @@ pub(crate) mod vitest { pub mod prefer_to_be_object; pub mod prefer_to_be_truthy; pub mod require_local_test_context_for_concurrent_snapshots; + pub mod valid_expect; pub mod warn_todo; } diff --git a/crates/oxc_linter/src/rules/jest/valid_expect.rs b/crates/oxc_linter/src/rules/jest/valid_expect.rs index 9a49195d486ae..11f6975276c48 100644 --- a/crates/oxc_linter/src/rules/jest/valid_expect.rs +++ b/crates/oxc_linter/src/rules/jest/valid_expect.rs @@ -74,14 +74,12 @@ declare_oxc_lint!( /// expect(); /// expect('something'); /// expect(true).toBeDefined; - /// expect(Promise.resolve('Hi!')).resolves.toBe('Hi!'); /// ``` /// /// Examples of **correct** code for this rule: /// ```javascript /// expect('something').toEqual('something'); /// expect(true).toBeDefined(); - /// expect(Promise.resolve('Hi!')).resolves.toBe('Hi!'); /// ``` /// /// This rule is compatible with [eslint-plugin-vitest](https://github.com/vitest-dev/eslint-plugin-vitest/blob/v1.1.9/docs/rules/valid-expect.md), @@ -450,11 +448,12 @@ fn test_1() { Tester::new(ValidExpect::NAME, ValidExpect::PLUGIN, pass, fail).with_jest_plugin(true).test(); } + #[test] fn test() { use crate::tester::Tester; - let mut pass = vec![ + let pass = vec![ ("expect.hasAssertions", None), ("expect.hasAssertions()", None), ("expect('something').toEqual('else');", None), @@ -579,12 +578,9 @@ fn test() { ("test('valid-expect', () => { expect(Promise.reject(2)).toRejectWith(2); });", Some(serde_json::json!([{ "asyncMatchers": ["toResolveWith"] }]))), ("test('valid-expect', async () => { await expect(Promise.resolve(2)).toResolve(); });", Some(serde_json::json!([{ "asyncMatchers": ["toResolveWith"] }]))), ("test('valid-expect', async () => { expect(Promise.resolve(2)).toResolve(); });", Some(serde_json::json!([{ "asyncMatchers": ["toResolveWith"] }]))), - ( - "import { describe, expect, it } from 'vitest'; async function runCaseInWorker(type, props) { await runCase({ type, props }, expect); }", None - ) ]; - let mut fail = vec![ + let fail = vec![ ("expect().toBe(2);", None), ("expect().toBe(true);", None), ("expect().toEqual('something');", None), @@ -832,335 +828,7 @@ fn test() { ), ]; - let pass_vitest = vec![ - ("expect.hasAssertions", None), - ("expect.hasAssertions()", None), - ("expect(\"something\").toEqual(\"else\");", None), - ("expect(true).toBeDefined();", None), - ("expect([1, 2, 3]).toEqual([1, 2, 3]);", None), - ("expect(undefined).not.toBeDefined();", None), - ("test(\"valid-expect\", () => { return expect(Promise.resolve(2)).resolves.toBeDefined(); });", None), - ("test(\"valid-expect\", () => { return expect(Promise.reject(2)).rejects.toBeDefined(); });", None), - ("test(\"valid-expect\", () => { return expect(Promise.resolve(2)).resolves.not.toBeDefined(); });", None), - ("test(\"valid-expect\", () => { return expect(Promise.resolve(2)).rejects.not.toBeDefined(); });", None), - ("test(\"valid-expect\", function () { return expect(Promise.resolve(2)).resolves.not.toBeDefined(); });", None), - ("test(\"valid-expect\", function () { return expect(Promise.resolve(2)).rejects.not.toBeDefined(); });", None), - ("test(\"valid-expect\", function () { return Promise.resolve(expect(Promise.resolve(2)).resolves.not.toBeDefined()); });", None), - ("test(\"valid-expect\", function () { return Promise.resolve(expect(Promise.resolve(2)).rejects.not.toBeDefined()); });", None), - ("test(\"valid-expect\", () => expect(Promise.resolve(2)).resolves.toBeDefined());", None), - ("test(\"valid-expect\", () => expect(Promise.reject(2)).rejects.toBeDefined());", None), - ("test(\"valid-expect\", () => expect(Promise.reject(2)).resolves.not.toBeDefined());", None), - ("test(\"valid-expect\", () => expect(Promise.reject(2)).rejects.not.toBeDefined());", None), - ("test(\"valid-expect\", async () => { await expect(Promise.reject(2)).resolves.not.toBeDefined(); });", None), - ("test(\"valid-expect\", async () => { await expect(Promise.reject(2)).rejects.not.toBeDefined(); });", None), - ("test(\"valid-expect\", async function () { await expect(Promise.reject(2)).resolves.not.toBeDefined(); });", None), - ("test(\"valid-expect\", async function () { await expect(Promise.reject(2)).rejects.not.toBeDefined(); });", None), - ("test(\"valid-expect\", async () => { await Promise.resolve(expect(Promise.reject(2)).rejects.not.toBeDefined()); });", None), - ("test(\"valid-expect\", async () => { await Promise.reject(expect(Promise.reject(2)).rejects.not.toBeDefined()); });", None), - ("test(\"valid-expect\", async () => { await Promise.all([expect(Promise.reject(2)).rejects.not.toBeDefined(), expect(Promise.reject(2)).rejects.not.toBeDefined()]); });", None), - ("test(\"valid-expect\", async () => { await Promise.race([expect(Promise.reject(2)).rejects.not.toBeDefined(), expect(Promise.reject(2)).rejects.not.toBeDefined()]); });", None), - ("test(\"valid-expect\", async () => { await Promise.allSettled([expect(Promise.reject(2)).rejects.not.toBeDefined(), expect(Promise.reject(2)).rejects.not.toBeDefined()]); });", None), - ("test(\"valid-expect\", async () => { await Promise.any([expect(Promise.reject(2)).rejects.not.toBeDefined(), expect(Promise.reject(2)).rejects.not.toBeDefined()]); });", None), - ("test(\"valid-expect\", async () => { return expect(Promise.reject(2)).resolves.not.toBeDefined().then(() => console.log(\"valid-case\")); });", None), - ("test(\"valid-expect\", async () => { return expect(Promise.reject(2)).resolves.not.toBeDefined().then(() => console.log(\"valid-case\")).then(() => console.log(\"another valid case\")); });", None), - ("test(\"valid-expect\", async () => { return expect(Promise.reject(2)).resolves.not.toBeDefined().catch(() => console.log(\"valid-case\")); });", None), - ("test(\"valid-expect\", async () => { return expect(Promise.reject(2)).resolves.not.toBeDefined().then(() => console.log(\"valid-case\")).catch(() => console.log(\"another valid case\")); });", None), - ("test(\"valid-expect\", async () => { return expect(Promise.reject(2)).resolves.not.toBeDefined().then(() => { expect(someMock).toHaveBeenCalledTimes(1); }); });", None), - ("test(\"valid-expect\", async () => { await expect(Promise.reject(2)).resolves.not.toBeDefined().then(() => console.log(\"valid-case\")); });", None), - ("test(\"valid-expect\", async () => { await expect(Promise.reject(2)).resolves.not.toBeDefined().then(() => console.log(\"valid-case\")).then(() => console.log(\"another valid case\")); });", None), - ("test(\"valid-expect\", async () => { await expect(Promise.reject(2)).resolves.not.toBeDefined().catch(() => console.log(\"valid-case\")); });", None), - ("test(\"valid-expect\", async () => { await expect(Promise.reject(2)).resolves.not.toBeDefined().then(() => console.log(\"valid-case\")).catch(() => console.log(\"another valid case\")); });", None), - ("test(\"valid-expect\", async () => { await expect(Promise.reject(2)).resolves.not.toBeDefined().then(() => { expect(someMock).toHaveBeenCalledTimes(1); }); });", None), - ( - " - test(\"valid-expect\", () => { - return expect(functionReturningAPromise()).resolves.toEqual(1).then(() => { - return expect(Promise.resolve(2)).resolves.toBe(1); - }); - }); - ", - None, - ), - ( - " - test(\"valid-expect\", () => { - return expect(functionReturningAPromise()).resolves.toEqual(1).then(async () => { - await expect(Promise.resolve(2)).resolves.toBe(1); - }); - }); - ", - None, - ), - ( - " - test(\"valid-expect\", () => { - return expect(functionReturningAPromise()).resolves.toEqual(1).then(() => expect(Promise.resolve(2)).resolves.toBe(1)); - }); - ", - None, - ), - ( - " - expect.extend({ - toResolve(obj) { - return this.isNot - ? expect(obj).toBe(true) - : expect(obj).resolves.not.toThrow(); - } - }); - ", - None, - ), - ( - " - expect.extend({ - toResolve(obj) { - return this.isNot - ? expect(obj).resolves.not.toThrow() - : expect(obj).toBe(true); - } - }); - ", - None, - ), - ( - " - expect.extend({ - toResolve(obj) { - return this.isNot - ? expect(obj).toBe(true) - : anotherCondition - ? expect(obj).resolves.not.toThrow() - : expect(obj).toBe(false) - } - }); - ", - None, - ), - ("expect(1).toBe(2);", Some(serde_json::json!([{ "maxArgs": 2 }]))), - ("expect(1, \"1 !== 2\").toBe(2);", Some(serde_json::json!([{ "maxArgs": 2 }]))), - ( - "test(\"valid-expect\", () => { expect(2).not.toBe(2); });", - Some(serde_json::json!([{ "asyncMatchers": ["toRejectWith"] }])), - ), - ( - "test(\"valid-expect\", () => { expect(Promise.reject(2)).toRejectWith(2); });", - Some(serde_json::json!([{ "asyncMatchers": ["toResolveWith"] }])), - ), - ( - "test(\"valid-expect\", async () => { await expect(Promise.resolve(2)).toResolve(); });", - Some(serde_json::json!([{ "asyncMatchers": ["toResolveWith"] }])), - ), - ( - "test(\"valid-expect\", async () => { expect(Promise.resolve(2)).toResolve(); });", - Some(serde_json::json!([{ "asyncMatchers": ["toResolveWith"] }])), - ), - ]; - - let fail_vitest = vec![ - ("expect().toBe(2);", Some(serde_json::json!([{ "minArgs": "undefined", "maxArgs": "undefined" }]))), - ("expect().toBe(true);", None), - ("expect().toEqual(\"something\");", None), - ("expect(\"something\", \"else\").toEqual(\"something\");", None), - ("expect(\"something\", \"else\", \"entirely\").toEqual(\"something\");", Some(serde_json::json!([{ "maxArgs": 2 }]))), - ("expect(\"something\", \"else\", \"entirely\").toEqual(\"something\");", Some(serde_json::json!([{ "maxArgs": 2, "minArgs": 2 }]))), - ("expect(\"something\", \"else\", \"entirely\").toEqual(\"something\");", Some(serde_json::json!([{ "maxArgs": 2, "minArgs": 1 }]))), - ("expect(\"something\").toEqual(\"something\");", Some(serde_json::json!([{ "minArgs": 2 }]))), - ("expect(\"something\", \"else\").toEqual(\"something\");", Some(serde_json::json!([{ "maxArgs": 1, "minArgs": 3 }]))), - ("expect(\"something\");", None), - ("expect();", None), - ("expect(true).toBeDefined;", None), - ("expect(true).not.toBeDefined;", None), - ("expect(true).nope.toBeDefined;", None), - ("expect(true).nope.toBeDefined();", None), - ("expect(true).not.resolves.toBeDefined();", None), - ("expect(true).not.not.toBeDefined();", None), - ("expect(true).resolves.not.exactly.toBeDefined();", None), - ("expect(true).resolves;", None), - ("expect(true).rejects;", None), - ("expect(true).not;", None), - ("expect(Promise.resolve(2)).resolves.toBeDefined();", None), - ("expect(Promise.resolve(2)).rejects.toBeDefined();", None), - ("expect(Promise.resolve(2)).resolves.toBeDefined();", Some(serde_json::json!([{ "alwaysAwait": true }]))), - ( - " - expect.extend({ - toResolve(obj) { - this.isNot - ? expect(obj).toBe(true) - : expect(obj).resolves.not.toThrow(); - } - }); - ", - None, - ), - ( - " - expect.extend({ - toResolve(obj) { - this.isNot - ? expect(obj).resolves.not.toThrow() - : expect(obj).toBe(true); - } - }); - ", - None, - ), - ("test(\"valid-expect\", () => { expect(Promise.resolve(2)).resolves.toBeDefined(); });", None), - ("test(\"valid-expect\", () => { expect(Promise.resolve(2)).toResolve(); });", None), - ("test(\"valid-expect\", () => { expect(Promise.resolve(2)).toResolve(); });", Some(serde_json::json!([{ "asyncMatchers": "undefined" }]))), - ("test(\"valid-expect\", () => { expect(Promise.resolve(2)).toReject(); });", None), - ("test(\"valid-expect\", () => { expect(Promise.resolve(2)).not.toReject(); });", None), - ("test(\"valid-expect\", () => { expect(Promise.resolve(2)).resolves.not.toBeDefined(); });", None), - ("test(\"valid-expect\", () => { expect(Promise.resolve(2)).rejects.toBeDefined(); });", None), - ("test(\"valid-expect\", () => { expect(Promise.resolve(2)).rejects.not.toBeDefined(); });", None), - ("test(\"valid-expect\", async () => { expect(Promise.resolve(2)).resolves.toBeDefined(); });", None), - ("test(\"valid-expect\", async () => { expect(Promise.resolve(2)).resolves.not.toBeDefined(); });", None), - ("test(\"valid-expect\", () => { expect(Promise.reject(2)).toRejectWith(2); });", Some(serde_json::json!([{ "asyncMatchers": ["toRejectWith"] }]))), - ("test(\"valid-expect\", () => { expect(Promise.reject(2)).rejects.toBe(2); });", Some(serde_json::json!([{ "asyncMatchers": ["toRejectWith"] }]))), - ( - " - test(\"valid-expect\", async () => { - expect(Promise.resolve(2)).resolves.not.toBeDefined(); - expect(Promise.resolve(1)).rejects.toBeDefined(); - }); - ", - None, - ), - ( - " - test(\"valid-expect\", async () => { - await expect(Promise.resolve(2)).resolves.not.toBeDefined(); - expect(Promise.resolve(1)).rejects.toBeDefined(); - }); - ", - None, - ), - ( - " - test(\"valid-expect\", async () => { - expect(Promise.resolve(2)).resolves.not.toBeDefined(); - return expect(Promise.resolve(1)).rejects.toBeDefined(); - }); - ", - Some(serde_json::json!([{ "alwaysAwait": true }])), - ), - (" - test(\"valid-expect\", async () => { - expect(Promise.resolve(2)).resolves.not.toBeDefined(); - return expect(Promise.resolve(1)).rejects.toBeDefined(); - }); - ", - None, - ), - ( - " - test(\"valid-expect\", () => { - Promise.x(expect(Promise.resolve(2)).resolves.not.toBeDefined()); - }); - ", - None, - ), - ( - " - test(\"valid-expect\", () => { - Promise.resolve(expect(Promise.resolve(2)).resolves.not.toBeDefined()); - }); - ", - Some(serde_json::json!([{ "alwaysAwait": true }])), - ), - ( - " - test(\"valid-expect\", () => { - Promise.all([ - expect(Promise.resolve(2)).resolves.not.toBeDefined(), - expect(Promise.resolve(3)).resolves.not.toBeDefined(), - ]); - }); - ", - None, - ), - ( - " - test(\"valid-expect\", () => { - Promise.x([ - expect(Promise.resolve(2)).resolves.not.toBeDefined(), - expect(Promise.resolve(3)).resolves.not.toBeDefined(), - ]); - }); - ", - None, - ), - ( - " - test(\"valid-expect\", () => { - const assertions = [ - expect(Promise.resolve(2)).resolves.not.toBeDefined(), - expect(Promise.resolve(3)).resolves.not.toBeDefined(), - ] - }); - ", - None, - ), - ( - " - test(\"valid-expect\", () => { - const assertions = [ - expect(Promise.resolve(2)).toResolve(), - expect(Promise.resolve(3)).toReject(), - ] - }); - ", - None, - ), - ( - " - test(\"valid-expect\", () => { - const assertions = [ - expect(Promise.resolve(2)).not.toResolve(), - expect(Promise.resolve(3)).resolves.toReject(), - ] - }); - ", - None, - ), - ("expect(Promise.resolve(2)).resolves.toBe;", None), - ( - " - test(\"valid-expect\", () => { - return expect(functionReturningAPromise()).resolves.toEqual(1).then(() => { - expect(Promise.resolve(2)).resolves.toBe(1); - }); - }); - ", - None, - ), - ( - " - test(\"valid-expect\", () => { - return expect(functionReturningAPromise()).resolves.toEqual(1).then(async () => { - await expect(Promise.resolve(2)).resolves.toBe(1); - expect(Promise.resolve(4)).resolves.toBe(4); - }); - }); - ", - None, - ), - ( - " - test(\"valid-expect\", async () => { - await expect(Promise.resolve(1)); - }); - ", - None, - ), - ]; - - pass.extend(pass_vitest); - fail.extend(fail_vitest); - Tester::new(ValidExpect::NAME, ValidExpect::PLUGIN, pass, fail) .with_jest_plugin(true) - .with_vitest_plugin(true) .test_and_snapshot(); } diff --git a/crates/oxc_linter/src/rules/vitest/valid_expect.rs b/crates/oxc_linter/src/rules/vitest/valid_expect.rs new file mode 100644 index 0000000000000..5deb0c8cd44ce --- /dev/null +++ b/crates/oxc_linter/src/rules/vitest/valid_expect.rs @@ -0,0 +1,787 @@ +use std::borrow::Cow; + +use oxc_ast::{AstKind, ast::Expression}; +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_span::{GetSpan, Span}; +use schemars::JsonSchema; + +use crate::{ + AstNode, + context::LintContext, + rule::Rule, + utils::{ExpectError, PossibleJestNode, parse_expect_jest_fn_call}, +}; + +fn valid_expect_diagnostic>>( + x1: S, + x2: &'static str, + span3: Span, +) -> OxcDiagnostic { + OxcDiagnostic::warn(x1).with_help(x2).with_label(span3) +} + +#[derive(Debug, Default, Clone)] +pub struct ValidExpect(Box); + +#[derive(Debug, Clone, JsonSchema)] +#[serde(rename_all = "camelCase", default)] +pub struct ValidExpectConfig { + /// List of matchers that are considered async and therefore require awaiting (e.g. `toResolve`, `toReject`). + async_matchers: Vec, + /// Minimum number of arguments `expect` should be called with. + min_args: usize, + /// Maximum number of arguments `expect` should be called with. + /// + /// Note: In Vitest, a second argument is always allowed when it is a string or template + /// literal (for custom failure messages), regardless of the configured `max_args` value. + max_args: usize, + /// When `true`, async assertions must be awaited in all contexts (not just return statements). + always_await: bool, +} + +impl std::ops::Deref for ValidExpect { + type Target = ValidExpectConfig; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Default for ValidExpectConfig { + fn default() -> Self { + Self { + async_matchers: vec![String::from("toResolve"), String::from("toReject")], + min_args: 1, + max_args: 1, + always_await: false, + } + } +} + +declare_oxc_lint!( + /// ### What it does + /// + /// Checks that `expect()` is called correctly. + /// + /// ### Why is this bad? + /// + /// `expect()` is a function that is used to assert values in tests. + /// It should be called with a single argument, which is the value to be tested. + /// If you call `expect()` with no arguments, or with more than one argument, it will not work as expected. + /// + /// ### Examples + /// + /// Examples of **incorrect** code for this rule: + /// ```javascript + /// expect(); + /// expect('something'); + /// expect(true).toBeDefined; + /// ``` + /// + /// Examples of **correct** code for this rule: + /// ```javascript + /// expect('value', 'custom message').toEqual('value'); + /// expect('something').toEqual('something'); + /// expect(true).toBeDefined(); + /// expect(Promise.resolve('Hi!')).resolves.toBe('Hi!'); + /// ``` + /// + /// This rule is compatible with [eslint-plugin-vitest](https://github.com/vitest-dev/eslint-plugin-vitest/blob/v1.1.9/docs/rules/valid-expect.md), + /// to use it, add the following configuration to your `.oxlintrc.json`: + /// + /// ```json + /// { + /// "rules": { + /// "vitest/valid-expect": "error" + /// } + /// } + /// ``` + ValidExpect, + vitest, + correctness, + config = ValidExpectConfig, +); + +impl Rule for ValidExpect { + fn from_configuration(value: serde_json::Value) -> Result { + let default_async_matchers = vec![String::from("toResolve"), String::from("toReject")]; + let config = value.get(0); + + let async_matchers = config + .and_then(|config| config.get("asyncMatchers")) + .and_then(serde_json::Value::as_array) + .map_or(default_async_matchers, |v| { + v.iter().filter_map(serde_json::Value::as_str).map(String::from).collect() + }); + let min_args = config + .and_then(|config| config.get("minArgs")) + .and_then(serde_json::Value::as_number) + .and_then(serde_json::Number::as_u64) + .map_or(1, |v| usize::try_from(v).unwrap_or(1)); + + let max_args = config + .and_then(|config| config.get("maxArgs")) + .and_then(serde_json::Value::as_number) + .and_then(serde_json::Number::as_u64) + .map_or(1, |v| usize::try_from(v).unwrap_or(1)); + + let always_await = config + .and_then(|config| config.get("alwaysAwait")) + .and_then(serde_json::Value::as_bool) + .unwrap_or(false); + + Ok(Self(Box::new(ValidExpectConfig { async_matchers, min_args, max_args, always_await }))) + } + + fn run_on_jest_node<'a, 'b>( + &self, + jest_node: &PossibleJestNode<'a, 'b>, + ctx: &'b LintContext<'a>, + ) { + self.run(jest_node, ctx); + } +} + +impl ValidExpect { + fn run<'a>(&self, possible_jest_node: &PossibleJestNode<'a, '_>, ctx: &LintContext<'a>) { + let node = possible_jest_node.node; + let AstKind::CallExpression(call_expr) = node.kind() else { + return; + }; + let Some(jest_fn_call) = parse_expect_jest_fn_call(call_expr, possible_jest_node, ctx) + else { + return; + }; + let reporting_span = jest_fn_call.expect_error.map_or(call_expr.span, |_| { + find_top_most_member_expression(node, ctx) + .map_or(call_expr.span, |top_most_member_expr| top_most_member_expr.span()) + }); + + match jest_fn_call.expect_error { + Some(ExpectError::MatcherNotFound) => { + let (error, help) = Message::MatcherNotFound.details(); + ctx.diagnostic(valid_expect_diagnostic(error, help, reporting_span)); + return; + } + Some(ExpectError::MatcherNotCalled) => { + let (error, help) = Message::MatcherNotCalled.details(); + ctx.diagnostic(valid_expect_diagnostic(error, help, reporting_span)); + return; + } + Some(ExpectError::ModifierUnknown) => { + let (error, help) = Message::ModifierUnknown.details(); + ctx.diagnostic(valid_expect_diagnostic(error, help, reporting_span)); + return; + } + None => {} + } + + let Some(Expression::CallExpression(call_expr)) = jest_fn_call.head.parent else { + return; + }; + + let allow_message_arg = call_expr.arguments.len() == 2 + && call_expr + .arguments + .get(1) + .and_then(|arg| arg.as_expression()) + .is_some_and(|expr| { + matches!(expr, Expression::StringLiteral(_) | Expression::TemplateLiteral(_)) + }); + + if call_expr.arguments.len() > self.max_args && !allow_message_arg { + let error = format!( + "Expect takes at most {} argument{} ", + self.max_args, + if self.max_args > 1 { "s" } else { "" } + ); + let help = "Remove the extra arguments."; + ctx.diagnostic(valid_expect_diagnostic(error, help, call_expr.span)); + return; + } + if call_expr.arguments.len() < self.min_args { + let error = format!( + "Expect requires at least {} argument{} ", + self.min_args, + if self.min_args > 1 { "s" } else { "" } + ); + let help = "Add the missing arguments."; + ctx.diagnostic(valid_expect_diagnostic(error, help, call_expr.span)); + return; + } + + let Some(matcher) = jest_fn_call.matcher() else { + return; + }; + let Some(matcher_name) = matcher.name() else { + return; + }; + + let parent = ctx.nodes().parent_node(node.id()); + + let should_be_awaited = + jest_fn_call.modifiers().iter().any(|modifier| modifier.is_name_unequal("not")) + || self.async_matchers.contains(&matcher_name.to_string()); + + if matches!(parent.kind(), AstKind::Program(_)) || !should_be_awaited { + return; + } + + // An async assertion can be chained with `then` or `catch` statements. + // In that case our target CallExpression node is the one with + // the last `then` or `catch` statement. + let target_node = get_parent_if_thenable(node, ctx); + let Some(final_node) = find_promise_call_expression_node(node, ctx, target_node) else { + return; + }; + let parent = ctx.nodes().parent_node(final_node.id()); + if !is_acceptable_return_node(parent, !self.always_await, ctx) { + let span; + let (error, help) = if target_node.id() == final_node.id() { + let AstKind::CallExpression(call_expr) = target_node.kind() else { + return; + }; + span = call_expr.span; + Message::AsyncMustBeAwaited.details() + } else { + let AstKind::CallExpression(call_expr) = final_node.kind() else { + return; + }; + span = call_expr.span; + Message::PromisesWithAsyncAssertionsMustBeAwaited.details() + }; + ctx.diagnostic(valid_expect_diagnostic(error, help, span)); + } + } +} + +fn find_top_most_member_expression<'a, 'b>( + node: &'b AstNode<'a>, + ctx: &'b LintContext<'a>, +) -> Option> { + let mut top_most_member_expression = None; + let mut node = node; + + loop { + let parent = ctx.nodes().parent_node(node.id()); + match node.kind() { + member_expr if member_expr.is_member_expression_kind() => { + top_most_member_expression = Some(member_expr); + } + _ => { + if !parent.kind().is_member_expression_kind() { + break; + } + } + } + node = parent; + } + + top_most_member_expression +} + +fn is_acceptable_return_node<'a, 'b>( + node: &'b AstNode<'a>, + allow_return: bool, + ctx: &'b LintContext<'a>, +) -> bool { + let mut node = node; + loop { + if allow_return && matches!(node.kind(), AstKind::ReturnStatement(_)) { + return true; + } + + match node.kind() { + AstKind::ConditionalExpression(_) + | AstKind::ExpressionStatement(_) + | AstKind::FunctionBody(_) => { + node = ctx.nodes().parent_node(node.id()); + } + AstKind::ArrowFunctionExpression(arrow_expr) => return arrow_expr.expression, + AstKind::AwaitExpression(_) => return true, + _ => return false, + } + } +} + +type ParentAndIsFirstItem<'a, 'b> = (&'b AstNode<'a>, bool); + +/// Checks if a node should be skipped during parent traversal +fn should_skip_parent_node(node: &AstNode, parent: &AstNode) -> bool { + match parent.kind() { + AstKind::CallExpression(call) => { + // Don't skip arguments to Promise methods - they're semantically important for await detection + if let Some(member_expr) = call.callee.as_member_expression() + && let Expression::Identifier(ident) = member_expr.object() + && ident.name == "Promise" + { + return false; // Never skip Promise method arguments + } + + // For other call expressions, skip if this node is one of the arguments + call.arguments.iter().any(|arg| arg.span() == node.span()) + } + AstKind::NewExpression(new_expr) => { + // Skip if this node is one of the new expression arguments + new_expr.arguments.iter().any(|arg| arg.span() == node.span()) + } + _ => false, + } +} + +// Returns the parent node of the given node, ignoring some nodes, +// and return whether the first item if parent is an array. +fn get_parent_with_ignore<'a, 'b>( + node: &'b AstNode<'a>, + ctx: &'b LintContext<'a>, +) -> Option> { + let mut node = node; + loop { + let parent = ctx.nodes().parent_node(node.id()); + if !should_skip_parent_node(node, parent) { + // we don't want to report `Promise.all([invalidExpectCall_1, invalidExpectCall_2])` twice. + // so we need mark whether the node is the first item of an array. + // if it not the first item, we ignore it in `find_promise_call_expression_node`. + let is_first_item = if let AstKind::ArrayExpression(array_expr) = parent.kind() { + array_expr.elements.first()?.span() == node.span() + } else { + // if parent is not an array, we assume it's the first item + true + }; + + return Some((parent, is_first_item)); + } + + node = parent; + } +} + +fn find_promise_call_expression_node<'a, 'b>( + node: &'b AstNode<'a>, + ctx: &'b LintContext<'a>, + default_node: &'b AstNode<'a>, +) -> Option<&'b AstNode<'a>> { + let Some((mut parent, is_first_array_item)) = get_parent_with_ignore(node, ctx) else { + return Some(default_node); + }; + if !matches!(parent.kind(), AstKind::CallExpression(_) | AstKind::ArrayExpression(_)) { + return Some(default_node); + } + let Some((grandparent, _)) = get_parent_with_ignore(parent, ctx) else { + return Some(default_node); + }; + if matches!(parent.kind(), AstKind::ArrayExpression(_)) + && matches!(grandparent.kind(), AstKind::CallExpression(_)) + { + parent = grandparent; + } + + if let AstKind::CallExpression(call_expr) = parent.kind() + && let Some(member_expr) = call_expr.callee.as_member_expression() + && let Expression::Identifier(ident) = member_expr.object() + && matches!(ident.name.as_str(), "Promise") + && !matches!(parent.kind(), AstKind::Program(_)) + { + if is_first_array_item { + return Some(parent); + } + return None; + } + + Some(default_node) +} + +fn get_parent_if_thenable<'a, 'b>( + node: &'b AstNode<'a>, + ctx: &'b LintContext<'a>, +) -> &'b AstNode<'a> { + let grandparent = ctx.nodes().parent_node(ctx.nodes().parent_id(node.id())); + let AstKind::CallExpression(call_expr) = grandparent.kind() else { + return node; + }; + let Some(member_expr) = call_expr.callee.as_member_expression() else { + return node; + }; + let Some(name) = member_expr.static_property_name() else { + return node; + }; + + if ["then", "catch"].contains(&name) { + return get_parent_if_thenable(grandparent, ctx); + } + + node +} + +#[derive(Clone, Copy)] +enum Message { + MatcherNotFound, + MatcherNotCalled, + ModifierUnknown, + AsyncMustBeAwaited, + PromisesWithAsyncAssertionsMustBeAwaited, +} + +impl Message { + fn details(self) -> (&'static str, &'static str) { + match self { + Self::MatcherNotFound => ( + "Expect must have a corresponding matcher call.", + "Did you forget add a matcher, e.g. `toBe`, `toBeDefined`", + ), + Self::MatcherNotCalled => ( + "Matchers must be called to assert.", + "You need call your matcher, e.g. `expect(true).toBe(true)`.", + ), + Self::ModifierUnknown => { + ("Expect has an unknown modifier.", "Is it a spelling mistake?") + } + Self::AsyncMustBeAwaited => { + ("Async assertions must be awaited.", "Add `await` to your assertion.") + } + Self::PromisesWithAsyncAssertionsMustBeAwaited => ( + "Promises which return async assertions must be awaited.", + "Add `await` to your assertion.", + ), + } + } +} + + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + ("expect.hasAssertions", None), + ("expect.hasAssertions()", None), + ("expect(\"something\").toEqual(\"else\");", None), + ("expect(\"something\", \"else\").toEqual(\"something\");", None), + ("expect(true).toBeDefined();", None), + ("expect([1, 2, 3]).toEqual([1, 2, 3]);", None), + ("expect(undefined).not.toBeDefined();", None), + ("test(\"valid-expect\", () => { return expect(Promise.resolve(2)).resolves.toBeDefined(); });", None), + ("test(\"valid-expect\", () => { return expect(Promise.reject(2)).rejects.toBeDefined(); });", None), + ("test(\"valid-expect\", () => { return expect(Promise.resolve(2)).resolves.not.toBeDefined(); });", None), + ("test(\"valid-expect\", () => { return expect(Promise.resolve(2)).rejects.not.toBeDefined(); });", None), + ("test(\"valid-expect\", function () { return expect(Promise.resolve(2)).resolves.not.toBeDefined(); });", None), + ("test(\"valid-expect\", function () { return expect(Promise.resolve(2)).rejects.not.toBeDefined(); });", None), + ("test(\"valid-expect\", function () { return Promise.resolve(expect(Promise.resolve(2)).resolves.not.toBeDefined()); });", None), + ("test(\"valid-expect\", function () { return Promise.resolve(expect(Promise.resolve(2)).rejects.not.toBeDefined()); });", None), + ("test(\"valid-expect\", () => expect(Promise.resolve(2)).resolves.toBeDefined());", None), + ("test(\"valid-expect\", () => expect(Promise.reject(2)).rejects.toBeDefined());", None), + ("test(\"valid-expect\", () => expect(Promise.reject(2)).resolves.not.toBeDefined());", None), + ("test(\"valid-expect\", () => expect(Promise.reject(2)).rejects.not.toBeDefined());", None), + ("test(\"valid-expect\", async () => { await expect(Promise.reject(2)).resolves.not.toBeDefined(); });", None), + ("test(\"valid-expect\", async () => { await expect(Promise.reject(2)).rejects.not.toBeDefined(); });", None), + ("test(\"valid-expect\", async function () { await expect(Promise.reject(2)).resolves.not.toBeDefined(); });", None), + ("test(\"valid-expect\", async function () { await expect(Promise.reject(2)).rejects.not.toBeDefined(); });", None), + ("test(\"valid-expect\", async () => { await Promise.resolve(expect(Promise.reject(2)).rejects.not.toBeDefined()); });", None), + ("test(\"valid-expect\", async () => { await Promise.reject(expect(Promise.reject(2)).rejects.not.toBeDefined()); });", None), + ("test(\"valid-expect\", async () => { await Promise.all([expect(Promise.reject(2)).rejects.not.toBeDefined(), expect(Promise.reject(2)).rejects.not.toBeDefined()]); });", None), + ("test(\"valid-expect\", async () => { await Promise.race([expect(Promise.reject(2)).rejects.not.toBeDefined(), expect(Promise.reject(2)).rejects.not.toBeDefined()]); });", None), + ("test(\"valid-expect\", async () => { await Promise.allSettled([expect(Promise.reject(2)).rejects.not.toBeDefined(), expect(Promise.reject(2)).rejects.not.toBeDefined()]); });", None), + ("test(\"valid-expect\", async () => { await Promise.any([expect(Promise.reject(2)).rejects.not.toBeDefined(), expect(Promise.reject(2)).rejects.not.toBeDefined()]); });", None), + ("test(\"valid-expect\", async () => { return expect(Promise.reject(2)).resolves.not.toBeDefined().then(() => console.log(\"valid-case\")); });", None), + ("test(\"valid-expect\", async () => { return expect(Promise.reject(2)).resolves.not.toBeDefined().then(() => console.log(\"valid-case\")).then(() => console.log(\"another valid case\")); });", None), + ("test(\"valid-expect\", async () => { return expect(Promise.reject(2)).resolves.not.toBeDefined().catch(() => console.log(\"valid-case\")); });", None), + ("test(\"valid-expect\", async () => { return expect(Promise.reject(2)).resolves.not.toBeDefined().then(() => console.log(\"valid-case\")).catch(() => console.log(\"another valid case\")); });", None), + ("test(\"valid-expect\", async () => { return expect(Promise.reject(2)).resolves.not.toBeDefined().then(() => { expect(someMock).toHaveBeenCalledTimes(1); }); });", None), + ("test(\"valid-expect\", async () => { await expect(Promise.reject(2)).resolves.not.toBeDefined().then(() => console.log(\"valid-case\")); });", None), + ("test(\"valid-expect\", async () => { await expect(Promise.reject(2)).resolves.not.toBeDefined().then(() => console.log(\"valid-case\")).then(() => console.log(\"another valid case\")); });", None), + ("test(\"valid-expect\", async () => { await expect(Promise.reject(2)).resolves.not.toBeDefined().catch(() => console.log(\"valid-case\")); });", None), + ("test(\"valid-expect\", async () => { await expect(Promise.reject(2)).resolves.not.toBeDefined().then(() => console.log(\"valid-case\")).catch(() => console.log(\"another valid case\")); });", None), + ("test(\"valid-expect\", async () => { await expect(Promise.reject(2)).resolves.not.toBeDefined().then(() => { expect(someMock).toHaveBeenCalledTimes(1); }); });", None), + ( + " + test(\"valid-expect\", () => { + return expect(functionReturningAPromise()).resolves.toEqual(1).then(() => { + return expect(Promise.resolve(2)).resolves.toBe(1); + }); + }); + ", + None, + ), + ( + " + test(\"valid-expect\", () => { + return expect(functionReturningAPromise()).resolves.toEqual(1).then(async () => { + await expect(Promise.resolve(2)).resolves.toBe(1); + }); + }); + ", + None, + ), + ( + " + test(\"valid-expect\", () => { + return expect(functionReturningAPromise()).resolves.toEqual(1).then(() => expect(Promise.resolve(2)).resolves.toBe(1)); + }); + ", + None, + ), + ( + " + expect.extend({ + toResolve(obj) { + return this.isNot + ? expect(obj).toBe(true) + : expect(obj).resolves.not.toThrow(); + } + }); + ", + None, + ), + ( + " + expect.extend({ + toResolve(obj) { + return this.isNot + ? expect(obj).resolves.not.toThrow() + : expect(obj).toBe(true); + } + }); + ", + None, + ), + ( + " + expect.extend({ + toResolve(obj) { + return this.isNot + ? expect(obj).toBe(true) + : anotherCondition + ? expect(obj).resolves.not.toThrow() + : expect(obj).toBe(false) + } + }); + ", + None, + ), + ("expect(1).toBe(2);", Some(serde_json::json!([{ "maxArgs": 2 }]))), + ("expect(1, \"1 !== 2\").toBe(2);", Some(serde_json::json!([{ "maxArgs": 2 }]))), + ("expect(1, \"sum is incorrect\").toBe(1);", None), + ("test(\"valid-expect\", () => { expect(1 + 2, \"sum is incorrect\").toBe(3); });", None), + ("test(\"valid-expect\", () => { expect(1 + 2, `sum is ${label}`).toBe(3); });", None), + ( + "test(\"valid-expect\", () => { expect(2).not.toBe(2); });", + Some(serde_json::json!([{ "asyncMatchers": ["toRejectWith"] }])), + ), + ( + "test(\"valid-expect\", () => { expect(Promise.reject(2)).toRejectWith(2); });", + Some(serde_json::json!([{ "asyncMatchers": ["toResolveWith"] }])), + ), + ( + "test(\"valid-expect\", async () => { await expect(Promise.resolve(2)).toResolve(); });", + Some(serde_json::json!([{ "asyncMatchers": ["toResolveWith"] }])), + ), + ( + "test(\"valid-expect\", async () => { expect(Promise.resolve(2)).toResolve(); });", + Some(serde_json::json!([{ "asyncMatchers": ["toResolveWith"] }])), + ), + ]; + + let fail = vec![ + ("expect().toBe(2);", Some(serde_json::json!([{ "minArgs": "undefined", "maxArgs": "undefined" }]))), + ("expect().toBe(true);", None), + ("expect().toEqual(\"something\");", None), + ("expect(\"something\", \"else\", \"entirely\").toEqual(\"something\");", Some(serde_json::json!([{ "maxArgs": 2 }]))), + ("expect(\"something\", \"else\", \"entirely\").toEqual(\"something\");", Some(serde_json::json!([{ "maxArgs": 2, "minArgs": 2 }]))), + ("expect(\"something\", \"else\", \"entirely\").toEqual(\"something\");", Some(serde_json::json!([{ "maxArgs": 2, "minArgs": 1 }]))), + ("expect(\"something\").toEqual(\"something\");", Some(serde_json::json!([{ "minArgs": 2 }]))), + ("expect(\"something\", \"else\").toEqual(\"something\");", Some(serde_json::json!([{ "maxArgs": 1, "minArgs": 3 }]))), + ("expect(1, message).toBe(1);", None), + ("expect(\"something\");", None), + ("expect();", None), + ("expect(true).toBeDefined;", None), + ("expect(true).not.toBeDefined;", None), + ("expect(true).nope.toBeDefined;", None), + ("expect(true).nope.toBeDefined();", None), + ("expect(true).not.resolves.toBeDefined();", None), + ("expect(true).not.not.toBeDefined();", None), + ("expect(true).resolves.not.exactly.toBeDefined();", None), + ("expect(true).resolves;", None), + ("expect(true).rejects;", None), + ("expect(true).not;", None), + ("expect(Promise.resolve(2)).resolves.toBeDefined();", None), + ("expect(Promise.resolve(2)).rejects.toBeDefined();", None), + ("expect(Promise.resolve(2)).resolves.toBeDefined();", Some(serde_json::json!([{ "alwaysAwait": true }]))), + ( + " + expect.extend({ + toResolve(obj) { + this.isNot + ? expect(obj).toBe(true) + : expect(obj).resolves.not.toThrow(); + } + }); + ", + None, + ), + ( + " + expect.extend({ + toResolve(obj) { + this.isNot + ? expect(obj).resolves.not.toThrow() + : expect(obj).toBe(true); + } + }); + ", + None, + ), + ("test(\"valid-expect\", () => { expect(Promise.resolve(2)).resolves.toBeDefined(); });", None), + ("test(\"valid-expect\", () => { expect(Promise.resolve(2)).toResolve(); });", None), + ("test(\"valid-expect\", () => { expect(Promise.resolve(2)).toResolve(); });", Some(serde_json::json!([{ "asyncMatchers": "undefined" }]))), + ("test(\"valid-expect\", () => { expect(Promise.resolve(2)).toReject(); });", None), + ("test(\"valid-expect\", () => { expect(Promise.resolve(2)).not.toReject(); });", None), + ("test(\"valid-expect\", () => { expect(Promise.resolve(2)).resolves.not.toBeDefined(); });", None), + ("test(\"valid-expect\", () => { expect(Promise.resolve(2)).rejects.toBeDefined(); });", None), + ("test(\"valid-expect\", () => { expect(Promise.resolve(2)).rejects.not.toBeDefined(); });", None), + ("test(\"valid-expect\", async () => { expect(Promise.resolve(2)).resolves.toBeDefined(); });", None), + ("test(\"valid-expect\", async () => { expect(Promise.resolve(2)).resolves.not.toBeDefined(); });", None), + ("test(\"valid-expect\", () => { expect(Promise.reject(2)).toRejectWith(2); });", Some(serde_json::json!([{ "asyncMatchers": ["toRejectWith"] }]))), + ("test(\"valid-expect\", () => { expect(Promise.reject(2)).rejects.toBe(2); });", Some(serde_json::json!([{ "asyncMatchers": ["toRejectWith"] }]))), + ( + " + test(\"valid-expect\", async () => { + expect(Promise.resolve(2)).resolves.not.toBeDefined(); + expect(Promise.resolve(1)).rejects.toBeDefined(); + }); + ", + None, + ), + ( + " + test(\"valid-expect\", async () => { + await expect(Promise.resolve(2)).resolves.not.toBeDefined(); + expect(Promise.resolve(1)).rejects.toBeDefined(); + }); + ", + None, + ), + ( + " + test(\"valid-expect\", async () => { + expect(Promise.resolve(2)).resolves.not.toBeDefined(); + return expect(Promise.resolve(1)).rejects.toBeDefined(); + }); + ", + Some(serde_json::json!([{ "alwaysAwait": true }])), + ), + (" + test(\"valid-expect\", async () => { + expect(Promise.resolve(2)).resolves.not.toBeDefined(); + return expect(Promise.resolve(1)).rejects.toBeDefined(); + }); + ", + None, + ), + ( + " + test(\"valid-expect\", () => { + Promise.x(expect(Promise.resolve(2)).resolves.not.toBeDefined()); + }); + ", + None, + ), + ( + " + test(\"valid-expect\", () => { + Promise.resolve(expect(Promise.resolve(2)).resolves.not.toBeDefined()); + }); + ", + Some(serde_json::json!([{ "alwaysAwait": true }])), + ), + ( + " + test(\"valid-expect\", () => { + Promise.all([ + expect(Promise.resolve(2)).resolves.not.toBeDefined(), + expect(Promise.resolve(3)).resolves.not.toBeDefined(), + ]); + }); + ", + None, + ), + ( + " + test(\"valid-expect\", () => { + Promise.x([ + expect(Promise.resolve(2)).resolves.not.toBeDefined(), + expect(Promise.resolve(3)).resolves.not.toBeDefined(), + ]); + }); + ", + None, + ), + ( + " + test(\"valid-expect\", () => { + const assertions = [ + expect(Promise.resolve(2)).resolves.not.toBeDefined(), + expect(Promise.resolve(3)).resolves.not.toBeDefined(), + ] + }); + ", + None, + ), + ( + " + test(\"valid-expect\", () => { + const assertions = [ + expect(Promise.resolve(2)).toResolve(), + expect(Promise.resolve(3)).toReject(), + ] + }); + ", + None, + ), + ( + " + test(\"valid-expect\", () => { + const assertions = [ + expect(Promise.resolve(2)).not.toResolve(), + expect(Promise.resolve(3)).resolves.toReject(), + ] + }); + ", + None, + ), + ("expect(Promise.resolve(2)).resolves.toBe;", None), + ( + " + test(\"valid-expect\", () => { + return expect(functionReturningAPromise()).resolves.toEqual(1).then(() => { + expect(Promise.resolve(2)).resolves.toBe(1); + }); + }); + ", + None, + ), + ( + " + test(\"valid-expect\", () => { + return expect(functionReturningAPromise()).resolves.toEqual(1).then(async () => { + await expect(Promise.resolve(2)).resolves.toBe(1); + expect(Promise.resolve(4)).resolves.toBe(4); + }); + }); + ", + None, + ), + ( + " + test(\"valid-expect\", async () => { + await expect(Promise.resolve(1)); + }); + ", + None, + ), + ]; + + Tester::new(ValidExpect::NAME, ValidExpect::PLUGIN, pass, fail) + .with_vitest_plugin(true) + .test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/jest_valid_expect.snap b/crates/oxc_linter/src/snapshots/jest_valid_expect.snap index 0721768df26d0..ada3a62210474 100644 --- a/crates/oxc_linter/src/snapshots/jest_valid_expect.snap +++ b/crates/oxc_linter/src/snapshots/jest_valid_expect.snap @@ -1,5 +1,6 @@ --- source: crates/oxc_linter/src/tester.rs +assertion_line: 444 --- ⚠ eslint-plugin-jest(valid-expect): Expect requires at least 1 argument @@ -498,455 +499,3 @@ source: crates/oxc_linter/src/tester.rs 4 │ }); ╰──── help: Did you forget add a matcher, e.g. `toBe`, `toBeDefined` - - ⚠ eslint-plugin-jest(valid-expect): Expect requires at least 1 argument - ╭─[valid_expect.tsx:1:1] - 1 │ expect().toBe(2); - · ──────── - ╰──── - help: Add the missing arguments. - - ⚠ eslint-plugin-jest(valid-expect): Expect requires at least 1 argument - ╭─[valid_expect.tsx:1:1] - 1 │ expect().toBe(true); - · ──────── - ╰──── - help: Add the missing arguments. - - ⚠ eslint-plugin-jest(valid-expect): Expect requires at least 1 argument - ╭─[valid_expect.tsx:1:1] - 1 │ expect().toEqual("something"); - · ──────── - ╰──── - help: Add the missing arguments. - - ⚠ eslint-plugin-jest(valid-expect): Expect takes at most 1 argument - ╭─[valid_expect.tsx:1:1] - 1 │ expect("something", "else").toEqual("something"); - · ─────────────────────────── - ╰──── - help: Remove the extra arguments. - - ⚠ eslint-plugin-jest(valid-expect): Expect takes at most 2 arguments - ╭─[valid_expect.tsx:1:1] - 1 │ expect("something", "else", "entirely").toEqual("something"); - · ─────────────────────────────────────── - ╰──── - help: Remove the extra arguments. - - ⚠ eslint-plugin-jest(valid-expect): Expect takes at most 2 arguments - ╭─[valid_expect.tsx:1:1] - 1 │ expect("something", "else", "entirely").toEqual("something"); - · ─────────────────────────────────────── - ╰──── - help: Remove the extra arguments. - - ⚠ eslint-plugin-jest(valid-expect): Expect takes at most 2 arguments - ╭─[valid_expect.tsx:1:1] - 1 │ expect("something", "else", "entirely").toEqual("something"); - · ─────────────────────────────────────── - ╰──── - help: Remove the extra arguments. - - ⚠ eslint-plugin-jest(valid-expect): Expect requires at least 2 arguments - ╭─[valid_expect.tsx:1:1] - 1 │ expect("something").toEqual("something"); - · ─────────────────── - ╰──── - help: Add the missing arguments. - - ⚠ eslint-plugin-jest(valid-expect): Expect takes at most 1 argument - ╭─[valid_expect.tsx:1:1] - 1 │ expect("something", "else").toEqual("something"); - · ─────────────────────────── - ╰──── - help: Remove the extra arguments. - - ⚠ eslint-plugin-jest(valid-expect): Expect must have a corresponding matcher call. - ╭─[valid_expect.tsx:1:1] - 1 │ expect("something"); - · ─────────────────── - ╰──── - help: Did you forget add a matcher, e.g. `toBe`, `toBeDefined` - - ⚠ eslint-plugin-jest(valid-expect): Expect must have a corresponding matcher call. - ╭─[valid_expect.tsx:1:1] - 1 │ expect(); - · ──────── - ╰──── - help: Did you forget add a matcher, e.g. `toBe`, `toBeDefined` - - ⚠ eslint-plugin-jest(valid-expect): Matchers must be called to assert. - ╭─[valid_expect.tsx:1:1] - 1 │ expect(true).toBeDefined; - · ──────────────────────── - ╰──── - help: You need call your matcher, e.g. `expect(true).toBe(true)`. - - ⚠ eslint-plugin-jest(valid-expect): Matchers must be called to assert. - ╭─[valid_expect.tsx:1:1] - 1 │ expect(true).not.toBeDefined; - · ──────────────────────────── - ╰──── - help: You need call your matcher, e.g. `expect(true).toBe(true)`. - - ⚠ eslint-plugin-jest(valid-expect): Matchers must be called to assert. - ╭─[valid_expect.tsx:1:1] - 1 │ expect(true).nope.toBeDefined; - · ───────────────────────────── - ╰──── - help: You need call your matcher, e.g. `expect(true).toBe(true)`. - - ⚠ eslint-plugin-jest(valid-expect): Expect has an unknown modifier. - ╭─[valid_expect.tsx:1:1] - 1 │ expect(true).nope.toBeDefined(); - · ─────────────────────────────── - ╰──── - help: Is it a spelling mistake? - - ⚠ eslint-plugin-jest(valid-expect): Expect has an unknown modifier. - ╭─[valid_expect.tsx:1:1] - 1 │ expect(true).not.resolves.toBeDefined(); - · ─────────────────────────────────────── - ╰──── - help: Is it a spelling mistake? - - ⚠ eslint-plugin-jest(valid-expect): Expect has an unknown modifier. - ╭─[valid_expect.tsx:1:1] - 1 │ expect(true).not.not.toBeDefined(); - · ────────────────────────────────── - ╰──── - help: Is it a spelling mistake? - - ⚠ eslint-plugin-jest(valid-expect): Expect has an unknown modifier. - ╭─[valid_expect.tsx:1:1] - 1 │ expect(true).resolves.not.exactly.toBeDefined(); - · ─────────────────────────────────────────────── - ╰──── - help: Is it a spelling mistake? - - ⚠ eslint-plugin-jest(valid-expect): Matchers must be called to assert. - ╭─[valid_expect.tsx:1:1] - 1 │ expect(true).resolves; - · ───────────────────── - ╰──── - help: You need call your matcher, e.g. `expect(true).toBe(true)`. - - ⚠ eslint-plugin-jest(valid-expect): Matchers must be called to assert. - ╭─[valid_expect.tsx:1:1] - 1 │ expect(true).rejects; - · ──────────────────── - ╰──── - help: You need call your matcher, e.g. `expect(true).toBe(true)`. - - ⚠ eslint-plugin-jest(valid-expect): Matchers must be called to assert. - ╭─[valid_expect.tsx:1:1] - 1 │ expect(true).not; - · ──────────────── - ╰──── - help: You need call your matcher, e.g. `expect(true).toBe(true)`. - - ⚠ eslint-plugin-jest(valid-expect): Async assertions must be awaited. - ╭─[valid_expect.tsx:1:1] - 1 │ expect(Promise.resolve(2)).resolves.toBeDefined(); - · ───────────────────────────────────────────────── - ╰──── - help: Add `await` to your assertion. - - ⚠ eslint-plugin-jest(valid-expect): Async assertions must be awaited. - ╭─[valid_expect.tsx:1:1] - 1 │ expect(Promise.resolve(2)).rejects.toBeDefined(); - · ──────────────────────────────────────────────── - ╰──── - help: Add `await` to your assertion. - - ⚠ eslint-plugin-jest(valid-expect): Async assertions must be awaited. - ╭─[valid_expect.tsx:1:1] - 1 │ expect(Promise.resolve(2)).resolves.toBeDefined(); - · ───────────────────────────────────────────────── - ╰──── - help: Add `await` to your assertion. - - ⚠ eslint-plugin-jest(valid-expect): Async assertions must be awaited. - ╭─[valid_expect.tsx:6:31] - 5 │ ? expect(obj).toBe(true) - 6 │ : expect(obj).resolves.not.toThrow(); - · ────────────────────────────────── - 7 │ } - ╰──── - help: Add `await` to your assertion. - - ⚠ eslint-plugin-jest(valid-expect): Async assertions must be awaited. - ╭─[valid_expect.tsx:5:31] - 4 │ this.isNot - 5 │ ? expect(obj).resolves.not.toThrow() - · ────────────────────────────────── - 6 │ : expect(obj).toBe(true); - ╰──── - help: Add `await` to your assertion. - - ⚠ eslint-plugin-jest(valid-expect): Async assertions must be awaited. - ╭─[valid_expect.tsx:1:30] - 1 │ test("valid-expect", () => { expect(Promise.resolve(2)).resolves.toBeDefined(); }); - · ───────────────────────────────────────────────── - ╰──── - help: Add `await` to your assertion. - - ⚠ eslint-plugin-jest(valid-expect): Async assertions must be awaited. - ╭─[valid_expect.tsx:1:30] - 1 │ test("valid-expect", () => { expect(Promise.resolve(2)).toResolve(); }); - · ────────────────────────────────────── - ╰──── - help: Add `await` to your assertion. - - ⚠ eslint-plugin-jest(valid-expect): Async assertions must be awaited. - ╭─[valid_expect.tsx:1:30] - 1 │ test("valid-expect", () => { expect(Promise.resolve(2)).toResolve(); }); - · ────────────────────────────────────── - ╰──── - help: Add `await` to your assertion. - - ⚠ eslint-plugin-jest(valid-expect): Async assertions must be awaited. - ╭─[valid_expect.tsx:1:30] - 1 │ test("valid-expect", () => { expect(Promise.resolve(2)).toReject(); }); - · ───────────────────────────────────── - ╰──── - help: Add `await` to your assertion. - - ⚠ eslint-plugin-jest(valid-expect): Async assertions must be awaited. - ╭─[valid_expect.tsx:1:30] - 1 │ test("valid-expect", () => { expect(Promise.resolve(2)).not.toReject(); }); - · ───────────────────────────────────────── - ╰──── - help: Add `await` to your assertion. - - ⚠ eslint-plugin-jest(valid-expect): Async assertions must be awaited. - ╭─[valid_expect.tsx:1:30] - 1 │ test("valid-expect", () => { expect(Promise.resolve(2)).resolves.not.toBeDefined(); }); - · ───────────────────────────────────────────────────── - ╰──── - help: Add `await` to your assertion. - - ⚠ eslint-plugin-jest(valid-expect): Async assertions must be awaited. - ╭─[valid_expect.tsx:1:30] - 1 │ test("valid-expect", () => { expect(Promise.resolve(2)).rejects.toBeDefined(); }); - · ──────────────────────────────────────────────── - ╰──── - help: Add `await` to your assertion. - - ⚠ eslint-plugin-jest(valid-expect): Async assertions must be awaited. - ╭─[valid_expect.tsx:1:30] - 1 │ test("valid-expect", () => { expect(Promise.resolve(2)).rejects.not.toBeDefined(); }); - · ──────────────────────────────────────────────────── - ╰──── - help: Add `await` to your assertion. - - ⚠ eslint-plugin-jest(valid-expect): Async assertions must be awaited. - ╭─[valid_expect.tsx:1:36] - 1 │ test("valid-expect", async () => { expect(Promise.resolve(2)).resolves.toBeDefined(); }); - · ───────────────────────────────────────────────── - ╰──── - help: Add `await` to your assertion. - - ⚠ eslint-plugin-jest(valid-expect): Async assertions must be awaited. - ╭─[valid_expect.tsx:1:36] - 1 │ test("valid-expect", async () => { expect(Promise.resolve(2)).resolves.not.toBeDefined(); }); - · ───────────────────────────────────────────────────── - ╰──── - help: Add `await` to your assertion. - - ⚠ eslint-plugin-jest(valid-expect): Async assertions must be awaited. - ╭─[valid_expect.tsx:1:30] - 1 │ test("valid-expect", () => { expect(Promise.reject(2)).toRejectWith(2); }); - · ───────────────────────────────────────── - ╰──── - help: Add `await` to your assertion. - - ⚠ eslint-plugin-jest(valid-expect): Async assertions must be awaited. - ╭─[valid_expect.tsx:1:30] - 1 │ test("valid-expect", () => { expect(Promise.reject(2)).rejects.toBe(2); }); - · ───────────────────────────────────────── - ╰──── - help: Add `await` to your assertion. - - ⚠ eslint-plugin-jest(valid-expect): Async assertions must be awaited. - ╭─[valid_expect.tsx:3:21] - 2 │ test("valid-expect", async () => { - 3 │ expect(Promise.resolve(2)).resolves.not.toBeDefined(); - · ───────────────────────────────────────────────────── - 4 │ expect(Promise.resolve(1)).rejects.toBeDefined(); - ╰──── - help: Add `await` to your assertion. - - ⚠ eslint-plugin-jest(valid-expect): Async assertions must be awaited. - ╭─[valid_expect.tsx:4:21] - 3 │ expect(Promise.resolve(2)).resolves.not.toBeDefined(); - 4 │ expect(Promise.resolve(1)).rejects.toBeDefined(); - · ──────────────────────────────────────────────── - 5 │ }); - ╰──── - help: Add `await` to your assertion. - - ⚠ eslint-plugin-jest(valid-expect): Async assertions must be awaited. - ╭─[valid_expect.tsx:4:21] - 3 │ await expect(Promise.resolve(2)).resolves.not.toBeDefined(); - 4 │ expect(Promise.resolve(1)).rejects.toBeDefined(); - · ──────────────────────────────────────────────── - 5 │ }); - ╰──── - help: Add `await` to your assertion. - - ⚠ eslint-plugin-jest(valid-expect): Async assertions must be awaited. - ╭─[valid_expect.tsx:3:21] - 2 │ test("valid-expect", async () => { - 3 │ expect(Promise.resolve(2)).resolves.not.toBeDefined(); - · ───────────────────────────────────────────────────── - 4 │ return expect(Promise.resolve(1)).rejects.toBeDefined(); - ╰──── - help: Add `await` to your assertion. - - ⚠ eslint-plugin-jest(valid-expect): Async assertions must be awaited. - ╭─[valid_expect.tsx:4:28] - 3 │ expect(Promise.resolve(2)).resolves.not.toBeDefined(); - 4 │ return expect(Promise.resolve(1)).rejects.toBeDefined(); - · ──────────────────────────────────────────────── - 5 │ }); - ╰──── - help: Add `await` to your assertion. - - ⚠ eslint-plugin-jest(valid-expect): Async assertions must be awaited. - ╭─[valid_expect.tsx:3:21] - 2 │ test("valid-expect", async () => { - 3 │ expect(Promise.resolve(2)).resolves.not.toBeDefined(); - · ───────────────────────────────────────────────────── - 4 │ return expect(Promise.resolve(1)).rejects.toBeDefined(); - ╰──── - help: Add `await` to your assertion. - - ⚠ eslint-plugin-jest(valid-expect): Promises which return async assertions must be awaited. - ╭─[valid_expect.tsx:3:21] - 2 │ test("valid-expect", () => { - 3 │ Promise.x(expect(Promise.resolve(2)).resolves.not.toBeDefined()); - · ──────────────────────────────────────────────────────────────── - 4 │ }); - ╰──── - help: Add `await` to your assertion. - - ⚠ eslint-plugin-jest(valid-expect): Promises which return async assertions must be awaited. - ╭─[valid_expect.tsx:3:21] - 2 │ test("valid-expect", () => { - 3 │ Promise.resolve(expect(Promise.resolve(2)).resolves.not.toBeDefined()); - · ────────────────────────────────────────────────────────────────────── - 4 │ }); - ╰──── - help: Add `await` to your assertion. - - ⚠ eslint-plugin-jest(valid-expect): Promises which return async assertions must be awaited. - ╭─[valid_expect.tsx:3:21] - 2 │ test("valid-expect", () => { - 3 │ ╭─▶ Promise.all([ - 4 │ │ expect(Promise.resolve(2)).resolves.not.toBeDefined(), - 5 │ │ expect(Promise.resolve(3)).resolves.not.toBeDefined(), - 6 │ ╰─▶ ]); - 7 │ }); - ╰──── - help: Add `await` to your assertion. - - ⚠ eslint-plugin-jest(valid-expect): Promises which return async assertions must be awaited. - ╭─[valid_expect.tsx:3:21] - 2 │ test("valid-expect", () => { - 3 │ ╭─▶ Promise.x([ - 4 │ │ expect(Promise.resolve(2)).resolves.not.toBeDefined(), - 5 │ │ expect(Promise.resolve(3)).resolves.not.toBeDefined(), - 6 │ ╰─▶ ]); - 7 │ }); - ╰──── - help: Add `await` to your assertion. - - ⚠ eslint-plugin-jest(valid-expect): Async assertions must be awaited. - ╭─[valid_expect.tsx:4:25] - 3 │ const assertions = [ - 4 │ expect(Promise.resolve(2)).resolves.not.toBeDefined(), - · ───────────────────────────────────────────────────── - 5 │ expect(Promise.resolve(3)).resolves.not.toBeDefined(), - ╰──── - help: Add `await` to your assertion. - - ⚠ eslint-plugin-jest(valid-expect): Async assertions must be awaited. - ╭─[valid_expect.tsx:5:25] - 4 │ expect(Promise.resolve(2)).resolves.not.toBeDefined(), - 5 │ expect(Promise.resolve(3)).resolves.not.toBeDefined(), - · ───────────────────────────────────────────────────── - 6 │ ] - ╰──── - help: Add `await` to your assertion. - - ⚠ eslint-plugin-jest(valid-expect): Async assertions must be awaited. - ╭─[valid_expect.tsx:4:25] - 3 │ const assertions = [ - 4 │ expect(Promise.resolve(2)).toResolve(), - · ────────────────────────────────────── - 5 │ expect(Promise.resolve(3)).toReject(), - ╰──── - help: Add `await` to your assertion. - - ⚠ eslint-plugin-jest(valid-expect): Async assertions must be awaited. - ╭─[valid_expect.tsx:5:25] - 4 │ expect(Promise.resolve(2)).toResolve(), - 5 │ expect(Promise.resolve(3)).toReject(), - · ───────────────────────────────────── - 6 │ ] - ╰──── - help: Add `await` to your assertion. - - ⚠ eslint-plugin-jest(valid-expect): Async assertions must be awaited. - ╭─[valid_expect.tsx:4:25] - 3 │ const assertions = [ - 4 │ expect(Promise.resolve(2)).not.toResolve(), - · ────────────────────────────────────────── - 5 │ expect(Promise.resolve(3)).resolves.toReject(), - ╰──── - help: Add `await` to your assertion. - - ⚠ eslint-plugin-jest(valid-expect): Async assertions must be awaited. - ╭─[valid_expect.tsx:5:25] - 4 │ expect(Promise.resolve(2)).not.toResolve(), - 5 │ expect(Promise.resolve(3)).resolves.toReject(), - · ────────────────────────────────────────────── - 6 │ ] - ╰──── - help: Add `await` to your assertion. - - ⚠ eslint-plugin-jest(valid-expect): Matchers must be called to assert. - ╭─[valid_expect.tsx:1:1] - 1 │ expect(Promise.resolve(2)).resolves.toBe; - · ──────────────────────────────────────── - ╰──── - help: You need call your matcher, e.g. `expect(true).toBe(true)`. - - ⚠ eslint-plugin-jest(valid-expect): Async assertions must be awaited. - ╭─[valid_expect.tsx:4:25] - 3 │ return expect(functionReturningAPromise()).resolves.toEqual(1).then(() => { - 4 │ expect(Promise.resolve(2)).resolves.toBe(1); - · ─────────────────────────────────────────── - 5 │ }); - ╰──── - help: Add `await` to your assertion. - - ⚠ eslint-plugin-jest(valid-expect): Async assertions must be awaited. - ╭─[valid_expect.tsx:5:25] - 4 │ await expect(Promise.resolve(2)).resolves.toBe(1); - 5 │ expect(Promise.resolve(4)).resolves.toBe(4); - · ─────────────────────────────────────────── - 6 │ }); - ╰──── - help: Add `await` to your assertion. - - ⚠ eslint-plugin-jest(valid-expect): Expect must have a corresponding matcher call. - ╭─[valid_expect.tsx:3:27] - 2 │ test("valid-expect", async () => { - 3 │ await expect(Promise.resolve(1)); - · ────────────────────────── - 4 │ }); - ╰──── - help: Did you forget add a matcher, e.g. `toBe`, `toBeDefined` diff --git a/crates/oxc_linter/src/snapshots/vitest_valid_expect.snap b/crates/oxc_linter/src/snapshots/vitest_valid_expect.snap new file mode 100644 index 0000000000000..aa637c08c9b58 --- /dev/null +++ b/crates/oxc_linter/src/snapshots/vitest_valid_expect.snap @@ -0,0 +1,456 @@ +--- +source: crates/oxc_linter/src/tester.rs +assertion_line: 444 +--- + + ⚠ eslint-plugin-vitest(valid-expect): Expect requires at least 1 argument + ╭─[valid_expect.tsx:1:1] + 1 │ expect().toBe(2); + · ──────── + ╰──── + help: Add the missing arguments. + + ⚠ eslint-plugin-vitest(valid-expect): Expect requires at least 1 argument + ╭─[valid_expect.tsx:1:1] + 1 │ expect().toBe(true); + · ──────── + ╰──── + help: Add the missing arguments. + + ⚠ eslint-plugin-vitest(valid-expect): Expect requires at least 1 argument + ╭─[valid_expect.tsx:1:1] + 1 │ expect().toEqual("something"); + · ──────── + ╰──── + help: Add the missing arguments. + + ⚠ eslint-plugin-vitest(valid-expect): Expect takes at most 2 arguments + ╭─[valid_expect.tsx:1:1] + 1 │ expect("something", "else", "entirely").toEqual("something"); + · ─────────────────────────────────────── + ╰──── + help: Remove the extra arguments. + + ⚠ eslint-plugin-vitest(valid-expect): Expect takes at most 2 arguments + ╭─[valid_expect.tsx:1:1] + 1 │ expect("something", "else", "entirely").toEqual("something"); + · ─────────────────────────────────────── + ╰──── + help: Remove the extra arguments. + + ⚠ eslint-plugin-vitest(valid-expect): Expect takes at most 2 arguments + ╭─[valid_expect.tsx:1:1] + 1 │ expect("something", "else", "entirely").toEqual("something"); + · ─────────────────────────────────────── + ╰──── + help: Remove the extra arguments. + + ⚠ eslint-plugin-vitest(valid-expect): Expect requires at least 2 arguments + ╭─[valid_expect.tsx:1:1] + 1 │ expect("something").toEqual("something"); + · ─────────────────── + ╰──── + help: Add the missing arguments. + + ⚠ eslint-plugin-vitest(valid-expect): Expect requires at least 3 arguments + ╭─[valid_expect.tsx:1:1] + 1 │ expect("something", "else").toEqual("something"); + · ─────────────────────────── + ╰──── + help: Add the missing arguments. + + ⚠ eslint-plugin-vitest(valid-expect): Expect takes at most 1 argument + ╭─[valid_expect.tsx:1:1] + 1 │ expect(1, message).toBe(1); + · ────────────────── + ╰──── + help: Remove the extra arguments. + + ⚠ eslint-plugin-vitest(valid-expect): Expect must have a corresponding matcher call. + ╭─[valid_expect.tsx:1:1] + 1 │ expect("something"); + · ─────────────────── + ╰──── + help: Did you forget add a matcher, e.g. `toBe`, `toBeDefined` + + ⚠ eslint-plugin-vitest(valid-expect): Expect must have a corresponding matcher call. + ╭─[valid_expect.tsx:1:1] + 1 │ expect(); + · ──────── + ╰──── + help: Did you forget add a matcher, e.g. `toBe`, `toBeDefined` + + ⚠ eslint-plugin-vitest(valid-expect): Matchers must be called to assert. + ╭─[valid_expect.tsx:1:1] + 1 │ expect(true).toBeDefined; + · ──────────────────────── + ╰──── + help: You need call your matcher, e.g. `expect(true).toBe(true)`. + + ⚠ eslint-plugin-vitest(valid-expect): Matchers must be called to assert. + ╭─[valid_expect.tsx:1:1] + 1 │ expect(true).not.toBeDefined; + · ──────────────────────────── + ╰──── + help: You need call your matcher, e.g. `expect(true).toBe(true)`. + + ⚠ eslint-plugin-vitest(valid-expect): Matchers must be called to assert. + ╭─[valid_expect.tsx:1:1] + 1 │ expect(true).nope.toBeDefined; + · ───────────────────────────── + ╰──── + help: You need call your matcher, e.g. `expect(true).toBe(true)`. + + ⚠ eslint-plugin-vitest(valid-expect): Expect has an unknown modifier. + ╭─[valid_expect.tsx:1:1] + 1 │ expect(true).nope.toBeDefined(); + · ─────────────────────────────── + ╰──── + help: Is it a spelling mistake? + + ⚠ eslint-plugin-vitest(valid-expect): Expect has an unknown modifier. + ╭─[valid_expect.tsx:1:1] + 1 │ expect(true).not.resolves.toBeDefined(); + · ─────────────────────────────────────── + ╰──── + help: Is it a spelling mistake? + + ⚠ eslint-plugin-vitest(valid-expect): Expect has an unknown modifier. + ╭─[valid_expect.tsx:1:1] + 1 │ expect(true).not.not.toBeDefined(); + · ────────────────────────────────── + ╰──── + help: Is it a spelling mistake? + + ⚠ eslint-plugin-vitest(valid-expect): Expect has an unknown modifier. + ╭─[valid_expect.tsx:1:1] + 1 │ expect(true).resolves.not.exactly.toBeDefined(); + · ─────────────────────────────────────────────── + ╰──── + help: Is it a spelling mistake? + + ⚠ eslint-plugin-vitest(valid-expect): Matchers must be called to assert. + ╭─[valid_expect.tsx:1:1] + 1 │ expect(true).resolves; + · ───────────────────── + ╰──── + help: You need call your matcher, e.g. `expect(true).toBe(true)`. + + ⚠ eslint-plugin-vitest(valid-expect): Matchers must be called to assert. + ╭─[valid_expect.tsx:1:1] + 1 │ expect(true).rejects; + · ──────────────────── + ╰──── + help: You need call your matcher, e.g. `expect(true).toBe(true)`. + + ⚠ eslint-plugin-vitest(valid-expect): Matchers must be called to assert. + ╭─[valid_expect.tsx:1:1] + 1 │ expect(true).not; + · ──────────────── + ╰──── + help: You need call your matcher, e.g. `expect(true).toBe(true)`. + + ⚠ eslint-plugin-vitest(valid-expect): Async assertions must be awaited. + ╭─[valid_expect.tsx:1:1] + 1 │ expect(Promise.resolve(2)).resolves.toBeDefined(); + · ───────────────────────────────────────────────── + ╰──── + help: Add `await` to your assertion. + + ⚠ eslint-plugin-vitest(valid-expect): Async assertions must be awaited. + ╭─[valid_expect.tsx:1:1] + 1 │ expect(Promise.resolve(2)).rejects.toBeDefined(); + · ──────────────────────────────────────────────── + ╰──── + help: Add `await` to your assertion. + + ⚠ eslint-plugin-vitest(valid-expect): Async assertions must be awaited. + ╭─[valid_expect.tsx:1:1] + 1 │ expect(Promise.resolve(2)).resolves.toBeDefined(); + · ───────────────────────────────────────────────── + ╰──── + help: Add `await` to your assertion. + + ⚠ eslint-plugin-vitest(valid-expect): Async assertions must be awaited. + ╭─[valid_expect.tsx:6:31] + 5 │ ? expect(obj).toBe(true) + 6 │ : expect(obj).resolves.not.toThrow(); + · ────────────────────────────────── + 7 │ } + ╰──── + help: Add `await` to your assertion. + + ⚠ eslint-plugin-vitest(valid-expect): Async assertions must be awaited. + ╭─[valid_expect.tsx:5:31] + 4 │ this.isNot + 5 │ ? expect(obj).resolves.not.toThrow() + · ────────────────────────────────── + 6 │ : expect(obj).toBe(true); + ╰──── + help: Add `await` to your assertion. + + ⚠ eslint-plugin-vitest(valid-expect): Async assertions must be awaited. + ╭─[valid_expect.tsx:1:30] + 1 │ test("valid-expect", () => { expect(Promise.resolve(2)).resolves.toBeDefined(); }); + · ───────────────────────────────────────────────── + ╰──── + help: Add `await` to your assertion. + + ⚠ eslint-plugin-vitest(valid-expect): Async assertions must be awaited. + ╭─[valid_expect.tsx:1:30] + 1 │ test("valid-expect", () => { expect(Promise.resolve(2)).toResolve(); }); + · ────────────────────────────────────── + ╰──── + help: Add `await` to your assertion. + + ⚠ eslint-plugin-vitest(valid-expect): Async assertions must be awaited. + ╭─[valid_expect.tsx:1:30] + 1 │ test("valid-expect", () => { expect(Promise.resolve(2)).toResolve(); }); + · ────────────────────────────────────── + ╰──── + help: Add `await` to your assertion. + + ⚠ eslint-plugin-vitest(valid-expect): Async assertions must be awaited. + ╭─[valid_expect.tsx:1:30] + 1 │ test("valid-expect", () => { expect(Promise.resolve(2)).toReject(); }); + · ───────────────────────────────────── + ╰──── + help: Add `await` to your assertion. + + ⚠ eslint-plugin-vitest(valid-expect): Async assertions must be awaited. + ╭─[valid_expect.tsx:1:30] + 1 │ test("valid-expect", () => { expect(Promise.resolve(2)).not.toReject(); }); + · ───────────────────────────────────────── + ╰──── + help: Add `await` to your assertion. + + ⚠ eslint-plugin-vitest(valid-expect): Async assertions must be awaited. + ╭─[valid_expect.tsx:1:30] + 1 │ test("valid-expect", () => { expect(Promise.resolve(2)).resolves.not.toBeDefined(); }); + · ───────────────────────────────────────────────────── + ╰──── + help: Add `await` to your assertion. + + ⚠ eslint-plugin-vitest(valid-expect): Async assertions must be awaited. + ╭─[valid_expect.tsx:1:30] + 1 │ test("valid-expect", () => { expect(Promise.resolve(2)).rejects.toBeDefined(); }); + · ──────────────────────────────────────────────── + ╰──── + help: Add `await` to your assertion. + + ⚠ eslint-plugin-vitest(valid-expect): Async assertions must be awaited. + ╭─[valid_expect.tsx:1:30] + 1 │ test("valid-expect", () => { expect(Promise.resolve(2)).rejects.not.toBeDefined(); }); + · ──────────────────────────────────────────────────── + ╰──── + help: Add `await` to your assertion. + + ⚠ eslint-plugin-vitest(valid-expect): Async assertions must be awaited. + ╭─[valid_expect.tsx:1:36] + 1 │ test("valid-expect", async () => { expect(Promise.resolve(2)).resolves.toBeDefined(); }); + · ───────────────────────────────────────────────── + ╰──── + help: Add `await` to your assertion. + + ⚠ eslint-plugin-vitest(valid-expect): Async assertions must be awaited. + ╭─[valid_expect.tsx:1:36] + 1 │ test("valid-expect", async () => { expect(Promise.resolve(2)).resolves.not.toBeDefined(); }); + · ───────────────────────────────────────────────────── + ╰──── + help: Add `await` to your assertion. + + ⚠ eslint-plugin-vitest(valid-expect): Async assertions must be awaited. + ╭─[valid_expect.tsx:1:30] + 1 │ test("valid-expect", () => { expect(Promise.reject(2)).toRejectWith(2); }); + · ───────────────────────────────────────── + ╰──── + help: Add `await` to your assertion. + + ⚠ eslint-plugin-vitest(valid-expect): Async assertions must be awaited. + ╭─[valid_expect.tsx:1:30] + 1 │ test("valid-expect", () => { expect(Promise.reject(2)).rejects.toBe(2); }); + · ───────────────────────────────────────── + ╰──── + help: Add `await` to your assertion. + + ⚠ eslint-plugin-vitest(valid-expect): Async assertions must be awaited. + ╭─[valid_expect.tsx:3:21] + 2 │ test("valid-expect", async () => { + 3 │ expect(Promise.resolve(2)).resolves.not.toBeDefined(); + · ───────────────────────────────────────────────────── + 4 │ expect(Promise.resolve(1)).rejects.toBeDefined(); + ╰──── + help: Add `await` to your assertion. + + ⚠ eslint-plugin-vitest(valid-expect): Async assertions must be awaited. + ╭─[valid_expect.tsx:4:21] + 3 │ expect(Promise.resolve(2)).resolves.not.toBeDefined(); + 4 │ expect(Promise.resolve(1)).rejects.toBeDefined(); + · ──────────────────────────────────────────────── + 5 │ }); + ╰──── + help: Add `await` to your assertion. + + ⚠ eslint-plugin-vitest(valid-expect): Async assertions must be awaited. + ╭─[valid_expect.tsx:4:21] + 3 │ await expect(Promise.resolve(2)).resolves.not.toBeDefined(); + 4 │ expect(Promise.resolve(1)).rejects.toBeDefined(); + · ──────────────────────────────────────────────── + 5 │ }); + ╰──── + help: Add `await` to your assertion. + + ⚠ eslint-plugin-vitest(valid-expect): Async assertions must be awaited. + ╭─[valid_expect.tsx:3:21] + 2 │ test("valid-expect", async () => { + 3 │ expect(Promise.resolve(2)).resolves.not.toBeDefined(); + · ───────────────────────────────────────────────────── + 4 │ return expect(Promise.resolve(1)).rejects.toBeDefined(); + ╰──── + help: Add `await` to your assertion. + + ⚠ eslint-plugin-vitest(valid-expect): Async assertions must be awaited. + ╭─[valid_expect.tsx:4:28] + 3 │ expect(Promise.resolve(2)).resolves.not.toBeDefined(); + 4 │ return expect(Promise.resolve(1)).rejects.toBeDefined(); + · ──────────────────────────────────────────────── + 5 │ }); + ╰──── + help: Add `await` to your assertion. + + ⚠ eslint-plugin-vitest(valid-expect): Async assertions must be awaited. + ╭─[valid_expect.tsx:3:21] + 2 │ test("valid-expect", async () => { + 3 │ expect(Promise.resolve(2)).resolves.not.toBeDefined(); + · ───────────────────────────────────────────────────── + 4 │ return expect(Promise.resolve(1)).rejects.toBeDefined(); + ╰──── + help: Add `await` to your assertion. + + ⚠ eslint-plugin-vitest(valid-expect): Promises which return async assertions must be awaited. + ╭─[valid_expect.tsx:3:21] + 2 │ test("valid-expect", () => { + 3 │ Promise.x(expect(Promise.resolve(2)).resolves.not.toBeDefined()); + · ──────────────────────────────────────────────────────────────── + 4 │ }); + ╰──── + help: Add `await` to your assertion. + + ⚠ eslint-plugin-vitest(valid-expect): Promises which return async assertions must be awaited. + ╭─[valid_expect.tsx:3:21] + 2 │ test("valid-expect", () => { + 3 │ Promise.resolve(expect(Promise.resolve(2)).resolves.not.toBeDefined()); + · ────────────────────────────────────────────────────────────────────── + 4 │ }); + ╰──── + help: Add `await` to your assertion. + + ⚠ eslint-plugin-vitest(valid-expect): Promises which return async assertions must be awaited. + ╭─[valid_expect.tsx:3:21] + 2 │ test("valid-expect", () => { + 3 │ ╭─▶ Promise.all([ + 4 │ │ expect(Promise.resolve(2)).resolves.not.toBeDefined(), + 5 │ │ expect(Promise.resolve(3)).resolves.not.toBeDefined(), + 6 │ ╰─▶ ]); + 7 │ }); + ╰──── + help: Add `await` to your assertion. + + ⚠ eslint-plugin-vitest(valid-expect): Promises which return async assertions must be awaited. + ╭─[valid_expect.tsx:3:21] + 2 │ test("valid-expect", () => { + 3 │ ╭─▶ Promise.x([ + 4 │ │ expect(Promise.resolve(2)).resolves.not.toBeDefined(), + 5 │ │ expect(Promise.resolve(3)).resolves.not.toBeDefined(), + 6 │ ╰─▶ ]); + 7 │ }); + ╰──── + help: Add `await` to your assertion. + + ⚠ eslint-plugin-vitest(valid-expect): Async assertions must be awaited. + ╭─[valid_expect.tsx:4:25] + 3 │ const assertions = [ + 4 │ expect(Promise.resolve(2)).resolves.not.toBeDefined(), + · ───────────────────────────────────────────────────── + 5 │ expect(Promise.resolve(3)).resolves.not.toBeDefined(), + ╰──── + help: Add `await` to your assertion. + + ⚠ eslint-plugin-vitest(valid-expect): Async assertions must be awaited. + ╭─[valid_expect.tsx:5:25] + 4 │ expect(Promise.resolve(2)).resolves.not.toBeDefined(), + 5 │ expect(Promise.resolve(3)).resolves.not.toBeDefined(), + · ───────────────────────────────────────────────────── + 6 │ ] + ╰──── + help: Add `await` to your assertion. + + ⚠ eslint-plugin-vitest(valid-expect): Async assertions must be awaited. + ╭─[valid_expect.tsx:4:25] + 3 │ const assertions = [ + 4 │ expect(Promise.resolve(2)).toResolve(), + · ────────────────────────────────────── + 5 │ expect(Promise.resolve(3)).toReject(), + ╰──── + help: Add `await` to your assertion. + + ⚠ eslint-plugin-vitest(valid-expect): Async assertions must be awaited. + ╭─[valid_expect.tsx:5:25] + 4 │ expect(Promise.resolve(2)).toResolve(), + 5 │ expect(Promise.resolve(3)).toReject(), + · ───────────────────────────────────── + 6 │ ] + ╰──── + help: Add `await` to your assertion. + + ⚠ eslint-plugin-vitest(valid-expect): Async assertions must be awaited. + ╭─[valid_expect.tsx:4:25] + 3 │ const assertions = [ + 4 │ expect(Promise.resolve(2)).not.toResolve(), + · ────────────────────────────────────────── + 5 │ expect(Promise.resolve(3)).resolves.toReject(), + ╰──── + help: Add `await` to your assertion. + + ⚠ eslint-plugin-vitest(valid-expect): Async assertions must be awaited. + ╭─[valid_expect.tsx:5:25] + 4 │ expect(Promise.resolve(2)).not.toResolve(), + 5 │ expect(Promise.resolve(3)).resolves.toReject(), + · ────────────────────────────────────────────── + 6 │ ] + ╰──── + help: Add `await` to your assertion. + + ⚠ eslint-plugin-vitest(valid-expect): Matchers must be called to assert. + ╭─[valid_expect.tsx:1:1] + 1 │ expect(Promise.resolve(2)).resolves.toBe; + · ──────────────────────────────────────── + ╰──── + help: You need call your matcher, e.g. `expect(true).toBe(true)`. + + ⚠ eslint-plugin-vitest(valid-expect): Async assertions must be awaited. + ╭─[valid_expect.tsx:4:25] + 3 │ return expect(functionReturningAPromise()).resolves.toEqual(1).then(() => { + 4 │ expect(Promise.resolve(2)).resolves.toBe(1); + · ─────────────────────────────────────────── + 5 │ }); + ╰──── + help: Add `await` to your assertion. + + ⚠ eslint-plugin-vitest(valid-expect): Async assertions must be awaited. + ╭─[valid_expect.tsx:5:25] + 4 │ await expect(Promise.resolve(2)).resolves.toBe(1); + 5 │ expect(Promise.resolve(4)).resolves.toBe(4); + · ─────────────────────────────────────────── + 6 │ }); + ╰──── + help: Add `await` to your assertion. + + ⚠ eslint-plugin-vitest(valid-expect): Expect must have a corresponding matcher call. + ╭─[valid_expect.tsx:3:27] + 2 │ test("valid-expect", async () => { + 3 │ await expect(Promise.resolve(1)); + · ────────────────────────── + 4 │ }); + ╰──── + help: Did you forget add a matcher, e.g. `toBe`, `toBeDefined`