mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 12:19:15 +00:00
feat(linter): eslint-plugin-vitest/expect-expect (#4299)
support [eslint-plugin-vitest/expect-expect](https://github.com/veritem/eslint-plugin-vitest/blob/main/src/rules/expect-expect.ts) --------- Co-authored-by: wenzhe <mysteryven@gmail.com>
This commit is contained in:
parent
ed49e169cb
commit
acc57295d5
3 changed files with 371 additions and 11 deletions
|
|
@ -13,13 +13,13 @@ use crate::{
|
|||
context::LintContext,
|
||||
rule::Rule,
|
||||
utils::{
|
||||
collect_possible_jest_call_node, get_node_name, is_type_of_jest_fn_call, JestFnKind,
|
||||
JestGeneralFnKind, PossibleJestNode,
|
||||
collect_possible_jest_call_node, get_node_name, get_test_plugin_name,
|
||||
is_type_of_jest_fn_call, JestFnKind, JestGeneralFnKind, PossibleJestNode, TestPluginName,
|
||||
},
|
||||
};
|
||||
|
||||
fn expect_expect_diagnostic(span0: Span) -> OxcDiagnostic {
|
||||
OxcDiagnostic::warn("eslint-plugin-jest(expect-expect): Test has no assertions")
|
||||
fn expect_expect_diagnostic(x0: TestPluginName, span0: Span) -> OxcDiagnostic {
|
||||
OxcDiagnostic::warn(format!("{x0}(expect-expect): Test has no assertions"))
|
||||
.with_help("Add assertion(s) in this Test")
|
||||
.with_label(span0)
|
||||
}
|
||||
|
|
@ -67,6 +67,17 @@ declare_oxc_lint!(
|
|||
/// });
|
||||
/// test('should assert something', () => {});
|
||||
/// ```
|
||||
///
|
||||
/// This rule is compatible with [eslint-plugin-vitest](https://github.com/veritem/eslint-plugin-vitest/blob/main/docs/rules/expect-expect.md),
|
||||
/// to use it, add the following configuration to your `.eslintrc.json`:
|
||||
///
|
||||
/// ```json
|
||||
/// {
|
||||
/// "rules": {
|
||||
/// "vitest/expect-expect": "error"
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
ExpectExpect,
|
||||
correctness
|
||||
);
|
||||
|
|
@ -111,6 +122,7 @@ fn run<'a>(
|
|||
) {
|
||||
let node = possible_jest_node.node;
|
||||
if let AstKind::CallExpression(call_expr) = node.kind() {
|
||||
let plugin_name = get_test_plugin_name(ctx);
|
||||
let name = get_node_name(&call_expr.callee);
|
||||
if is_type_of_jest_fn_call(
|
||||
call_expr,
|
||||
|
|
@ -126,6 +138,9 @@ fn run<'a>(
|
|||
if property_name == "todo" {
|
||||
return;
|
||||
}
|
||||
if property_name == "skip" && plugin_name.eq(&TestPluginName::Vitest) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Record visited nodes to avoid infinite loop.
|
||||
|
|
@ -135,7 +150,7 @@ fn run<'a>(
|
|||
check_arguments(call_expr, &rule.assert_function_names, &mut visited, ctx);
|
||||
|
||||
if !has_assert_function {
|
||||
ctx.diagnostic(expect_expect_diagnostic(call_expr.callee.span()));
|
||||
ctx.diagnostic(expect_expect_diagnostic(plugin_name, call_expr.callee.span()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -271,7 +286,7 @@ fn convert_pattern(pattern: &str) -> String {
|
|||
fn test() {
|
||||
use crate::tester::Tester;
|
||||
|
||||
let pass = vec![
|
||||
let mut pass = vec![
|
||||
("it.todo('will test something eventually')", None),
|
||||
("test.todo('will test something eventually')", None),
|
||||
("['x']();", None),
|
||||
|
|
@ -330,8 +345,8 @@ fn test() {
|
|||
(
|
||||
"
|
||||
theoretically('the number {input} is correctly translated to string', theories, theory => {
|
||||
const output = NumberToLongString(theory.input);
|
||||
expect(output).toBe(theory.expected);
|
||||
const output = NumberToLongString(theory.input);
|
||||
expect(output).toBe(theory.expected);
|
||||
})
|
||||
",
|
||||
Some(serde_json::json!([{ "additionalTestBlockFunctions": ["theoretically"] }])),
|
||||
|
|
@ -394,7 +409,7 @@ fn test() {
|
|||
),
|
||||
];
|
||||
|
||||
let fail = vec![
|
||||
let mut fail = vec![
|
||||
("it(\"should fail\", () => {});", None),
|
||||
("it(\"should fail\", myTest); function myTest() {}", None),
|
||||
("test(\"should fail\", () => {});", None),
|
||||
|
|
@ -486,5 +501,255 @@ fn test() {
|
|||
),
|
||||
];
|
||||
|
||||
Tester::new(ExpectExpect::NAME, pass, fail).with_jest_plugin(true).test_and_snapshot();
|
||||
let pass_vitest = vec![
|
||||
(
|
||||
"
|
||||
import { test } from 'vitest';
|
||||
test.skip(\"skipped test\", () => {})
|
||||
",
|
||||
None,
|
||||
),
|
||||
("it.todo(\"will test something eventually\")", None),
|
||||
("test.todo(\"will test something eventually\")", None),
|
||||
("['x']();", None),
|
||||
("it(\"should pass\", () => expect(true).toBeDefined())", None),
|
||||
("test(\"should pass\", () => expect(true).toBeDefined())", None),
|
||||
("it(\"should pass\", () => somePromise().then(() => expect(true).toBeDefined()))", None),
|
||||
("it(\"should pass\", myTest); function myTest() { expect(true).toBeDefined() }", None),
|
||||
(
|
||||
"
|
||||
test('should pass', () => {
|
||||
expect(true).toBeDefined();
|
||||
foo(true).toBe(true);
|
||||
});
|
||||
",
|
||||
Some(serde_json::json!([{ "assertFunctionNames": ["expect", "foo"] }]))
|
||||
),
|
||||
(
|
||||
"
|
||||
import { bench } from 'vitest'
|
||||
|
||||
bench('normal sorting', () => {
|
||||
const x = [1, 5, 4, 2, 3]
|
||||
x.sort((a, b) => {
|
||||
return a - b
|
||||
})
|
||||
}, { time: 1000 })
|
||||
",
|
||||
None,
|
||||
),
|
||||
(
|
||||
"it(\"should return undefined\", () => expectSaga(mySaga).returns());",
|
||||
Some(serde_json::json!([{ "assertFunctionNames": ["expectSaga"] }])),
|
||||
),
|
||||
(
|
||||
"test('verifies expect method call', () => expect$(123));",
|
||||
Some(serde_json::json!([{ "assertFunctionNames": ["expect\\$"] }])),
|
||||
),
|
||||
(
|
||||
"test('verifies expect method call', () => new Foo().expect(123));",
|
||||
Some(serde_json::json!([{ "assertFunctionNames": ["Foo.expect"] }])),
|
||||
),
|
||||
(
|
||||
"
|
||||
test('verifies deep expect method call', () => {
|
||||
tester.foo().expect(123);
|
||||
});
|
||||
",
|
||||
Some(serde_json::json!([{ "assertFunctionNames": ["tester.foo.expect"] }])),
|
||||
),
|
||||
(
|
||||
"
|
||||
test('verifies chained expect method call', () => {
|
||||
tester
|
||||
.foo()
|
||||
.bar()
|
||||
.expect(456);
|
||||
});
|
||||
",
|
||||
Some(serde_json::json!([{ "assertFunctionNames": ["tester.foo.bar.expect"] }])),
|
||||
),
|
||||
(
|
||||
"
|
||||
test(\"verifies the function call\", () => {
|
||||
td.verify(someFunctionCall())
|
||||
})
|
||||
",
|
||||
Some(serde_json::json!([{ "assertFunctionNames": ["td.verify"] }])),
|
||||
),
|
||||
(
|
||||
"it(\"should pass\", () => expect(true).toBeDefined())",
|
||||
Some(serde_json::json!([{
|
||||
"assertFunctionNames": "undefined",
|
||||
"additionalTestBlockFunctions": "undefined",
|
||||
}])),
|
||||
),
|
||||
(
|
||||
"
|
||||
theoretically('the number {input} is correctly translated to string', theories, theory => {
|
||||
const output = NumberToLongString(theory.input);
|
||||
expect(output).toBe(theory.expected);
|
||||
})
|
||||
",
|
||||
Some(serde_json::json!([{ "additionalTestBlockFunctions": ["theoretically"] }])),
|
||||
),
|
||||
(
|
||||
"test('should pass *', () => expect404ToBeLoaded());",
|
||||
Some(serde_json::json!([{ "assertFunctionNames": ["expect*"] }])),
|
||||
),
|
||||
(
|
||||
"test('should pass *', () => expect.toHaveStatus404());",
|
||||
Some(serde_json::json!([{ "assertFunctionNames": ["expect.**"] }])),
|
||||
),
|
||||
(
|
||||
"test('should pass', () => tester.foo().expect(123));",
|
||||
Some(serde_json::json!([{ "assertFunctionNames": ["tester.*.expect"] }])),
|
||||
),
|
||||
(
|
||||
"test('should pass **', () => tester.foo().expect(123));",
|
||||
Some(serde_json::json!([{ "assertFunctionNames": ["**"] }])),
|
||||
),
|
||||
(
|
||||
"test('should pass *', () => tester.foo().expect(123));",
|
||||
Some(serde_json::json!([{ "assertFunctionNames": ["*"] }])),
|
||||
),
|
||||
(
|
||||
"test('should pass', () => tester.foo().expect(123));",
|
||||
Some(serde_json::json!([{ "assertFunctionNames": ["tester.**"] }])),
|
||||
),
|
||||
(
|
||||
"test('should pass', () => tester.foo().expect(123));",
|
||||
Some(serde_json::json!([{ "assertFunctionNames": ["tester.*"] }])),
|
||||
),
|
||||
(
|
||||
"test('should pass', () => tester.foo().bar().expectIt(456));",
|
||||
Some(serde_json::json!([{ "assertFunctionNames": ["tester.**.expect*"] }])),
|
||||
),
|
||||
(
|
||||
"test('should pass', () => request.get().foo().expect(456));",
|
||||
Some(serde_json::json!([{ "assertFunctionNames": ["request.**.expect"] }])),
|
||||
),
|
||||
(
|
||||
"test('should pass', () => request.get().foo().expect(456));",
|
||||
Some(serde_json::json!([{ "assertFunctionNames": ["request.**.e*e*t"] }])),
|
||||
),
|
||||
(
|
||||
"
|
||||
import { test } from 'vitest';
|
||||
|
||||
test('should pass', () => {
|
||||
expect(true).toBeDefined();
|
||||
foo(true).toBe(true);
|
||||
});
|
||||
",
|
||||
Some(serde_json::json!([{ "assertFunctionNames": ["expect", "foo"] }])),
|
||||
),
|
||||
(
|
||||
"
|
||||
import { test as checkThat } from 'vitest';
|
||||
|
||||
checkThat('this passes', () => {
|
||||
expect(true).toBeDefined();
|
||||
foo(true).toBe(true);
|
||||
});
|
||||
",
|
||||
Some(serde_json::json!([{ "assertFunctionNames": ["expect", "foo"] }])),
|
||||
),
|
||||
(
|
||||
"
|
||||
const { test } = require('vitest');
|
||||
|
||||
test('verifies chained expect method call', () => {
|
||||
tester
|
||||
.foo()
|
||||
.bar()
|
||||
.expect(456);
|
||||
});
|
||||
",
|
||||
Some(serde_json::json!([{ "assertFunctionNames": ["tester.foo.bar.expect"] }])),
|
||||
),
|
||||
(
|
||||
"
|
||||
it(\"should pass with 'typecheck' enabled\", () => {
|
||||
expectTypeOf({ a: 1 }).toEqualTypeOf<{ a: number }>()
|
||||
});
|
||||
",
|
||||
None
|
||||
),
|
||||
];
|
||||
|
||||
let fail_vitest = vec![
|
||||
("it(\"should fail\", () => {});", None),
|
||||
("it(\"should fail\", myTest); function myTest() {}", None),
|
||||
("test(\"should fail\", () => {});", None),
|
||||
(
|
||||
"afterEach(() => {});",
|
||||
Some(serde_json::json!([{ "additionalTestBlockFunctions": ["afterEach"] }])),
|
||||
),
|
||||
// Todo: currently it's not support
|
||||
// (
|
||||
// "
|
||||
// theoretically('the number {input} is correctly translated to string', theories, theory => {
|
||||
// const output = NumberToLongString(theory.input);
|
||||
// })
|
||||
// ",
|
||||
// Some(serde_json::json!([{ "additionalTestBlockFunctions": ["theoretically"] }])),
|
||||
// ),
|
||||
("it(\"should fail\", () => { somePromise.then(() => {}); });", None),
|
||||
(
|
||||
"test(\"should fail\", () => { foo(true).toBe(true); })",
|
||||
Some(serde_json::json!([{ "assertFunctionNames": ["expect"] }])),
|
||||
),
|
||||
(
|
||||
"it(\"should also fail\",() => expectSaga(mySaga).returns());",
|
||||
Some(serde_json::json!([{ "assertFunctionNames": ["expect"] }])),
|
||||
),
|
||||
(
|
||||
"test('should fail', () => request.get().foo().expect(456));",
|
||||
Some(serde_json::json!([{ "assertFunctionNames": ["request.*.expect"] }])),
|
||||
),
|
||||
(
|
||||
"test('should fail', () => request.get().foo().bar().expect(456));",
|
||||
Some(serde_json::json!([{ "assertFunctionNames": ["request.foo**.expect"] }])),
|
||||
),
|
||||
(
|
||||
"test('should fail', () => tester.request(123));",
|
||||
Some(serde_json::json!([{ "assertFunctionNames": ["request.*"] }])),
|
||||
),
|
||||
(
|
||||
"test('should fail', () => request(123));",
|
||||
Some(serde_json::json!([{ "assertFunctionNames": ["request.*"] }])),
|
||||
),
|
||||
(
|
||||
"test('should fail', () => request(123));",
|
||||
Some(serde_json::json!([{ "assertFunctionNames": ["request.**"] }])),
|
||||
),
|
||||
(
|
||||
"
|
||||
import { test as checkThat } from 'vitest';
|
||||
|
||||
checkThat('this passes', () => {
|
||||
// ...
|
||||
});
|
||||
",
|
||||
Some(serde_json::json!([{ "assertFunctionNames": ["expect", "foo"] }])),
|
||||
),
|
||||
// Todo: currently we couldn't support ignore the typecheck option.
|
||||
// (
|
||||
// "
|
||||
// it(\"should fail without 'typecheck' enabled\", () => {
|
||||
// expectTypeOf({ a: 1 }).toEqualTypeOf<{ a: number }>()
|
||||
// });
|
||||
// ",
|
||||
// None,
|
||||
// ),
|
||||
];
|
||||
|
||||
pass.extend(pass_vitest);
|
||||
fail.extend(fail_vitest);
|
||||
|
||||
Tester::new(ExpectExpect::NAME, pass, fail)
|
||||
.with_jest_plugin(true)
|
||||
.with_vitest_plugin(true)
|
||||
.test_and_snapshot();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
source: crates/oxc_linter/src/tester.rs
|
||||
assertion_line: 216
|
||||
---
|
||||
⚠ eslint-plugin-jest(expect-expect): Test has no assertions
|
||||
╭─[expect_expect.tsx:1:1]
|
||||
|
|
@ -127,3 +128,96 @@ source: crates/oxc_linter/src/tester.rs
|
|||
3 │ t.test("emitter with newListener that removes handler", function(t) {
|
||||
╰────
|
||||
help: Add assertion(s) in this Test
|
||||
|
||||
⚠ eslint-plugin-jest(expect-expect): Test has no assertions
|
||||
╭─[expect_expect.tsx:1:1]
|
||||
1 │ it("should fail", () => {});
|
||||
· ──
|
||||
╰────
|
||||
help: Add assertion(s) in this Test
|
||||
|
||||
⚠ eslint-plugin-jest(expect-expect): Test has no assertions
|
||||
╭─[expect_expect.tsx:1:1]
|
||||
1 │ it("should fail", myTest); function myTest() {}
|
||||
· ──
|
||||
╰────
|
||||
help: Add assertion(s) in this Test
|
||||
|
||||
⚠ eslint-plugin-jest(expect-expect): Test has no assertions
|
||||
╭─[expect_expect.tsx:1:1]
|
||||
1 │ test("should fail", () => {});
|
||||
· ────
|
||||
╰────
|
||||
help: Add assertion(s) in this Test
|
||||
|
||||
⚠ eslint-plugin-jest(expect-expect): Test has no assertions
|
||||
╭─[expect_expect.tsx:1:1]
|
||||
1 │ afterEach(() => {});
|
||||
· ─────────
|
||||
╰────
|
||||
help: Add assertion(s) in this Test
|
||||
|
||||
⚠ eslint-plugin-jest(expect-expect): Test has no assertions
|
||||
╭─[expect_expect.tsx:1:1]
|
||||
1 │ it("should fail", () => { somePromise.then(() => {}); });
|
||||
· ──
|
||||
╰────
|
||||
help: Add assertion(s) in this Test
|
||||
|
||||
⚠ eslint-plugin-jest(expect-expect): Test has no assertions
|
||||
╭─[expect_expect.tsx:1:1]
|
||||
1 │ test("should fail", () => { foo(true).toBe(true); })
|
||||
· ────
|
||||
╰────
|
||||
help: Add assertion(s) in this Test
|
||||
|
||||
⚠ eslint-plugin-jest(expect-expect): Test has no assertions
|
||||
╭─[expect_expect.tsx:1:1]
|
||||
1 │ it("should also fail",() => expectSaga(mySaga).returns());
|
||||
· ──
|
||||
╰────
|
||||
help: Add assertion(s) in this Test
|
||||
|
||||
⚠ eslint-plugin-jest(expect-expect): Test has no assertions
|
||||
╭─[expect_expect.tsx:1:1]
|
||||
1 │ test('should fail', () => request.get().foo().expect(456));
|
||||
· ────
|
||||
╰────
|
||||
help: Add assertion(s) in this Test
|
||||
|
||||
⚠ eslint-plugin-jest(expect-expect): Test has no assertions
|
||||
╭─[expect_expect.tsx:1:1]
|
||||
1 │ test('should fail', () => request.get().foo().bar().expect(456));
|
||||
· ────
|
||||
╰────
|
||||
help: Add assertion(s) in this Test
|
||||
|
||||
⚠ eslint-plugin-jest(expect-expect): Test has no assertions
|
||||
╭─[expect_expect.tsx:1:1]
|
||||
1 │ test('should fail', () => tester.request(123));
|
||||
· ────
|
||||
╰────
|
||||
help: Add assertion(s) in this Test
|
||||
|
||||
⚠ eslint-plugin-jest(expect-expect): Test has no assertions
|
||||
╭─[expect_expect.tsx:1:1]
|
||||
1 │ test('should fail', () => request(123));
|
||||
· ────
|
||||
╰────
|
||||
help: Add assertion(s) in this Test
|
||||
|
||||
⚠ eslint-plugin-jest(expect-expect): Test has no assertions
|
||||
╭─[expect_expect.tsx:1:1]
|
||||
1 │ test('should fail', () => request(123));
|
||||
· ────
|
||||
╰────
|
||||
help: Add assertion(s) in this Test
|
||||
|
||||
⚠ eslint-plugin-vitest(expect-expect): Test has no assertions
|
||||
╭─[expect_expect.tsx:4:17]
|
||||
3 │
|
||||
4 │ checkThat('this passes', () => {
|
||||
· ─────────
|
||||
5 │ // ...
|
||||
╰────
|
||||
help: Add assertion(s) in this Test
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ pub use self::{
|
|||
pub fn is_jest_rule_adapted_to_vitest(rule_name: &str) -> bool {
|
||||
let jest_rules: &[&str] = &[
|
||||
"consistent-test-it",
|
||||
"expect-expect",
|
||||
"no-alias-methods",
|
||||
"no-disabled-tests",
|
||||
"no-focused-tests",
|
||||
|
|
@ -30,7 +31,7 @@ pub fn is_jest_rule_adapted_to_vitest(rule_name: &str) -> bool {
|
|||
jest_rules.contains(&rule_name)
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub enum TestPluginName {
|
||||
Jest,
|
||||
Vitest,
|
||||
|
|
|
|||
Loading…
Reference in a new issue