Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 52 additions & 11 deletions crates/oxc_linter/src/rules/jest/no_restricted_jest_methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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
Expand Down Expand Up @@ -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;
}
Expand All @@ -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,
));
}
},
);
Expand All @@ -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),
Expand All @@ -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" }]))),
Expand All @@ -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();
}
40 changes: 35 additions & 5 deletions crates/oxc_linter/src/snapshots/no_restricted_jest_methods.snap
Original file line number Diff line number Diff line change
@@ -1,39 +1,69 @@
---
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();
· ───────────────────
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
3 changes: 3 additions & 0 deletions crates/oxc_linter/src/utils/jest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ pub const JEST_METHOD_NAMES: phf::Set<&'static str> = phf_set![
"fit",
"it",
"jest",
"vi",
"test",
"xdescribe",
"xit",
Expand All @@ -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),
Expand All @@ -75,6 +77,7 @@ pub enum JestGeneralFnKind {
Describe,
Test,
Jest,
Vitest,
}

/// <https://jestjs.io/docs/configuration#testmatch-arraystring>
Expand Down
12 changes: 9 additions & 3 deletions crates/oxc_linter/src/utils/jest/parse_jest_fn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down Expand Up @@ -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),
Expand Down
1 change: 1 addition & 0 deletions crates/oxc_linter/src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down