feat(linter/eslint-plugin-vitest): implement no-restricted-vi-methods (#4956)

Related to #4656
This commit is contained in:
dalaoshu 2024-08-22 08:07:53 +08:00 committed by GitHub
parent ddf83ff1b9
commit 14bf5d5177
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 100 additions and 19 deletions

View file

@ -13,14 +13,14 @@ use crate::{
},
};
fn restricted_jest_method(x0: &str, span1: Span) -> OxcDiagnostic {
OxcDiagnostic::warn("Disallow specific `jest.` methods")
fn restricted_jest_method(method_name: &str, x0: &str, span1: Span) -> OxcDiagnostic {
OxcDiagnostic::warn(format!("Disallow specific `{method_name}.` methods"))
.with_help(format!("Use of `{x0:?}` is disallowed"))
.with_label(span1)
}
fn restricted_jest_method_with_message(x0: &str, span1: Span) -> OxcDiagnostic {
OxcDiagnostic::warn("Disallow specific `jest.` methods")
fn restricted_jest_method_with_message(method_name: &str, x0: &str, span1: Span) -> OxcDiagnostic {
OxcDiagnostic::warn(format!("Disallow specific `{method_name}.` methods"))
.with_help(format!("{x0:?}"))
.with_label(span1)
}
@ -44,7 +44,7 @@ impl std::ops::Deref for NoRestrictedJestMethods {
declare_oxc_lint!(
/// ### What it does
///
/// Restrict the use of specific `jest` methods.
/// Restrict the use of specific `jest` and `vi` methods.
///
/// ### Example
/// ```javascript
@ -106,7 +106,10 @@ impl NoRestrictedJestMethods {
call_expr,
possible_jest_node,
ctx,
&[JestFnKind::General(JestGeneralFnKind::Jest)],
&[
JestFnKind::General(JestGeneralFnKind::Jest),
JestFnKind::General(JestGeneralFnKind::Vitest),
],
) {
return;
}
@ -122,15 +125,21 @@ impl NoRestrictedJestMethods {
};
if self.contains(property_name) {
let method_name =
mem_expr.object().get_identifier_reference().map_or("jest", |id| id.name.as_str());
self.get_message(property_name).map_or_else(
|| {
ctx.diagnostic(restricted_jest_method(property_name, span));
ctx.diagnostic(restricted_jest_method(method_name, property_name, span));
},
|message| {
if message.trim() == "" {
ctx.diagnostic(restricted_jest_method(property_name, span));
ctx.diagnostic(restricted_jest_method(method_name, property_name, span));
} else {
ctx.diagnostic(restricted_jest_method_with_message(&message, span));
ctx.diagnostic(restricted_jest_method_with_message(
method_name,
&message,
span,
));
}
},
);
@ -156,7 +165,7 @@ impl NoRestrictedJestMethods {
fn test() {
use crate::tester::Tester;
let pass = vec![
let mut pass = vec![
("jest", None),
("jest()", None),
("jest.mock()", None),
@ -172,7 +181,7 @@ fn test() {
),
];
let fail = vec![
let mut fail = vec![
("jest.fn()", Some(serde_json::json!([{ "fn": null }]))),
("jest[\"fn\"]()", Some(serde_json::json!([{ "fn": null }]))),
("jest.mock()", Some(serde_json::json!([{ "mock": "Do not use mocks" }]))),
@ -186,7 +195,39 @@ fn test() {
),
];
let pass_vitest = vec![
("vi", None),
("vi()", None),
("vi.mock()", None),
("expect(a).rejects;", None),
("expect(a);", None),
(
"
import { vi } from 'vitest';
vi;
",
None,
), // { "parserOptions": { "sourceType": "module" } }
];
let fail_vitest = vec![
("vi.fn()", Some(serde_json::json!([{ "fn": null }]))),
("vi.mock()", Some(serde_json::json!([{ "mock": "Do not use mocks" }]))),
(
"
import { vi } from 'vitest';
vi.advanceTimersByTime();
",
Some(serde_json::json!([{ "advanceTimersByTime": null }])),
), // { "parserOptions": { "sourceType": "module" } },
(r#"vi["fn"]()"#, Some(serde_json::json!([{ "fn": null }]))),
];
pass.extend(pass_vitest);
fail.extend(fail_vitest);
Tester::new(NoRestrictedJestMethods::NAME, pass, fail)
.with_jest_plugin(true)
.with_vitest_plugin(true)
.test_and_snapshot();
}

View file

@ -1,35 +1,35 @@
---
source: crates/oxc_linter/src/tester.rs
---
⚠ eslint-plugin-jest(no-restricted-jest-methods): Disallow specific `jest.` methods
⚠ eslint-plugin-vitest(no-restricted-jest-methods): Disallow specific `jest.` methods
╭─[no_restricted_jest_methods.tsx:1:6]
1 │ jest.fn()
· ──
╰────
help: Use of `"fn"` is disallowed
⚠ eslint-plugin-jest(no-restricted-jest-methods): Disallow specific `jest.` methods
⚠ eslint-plugin-vitest(no-restricted-jest-methods): Disallow specific `jest.` methods
╭─[no_restricted_jest_methods.tsx:1:6]
1 │ jest["fn"]()
· ────
╰────
help: Use of `"fn"` is disallowed
⚠ eslint-plugin-jest(no-restricted-jest-methods): Disallow specific `jest.` methods
⚠ eslint-plugin-vitest(no-restricted-jest-methods): Disallow specific `jest.` methods
╭─[no_restricted_jest_methods.tsx:1:6]
1 │ jest.mock()
· ────
╰────
help: "Do not use mocks"
⚠ eslint-plugin-jest(no-restricted-jest-methods): Disallow specific `jest.` methods
⚠ eslint-plugin-vitest(no-restricted-jest-methods): Disallow specific `jest.` methods
╭─[no_restricted_jest_methods.tsx:1:6]
1 │ jest["mock"]()
· ──────
╰────
help: "Do not use mocks"
⚠ eslint-plugin-jest(no-restricted-jest-methods): Disallow specific `jest.` methods
⚠ eslint-plugin-vitest(no-restricted-jest-methods): Disallow specific `jest.` methods
╭─[no_restricted_jest_methods.tsx:3:22]
2 │ import { jest } from '@jest/globals';
3 │ jest.advanceTimersByTime();
@ -37,3 +37,33 @@ source: crates/oxc_linter/src/tester.rs
4 │
╰────
help: Use of `"advanceTimersByTime"` is disallowed
⚠ eslint-plugin-vitest(no-restricted-jest-methods): Disallow specific `vi.` methods
╭─[no_restricted_jest_methods.tsx:1:4]
1 │ vi.fn()
· ──
╰────
help: Use of `"fn"` is disallowed
⚠ eslint-plugin-vitest(no-restricted-jest-methods): Disallow specific `vi.` methods
╭─[no_restricted_jest_methods.tsx:1:4]
1 │ vi.mock()
· ────
╰────
help: "Do not use mocks"
⚠ eslint-plugin-vitest(no-restricted-jest-methods): Disallow specific `vi.` methods
╭─[no_restricted_jest_methods.tsx:3:12]
2 │ import { vi } from 'vitest';
3 │ vi.advanceTimersByTime();
· ───────────────────
4 │
╰────
help: Use of `"advanceTimersByTime"` is disallowed
⚠ eslint-plugin-vitest(no-restricted-jest-methods): Disallow specific `vi.` methods
╭─[no_restricted_jest_methods.tsx:1:4]
1 │ vi["fn"]()
· ────
╰────
help: Use of `"fn"` is disallowed

View file

@ -31,6 +31,7 @@ pub const JEST_METHOD_NAMES: phf::Set<&'static str> = phf_set![
"fit",
"it",
"jest",
"vi",
"test",
"xdescribe",
"xit",
@ -51,6 +52,7 @@ impl JestFnKind {
match name {
"expect" => Self::Expect,
"expectTypeOf" => Self::ExpectTypeOf,
"vi" => Self::General(JestGeneralFnKind::Vitest),
"jest" => Self::General(JestGeneralFnKind::Jest),
"describe" | "fdescribe" | "xdescribe" => Self::General(JestGeneralFnKind::Describe),
"fit" | "it" | "test" | "xit" | "xtest" => Self::General(JestGeneralFnKind::Test),
@ -75,6 +77,7 @@ pub enum JestGeneralFnKind {
Describe,
Test,
Jest,
Vitest,
}
/// <https://jestjs.io/docs/configuration#testmatch-arraystring>

View file

@ -88,7 +88,8 @@ pub fn parse_jest_fn_call<'a>(
return None;
}
if matches!(kind, JestFnKind::General(JestGeneralFnKind::Jest)) {
if matches!(kind, JestFnKind::General(JestGeneralFnKind::Jest | JestGeneralFnKind::Vitest))
{
return parse_jest_jest_fn_call(members, name, resolved.local);
}
@ -244,12 +245,17 @@ fn parse_jest_jest_fn_call<'a>(
name: &'a str,
local: &'a str,
) -> Option<ParsedJestFnCall<'a>> {
if !name.to_ascii_lowercase().eq_ignore_ascii_case("jest") {
let lowercase_name = name.to_ascii_lowercase();
if !(lowercase_name == "jest" || lowercase_name == "vi") {
return None;
}
let kind =
if lowercase_name == "jest" { JestGeneralFnKind::Jest } else { JestGeneralFnKind::Vitest };
return Some(ParsedJestFnCall::GeneralJest(ParsedGeneralJestFnCall {
kind: JestFnKind::General(JestGeneralFnKind::Jest),
kind: JestFnKind::General(kind),
members,
name: Cow::Borrowed(name),
local: Cow::Borrowed(local),

View file

@ -27,6 +27,7 @@ pub fn is_jest_rule_adapted_to_vitest(rule_name: &str) -> bool {
"no-disabled-tests",
"no-focused-tests",
"no-identical-title",
"no-restricted-jest-methods",
"no-test-prefixes",
"prefer-hooks-in-order",
"valid-describe-callback",