fix(linter): jest/vitest rule compat (#4797)

This pr is for jest/vitest compat and add another jest rule condition

---------

Co-authored-by: Don Isaac <donald.isaac@gmail.com>
Co-authored-by: Wang Wenzhe <mysteryven@gmail.com>
This commit is contained in:
cinchen 2024-08-29 11:04:00 +08:00 committed by GitHub
parent 2bc36ccbb6
commit fdef8aec72
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 1360 additions and 12 deletions

View file

@ -61,6 +61,11 @@ impl FrameworkFlags {
pub const fn is_vitest(self) -> bool {
self.contains(Self::Vitest)
}
#[inline]
pub const fn is_jest(self) -> bool {
self.contains(Self::Jest)
}
}
/// <https://jestjs.io/docs/configuration#testmatch-arraystring>
@ -83,3 +88,7 @@ pub(crate) fn is_jestlike_file(path: &Path) -> bool {
pub(crate) fn has_vitest_imports(module_record: &ModuleRecord) -> bool {
module_record.import_entries.iter().any(|entry| entry.module_request.name() == "vitest")
}
pub(crate) fn has_jest_imports(module_record: &ModuleRecord) -> bool {
module_record.import_entries.iter().any(|entry| entry.module_request.name() == "@jest/globals")
}

View file

@ -160,11 +160,12 @@ impl Linter {
if self.options.plugins.jest || self.options.plugins.vitest {
let mut test_flags = FrameworkFlags::empty();
if frameworks::is_jestlike_file(path) {
test_flags.set(FrameworkFlags::Jest, self.options.plugins.jest);
test_flags.set(FrameworkFlags::Vitest, self.options.plugins.vitest);
} else if frameworks::has_vitest_imports(ctx.module_record()) {
if frameworks::has_vitest_imports(ctx.module_record()) {
test_flags.set(FrameworkFlags::Vitest, true);
} else if frameworks::is_jestlike_file(path)
|| frameworks::has_jest_imports(ctx.module_record())
{
test_flags.set(FrameworkFlags::Jest, true);
}
ctx = ctx.and_frameworks(test_flags);

View file

@ -67,8 +67,8 @@ fn test() {
),
("window.location = 'valid'", None, None, None),
("module.somethingElse = 'foo';", None, None, None),
("export const myThing = 'valid'", None, None, None),
("export default function () {}", None, None, None),
("export const myThing = 'valid'", None, None, Some(PathBuf::from("foo.js"))),
("export default function () {}", None, None, Some(PathBuf::from("foo.js"))),
("module.exports = function(){}", None, None, None),
("module.exports.myThing = 'valid';", None, None, None),
];

View file

@ -622,5 +622,5 @@ fn tests() {
),
];
Tester::new(RequireHook::NAME, pass, fail).test_and_snapshot();
Tester::new(RequireHook::NAME, pass, fail).with_jest_plugin(true).test_and_snapshot();
}

View file

@ -95,12 +95,26 @@ fn run<'a>(possible_jest_node: &PossibleJestNode<'a, '_>, ctx: &LintContext<'a>)
return;
}
if call_expr.arguments.len() == 0 {
let arg_len = call_expr.arguments.len();
// Handle describe.todo("runPrettierFormat")
if ctx.frameworks().is_vitest() && arg_len == 1 {
if let Some(member_expr) = call_expr.callee.as_member_expression() {
let Some(property_name) = member_expr.static_property_name() else {
return;
};
if property_name == "todo" {
return;
}
}
}
if arg_len == 0 {
diagnostic(ctx, call_expr.span, Message::NameAndCallback);
return;
}
if call_expr.arguments.len() == 1 {
if arg_len == 1 {
// For better error notice, we locate it to arguments[0]
diagnostic(ctx, call_expr.arguments[0].span(), Message::NameAndCallback);
return;
@ -353,7 +367,13 @@ fn test() {
("fdescribe(\"foo\", () => {})", None),
("describe.only(\"foo\", () => {})", None),
("describe.skip(\"foo\", () => {})", None),
("describe.todo(\"runPrettierFormat\");", None),
(
"
import { describe } from 'vitest';
describe.todo(\"runPrettierFormat\");
",
None,
),
(
"
describe('foo', () => {

View file

@ -364,6 +364,8 @@ impl Tester {
self.current_working_directory.join(&self.rule_path)
} else if let Some(path) = path {
self.current_working_directory.join(path)
} else if self.plugins.jest {
self.rule_path.with_extension("test.tsx")
} else {
self.rule_path.clone()
};

View file

@ -12,7 +12,10 @@ use oxc_span::Span;
use crate::{
context::LintContext,
utils::jest::{is_pure_string, JestFnKind, JestGeneralFnKind, PossibleJestNode},
utils::{
jest::{is_pure_string, JestFnKind, JestGeneralFnKind, PossibleJestNode},
vitest::VALID_VITEST_FN_CALL_CHAINS,
},
};
pub fn parse_jest_fn_call<'a>(
@ -100,7 +103,12 @@ pub fn parse_jest_fn_call<'a>(
let mut call_chains = Vec::from([Cow::Borrowed(name)]);
call_chains.extend(members.iter().filter_map(KnownMemberExpressionProperty::name));
if !is_valid_jest_call(&call_chains) {
if ctx.frameworks().is_jest() && !is_valid_jest_call(&call_chains) {
return None;
}
if ctx.frameworks().is_vitest() && !is_valid_vitest_call(&call_chains) {
return None;
}
@ -298,6 +306,10 @@ fn is_valid_jest_call(members: &[Cow<str>]) -> bool {
.is_ok()
}
fn is_valid_vitest_call(members: &[Cow<str>]) -> bool {
VALID_VITEST_FN_CALL_CHAINS.contains(&members.join("."))
}
fn resolve_to_jest_fn<'a>(
call_expr: &'a CallExpression<'a>,
original: Option<&'a str>,
@ -333,6 +345,7 @@ impl<'a> ParsedJestFnCall<'a> {
}
}
#[derive(Debug)]
pub struct ParsedGeneralJestFnCall<'a> {
pub kind: JestFnKind,
pub members: Vec<KnownMemberExpressionProperty<'a>>,

View file

@ -11,6 +11,9 @@ use super::{
PossibleJestNode,
};
mod valid_vitest_fn;
pub use crate::utils::vitest::valid_vitest_fn::VALID_VITEST_FN_CALL_CHAINS;
pub fn parse_expect_and_typeof_vitest_fn_call<'a>(
call_expr: &'a CallExpression<'a>,
possible_jest_node: &PossibleJestNode<'a, '_>,

File diff suppressed because it is too large Load diff