diff --git a/crates/oxc_linter/src/rules/jest/no_done_callback.rs b/crates/oxc_linter/src/rules/jest/no_done_callback.rs index f61fda3c2..91ddbf3b8 100644 --- a/crates/oxc_linter/src/rules/jest/no_done_callback.rs +++ b/crates/oxc_linter/src/rules/jest/no_done_callback.rs @@ -13,16 +13,24 @@ use crate::{ context::LintContext, rule::Rule, utils::{ - get_node_name, parse_general_jest_fn_call, JestFnKind, JestGeneralFnKind, - ParsedGeneralJestFnCall, + collect_possible_jest_call_node, get_node_name, parse_general_jest_fn_call_new, JestFnKind, + JestGeneralFnKind, PossibleJestNode, }, - AstNode, }; #[derive(Debug, Error, Diagnostic)] -#[error("eslint-plugin-jest(no-done-callback): {0:?}")] -#[diagnostic(severity(warning), help("{1:?}"))] -struct NoDoneCallbackDiagnostic(&'static str, &'static str, #[label] pub Span); +enum NoDoneCallbackDiagnostic { + #[error("eslint-plugin-jest(no-done-callback): Function parameter(s) use the `done` argument")] + #[diagnostic( + severity(warning), + help("Return a Promise instead of relying on callback parameter") + )] + NoDoneCallback(#[label] Span), + + #[error("eslint-plugin-jest(no-done-callback): Function parameter(s) use the `done` argument")] + #[diagnostic(severity(warning), help("Use await instead of callback in async functions"))] + UseAwaitInsteadOfCallback(#[label] Span), +} #[derive(Debug, Default, Clone)] pub struct NoDoneCallback; @@ -74,74 +82,78 @@ declare_oxc_lint!( ); impl Rule for NoDoneCallback { - fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { - if let AstKind::CallExpression(call_expr) = node.kind() { - if let Some(jest_fn_call) = parse_general_jest_fn_call(call_expr, node, ctx) { - let ParsedGeneralJestFnCall { kind, .. } = jest_fn_call; - if !matches!( - kind, - JestFnKind::General(JestGeneralFnKind::Test | JestGeneralFnKind::Hook) - ) { - return; - } + fn run_once(&self, ctx: &LintContext) { + for node in &collect_possible_jest_call_node(ctx) { + run(node, ctx); + } + } +} - let is_jest_each = get_node_name(&call_expr.callee).ends_with("each"); +fn run<'a>(possible_jest_node: &PossibleJestNode<'a, '_>, ctx: &LintContext<'a>) { + let node = possible_jest_node.node; + if let AstKind::CallExpression(call_expr) = node.kind() { + if let Some(jest_fn_call) = + parse_general_jest_fn_call_new(call_expr, possible_jest_node, ctx) + { + let kind = jest_fn_call.kind; + if !matches!( + kind, + JestFnKind::General(JestGeneralFnKind::Test | JestGeneralFnKind::Hook) + ) { + return; + } - if is_jest_each - && !matches!(call_expr.callee, Expression::TaggedTemplateExpression(_)) - { - // isJestEach but not a TaggedTemplateExpression, so this must be - // the `jest.each([])()` syntax which this rule doesn't support due - // to its complexity (see jest-community/eslint-plugin-jest#710) - return; - } + let is_jest_each = get_node_name(&call_expr.callee).ends_with("each"); - let Some(Argument::Expression(expr)) = - find_argument_of_callback(call_expr, is_jest_each, kind) - else { - return; - }; + if is_jest_each && !matches!(call_expr.callee, Expression::TaggedTemplateExpression(_)) + { + // isJestEach but not a TaggedTemplateExpression, so this must be + // the `jest.each([])()` syntax which this rule doesn't support due + // to its complexity (see jest-community/eslint-plugin-jest#710) + return; + } - let callback_arg_index = usize::from(is_jest_each); + let Some(Argument::Expression(expr)) = + find_argument_of_callback(call_expr, is_jest_each, kind) + else { + return; + }; - match expr { - Expression::FunctionExpression(func_expr) => { - if func_expr.params.parameters_count() != 1 + callback_arg_index { - return; - } - let Some(span) = get_span_of_first_parameter(&func_expr.params) else { - return; - }; + let callback_arg_index = usize::from(is_jest_each); - if func_expr.r#async { - let (message, help) = Message::UseAwaitInsteadOfCallback.details(); - ctx.diagnostic(NoDoneCallbackDiagnostic(message, help, span)); - return; - } - - let (message, help) = Message::NoDoneCallback.details(); - ctx.diagnostic(NoDoneCallbackDiagnostic(message, help, span)); + match expr { + Expression::FunctionExpression(func_expr) => { + if func_expr.params.parameters_count() != 1 + callback_arg_index { + return; } - Expression::ArrowExpression(arrow_expr) => { - if arrow_expr.params.parameters_count() != 1 + callback_arg_index { - return; - } + let Some(span) = get_span_of_first_parameter(&func_expr.params) else { + return; + }; - let Some(span) = get_span_of_first_parameter(&arrow_expr.params) else { - return; - }; - - if arrow_expr.r#async { - let (message, help) = Message::UseAwaitInsteadOfCallback.details(); - ctx.diagnostic(NoDoneCallbackDiagnostic(message, help, span)); - return; - } - - let (message, help) = Message::NoDoneCallback.details(); - ctx.diagnostic(NoDoneCallbackDiagnostic(message, help, span)); + if func_expr.r#async { + ctx.diagnostic(NoDoneCallbackDiagnostic::UseAwaitInsteadOfCallback(span)); + return; } - _ => {} + + ctx.diagnostic(NoDoneCallbackDiagnostic::NoDoneCallback(span)); } + Expression::ArrowExpression(arrow_expr) => { + if arrow_expr.params.parameters_count() != 1 + callback_arg_index { + return; + } + + let Some(span) = get_span_of_first_parameter(&arrow_expr.params) else { + return; + }; + + if arrow_expr.r#async { + ctx.diagnostic(NoDoneCallbackDiagnostic::UseAwaitInsteadOfCallback(span)); + return; + } + + ctx.diagnostic(NoDoneCallbackDiagnostic::NoDoneCallback(span)); + } + _ => {} } } } @@ -176,26 +188,6 @@ fn find_argument_of_callback<'a>( None } -enum Message { - NoDoneCallback, - UseAwaitInsteadOfCallback, -} - -impl Message { - fn details(&self) -> (&'static str, &'static str) { - match self { - Self::NoDoneCallback => ( - "Function parameter(s) use the `done` argument", - "Return a Promise instead of relying on callback parameter", - ), - Self::UseAwaitInsteadOfCallback => ( - "Function parameter(s) use the `done` argument", - "Use await instead of callback in async functions", - ), - } - } -} - #[test] fn test() { use crate::tester::Tester; diff --git a/crates/oxc_linter/src/rules/jest/no_focused_tests.rs b/crates/oxc_linter/src/rules/jest/no_focused_tests.rs index 6d893d80b..e0a85b502 100644 --- a/crates/oxc_linter/src/rules/jest/no_focused_tests.rs +++ b/crates/oxc_linter/src/rules/jest/no_focused_tests.rs @@ -11,10 +11,10 @@ use crate::{ fixer::Fix, rule::Rule, utils::{ - parse_general_jest_fn_call, JestFnKind, JestGeneralFnKind, MemberExpressionElement, - ParsedGeneralJestFnCall, + collect_possible_jest_call_node, parse_general_jest_fn_call_new, JestFnKind, + JestGeneralFnKind, MemberExpressionElementNew, ParsedGeneralJestFnCallNew, + PossibleJestNode, }, - AstNode, }; #[derive(Debug, Error, Diagnostic)] @@ -58,39 +58,46 @@ declare_oxc_lint!( ); impl Rule for NoFocusedTests { - fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { - let AstKind::CallExpression(call_expr) = node.kind() else { return }; - let Some(jest_fn_call) = parse_general_jest_fn_call(call_expr, node, ctx) else { return }; - let ParsedGeneralJestFnCall { kind, members, name } = jest_fn_call; - if !matches!( - kind, - JestFnKind::General(JestGeneralFnKind::Describe | JestGeneralFnKind::Test) - ) { - return; + fn run_once(&self, ctx: &LintContext) { + for node in &collect_possible_jest_call_node(ctx) { + run(node, ctx); } + } +} - if name.starts_with('f') { - ctx.diagnostic_with_fix(NoFocusedTestsDiagnostic(call_expr.span), || { - let start = call_expr.span.start; - Fix::delete(Span { start, end: start + 1 }) - }); +fn run<'a>(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_general_jest_fn_call_new(call_expr, possible_jest_node, ctx) + else { + return; + }; + let ParsedGeneralJestFnCallNew { kind, members, name } = jest_fn_call; + if !matches!(kind, JestFnKind::General(JestGeneralFnKind::Describe | JestGeneralFnKind::Test)) { + return; + } - return; - } + if name.starts_with('f') { + ctx.diagnostic_with_fix(NoFocusedTestsDiagnostic(call_expr.span), || { + let start = call_expr.span.start; + Fix::delete(Span { start, end: start + 1 }) + }); - let only_node = members.iter().find(|member| member.is_name_equal("only")); - if let Some(only_node) = only_node { - ctx.diagnostic_with_fix(NoFocusedTestsDiagnostic(call_expr.span), || { - let span = only_node.span; - let start = span.start - 1; - let end = if matches!(only_node.element, MemberExpressionElement::IdentName(_)) { - span.end - } else { - span.end + 1 - }; - Fix::delete(Span { start, end }) - }); - } + return; + } + + let only_node = members.iter().find(|member| member.is_name_equal("only")); + if let Some(only_node) = only_node { + ctx.diagnostic_with_fix(NoFocusedTestsDiagnostic(call_expr.span), || { + let span = only_node.span; + let start = span.start - 1; + let end = if matches!(only_node.element, MemberExpressionElementNew::IdentName(_)) { + span.end + } else { + span.end + 1 + }; + Fix::delete(Span { start, end }) + }); } } diff --git a/crates/oxc_linter/src/rules/jest/no_test_prefixes.rs b/crates/oxc_linter/src/rules/jest/no_test_prefixes.rs index b55b89a1f..fce617bf8 100644 --- a/crates/oxc_linter/src/rules/jest/no_test_prefixes.rs +++ b/crates/oxc_linter/src/rules/jest/no_test_prefixes.rs @@ -11,10 +11,9 @@ use crate::{ fixer::Fix, rule::Rule, utils::{ - parse_general_jest_fn_call, JestGeneralFnKind, KnownMemberExpressionProperty, - ParsedGeneralJestFnCall, + collect_possible_jest_call_node, parse_general_jest_fn_call_new, JestGeneralFnKind, + KnownMemberExpressionPropertyNew, ParsedGeneralJestFnCallNew, PossibleJestNode, }, - AstNode, }; #[derive(Debug, Error, Diagnostic)] @@ -51,13 +50,55 @@ declare_oxc_lint!( style ); -fn get_preferred_node_names(jest_fn_call: &ParsedGeneralJestFnCall) -> Atom { - let ParsedGeneralJestFnCall { members, name, .. } = jest_fn_call; +impl Rule for NoTestPrefixes { + fn run_once(&self, ctx: &LintContext) { + for node in &collect_possible_jest_call_node(ctx) { + run(node, ctx); + } + } +} + +fn run<'a>(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_general_jest_fn_call_new(call_expr, possible_jest_node, ctx) + else { + return; + }; + let ParsedGeneralJestFnCallNew { kind, name, .. } = &jest_fn_call; + let Some(kind) = kind.to_general() else { return }; + + if !matches!(kind, JestGeneralFnKind::Describe | JestGeneralFnKind::Test) { + return; + } + + if !name.starts_with('f') && !name.starts_with('x') { + return; + } + + let span = match &call_expr.callee { + Expression::TaggedTemplateExpression(tagged_template_expr) => { + tagged_template_expr.tag.span() + } + Expression::CallExpression(child_call_expr) => child_call_expr.callee.span(), + _ => call_expr.callee.span(), + }; + + let preferred_node_name = get_preferred_node_names(&jest_fn_call); + let preferred_node_name_cloned = preferred_node_name.clone(); + + ctx.diagnostic_with_fix(NoTestPrefixesDiagnostic(preferred_node_name, span), || { + Fix::new(preferred_node_name_cloned.to_string(), span) + }); +} + +fn get_preferred_node_names(jest_fn_call: &ParsedGeneralJestFnCallNew) -> Atom { + let ParsedGeneralJestFnCallNew { members, name, .. } = jest_fn_call; let preferred_modifier = if name.starts_with('f') { "only" } else { "skip" }; let member_names = members .iter() - .filter_map(KnownMemberExpressionProperty::name) + .filter_map(KnownMemberExpressionPropertyNew::name) .collect::>() .join("."); let name_slice = &name[1..]; @@ -69,38 +110,6 @@ fn get_preferred_node_names(jest_fn_call: &ParsedGeneralJestFnCall) -> Atom { } } -impl Rule for NoTestPrefixes { - fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { - let AstKind::CallExpression(call_expr) = node.kind() else { return }; - let Some(jest_fn_call) = parse_general_jest_fn_call(call_expr, node, ctx) else { return }; - let ParsedGeneralJestFnCall { kind, name, .. } = &jest_fn_call; - let Some(kind) = kind.to_general() else { return }; - - if !matches!(kind, JestGeneralFnKind::Describe | JestGeneralFnKind::Test) { - return; - } - - if !name.starts_with('f') && !name.starts_with('x') { - return; - } - - let span = match &call_expr.callee { - Expression::TaggedTemplateExpression(tagged_template_expr) => { - tagged_template_expr.tag.span() - } - Expression::CallExpression(child_call_expr) => child_call_expr.callee.span(), - _ => call_expr.callee.span(), - }; - - let preferred_node_name = get_preferred_node_names(&jest_fn_call); - let preferred_node_name_cloned = preferred_node_name.clone(); - - ctx.diagnostic_with_fix(NoTestPrefixesDiagnostic(preferred_node_name, span), || { - Fix::new(preferred_node_name_cloned.to_string(), span) - }); - } -} - #[test] fn test() { use crate::tester::Tester; diff --git a/crates/oxc_linter/src/rules/jest/valid_describe_callback.rs b/crates/oxc_linter/src/rules/jest/valid_describe_callback.rs index 5a0654ac6..d6e427e6f 100644 --- a/crates/oxc_linter/src/rules/jest/valid_describe_callback.rs +++ b/crates/oxc_linter/src/rules/jest/valid_describe_callback.rs @@ -12,8 +12,10 @@ use oxc_span::{GetSpan, Span}; use crate::{ context::LintContext, rule::Rule, - utils::{parse_general_jest_fn_call, JestFnKind, JestGeneralFnKind}, - AstNode, + utils::{ + collect_possible_jest_call_node, parse_general_jest_fn_call_new, JestFnKind, + JestGeneralFnKind, PossibleJestNode, + }, }; #[derive(Debug, Error, Diagnostic)] @@ -63,73 +65,83 @@ declare_oxc_lint!( ); impl Rule for ValidDescribeCallback { - fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { - let AstKind::CallExpression(call_expr) = node.kind() else { return }; - let Some(jest_fn_call) = parse_general_jest_fn_call(call_expr, node, ctx) else { return }; - if !matches!(jest_fn_call.kind, JestFnKind::General(JestGeneralFnKind::Describe)) { - return; + fn run_once(&self, ctx: &LintContext) { + for node in &collect_possible_jest_call_node(ctx) { + run(node, ctx); } + } +} - if call_expr.arguments.len() == 0 { - diagnostic(ctx, call_expr.span, Message::NameAndCallback); - return; - } +fn run<'a>(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_general_jest_fn_call_new(call_expr, possible_jest_node, ctx) + else { + return; + }; + if !matches!(jest_fn_call.kind, JestFnKind::General(JestGeneralFnKind::Describe)) { + return; + } - if call_expr.arguments.len() == 1 { - // For better error notice, we locate it to arguments[0] - diagnostic(ctx, call_expr.arguments[0].span(), Message::NameAndCallback); - return; - } + if call_expr.arguments.len() == 0 { + diagnostic(ctx, call_expr.span, Message::NameAndCallback); + return; + } - let callback = &call_expr.arguments[1]; - match callback { - Argument::Expression(expr) => match expr { - Expression::FunctionExpression(fn_expr) => { - if fn_expr.r#async { - diagnostic(ctx, fn_expr.span, Message::NoAsyncDescribeCallback); - } - let no_each_fields = - jest_fn_call.members.iter().all(|member| member.is_name_unequal("each")); - if no_each_fields && fn_expr.params.parameters_count() > 0 { - diagnostic(ctx, fn_expr.span, Message::UnexpectedDescribeArgument); - } + if call_expr.arguments.len() == 1 { + // For better error notice, we locate it to arguments[0] + diagnostic(ctx, call_expr.arguments[0].span(), Message::NameAndCallback); + return; + } - let Some(ref body) = fn_expr.body else { + let callback = &call_expr.arguments[1]; + match callback { + Argument::Expression(expr) => match expr { + Expression::FunctionExpression(fn_expr) => { + if fn_expr.r#async { + diagnostic(ctx, fn_expr.span, Message::NoAsyncDescribeCallback); + } + let no_each_fields = + jest_fn_call.members.iter().all(|member| member.is_name_unequal("each")); + if no_each_fields && fn_expr.params.parameters_count() > 0 { + diagnostic(ctx, fn_expr.span, Message::UnexpectedDescribeArgument); + } + + let Some(ref body) = fn_expr.body else { + return; + }; + if let Some(span) = find_first_return_stmt_span(body) { + diagnostic(ctx, span, Message::UnexpectedReturnInDescribe); + } + } + Expression::ArrowExpression(arrow_expr) => { + if arrow_expr.r#async { + diagnostic(ctx, arrow_expr.span, Message::NoAsyncDescribeCallback); + } + let no_each_fields = + jest_fn_call.members.iter().all(|member| member.is_name_unequal("each")); + if no_each_fields && arrow_expr.params.parameters_count() > 0 { + diagnostic(ctx, arrow_expr.span, Message::UnexpectedDescribeArgument); + } + + if arrow_expr.expression && arrow_expr.body.statements.len() > 0 { + let stmt = &arrow_expr.body.statements[0]; + let Statement::ExpressionStatement(expr_stmt) = stmt else { return; }; - if let Some(span) = find_first_return_stmt_span(body) { - diagnostic(ctx, span, Message::UnexpectedReturnInDescribe); + if let Expression::CallExpression(call_expr) = &expr_stmt.expression { + diagnostic(ctx, call_expr.span, Message::UnexpectedReturnInDescribe); } } - Expression::ArrowExpression(arrow_expr) => { - if arrow_expr.r#async { - diagnostic(ctx, arrow_expr.span, Message::NoAsyncDescribeCallback); - } - let no_each_fields = - jest_fn_call.members.iter().all(|member| member.is_name_unequal("each")); - if no_each_fields && arrow_expr.params.parameters_count() > 0 { - diagnostic(ctx, arrow_expr.span, Message::UnexpectedDescribeArgument); - } - if arrow_expr.expression && arrow_expr.body.statements.len() > 0 { - let stmt = &arrow_expr.body.statements[0]; - let Statement::ExpressionStatement(expr_stmt) = stmt else { - return; - }; - if let Expression::CallExpression(call_expr) = &expr_stmt.expression { - diagnostic(ctx, call_expr.span, Message::UnexpectedReturnInDescribe); - } - } - - if let Some(span) = find_first_return_stmt_span(&arrow_expr.body) { - diagnostic(ctx, span, Message::UnexpectedReturnInDescribe); - } + if let Some(span) = find_first_return_stmt_span(&arrow_expr.body) { + diagnostic(ctx, span, Message::UnexpectedReturnInDescribe); } - _ => diagnostic(ctx, expr.span(), Message::SecondArgumentMustBeFunction), - }, - Argument::SpreadElement(spreed_element) => { - diagnostic(ctx, spreed_element.span, Message::SecondArgumentMustBeFunction); } + _ => diagnostic(ctx, expr.span(), Message::SecondArgumentMustBeFunction), + }, + Argument::SpreadElement(spreed_element) => { + diagnostic(ctx, spreed_element.span, Message::SecondArgumentMustBeFunction); } } } diff --git a/crates/oxc_linter/src/rules/jest/valid_title.rs b/crates/oxc_linter/src/rules/jest/valid_title.rs index c02e4f5f4..a1724afe2 100644 --- a/crates/oxc_linter/src/rules/jest/valid_title.rs +++ b/crates/oxc_linter/src/rules/jest/valid_title.rs @@ -15,8 +15,10 @@ use regex::Regex; use crate::{ context::LintContext, rule::Rule, - utils::{parse_general_jest_fn_call, JestFnKind, JestGeneralFnKind}, - AstNode, + utils::{ + collect_possible_jest_call_node, parse_general_jest_fn_call_new, JestFnKind, + JestGeneralFnKind, PossibleJestNode, + }, }; #[derive(Debug, Error, Diagnostic)] @@ -94,11 +96,23 @@ impl Rule for ValidTitle { must_match_patterns, } } - fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + + fn run_once(&self, ctx: &LintContext) { + for node in &collect_possible_jest_call_node(ctx) { + self.run(node, ctx); + } + } +} + +impl ValidTitle { + fn run<'a>(&self, possible_jest_fn_node: &PossibleJestNode<'a, '_>, ctx: &LintContext<'a>) { + let node = possible_jest_fn_node.node; let AstKind::CallExpression(call_expr) = node.kind() else { return; }; - let Some(jest_fn_call) = parse_general_jest_fn_call(call_expr, node, ctx) else { + let Some(jest_fn_call) = + parse_general_jest_fn_call_new(call_expr, possible_jest_fn_node, ctx) + else { return; }; diff --git a/crates/oxc_linter/src/snapshots/no_done_callback.snap b/crates/oxc_linter/src/snapshots/no_done_callback.snap index b8b26fb85..aeb15d003 100644 --- a/crates/oxc_linter/src/snapshots/no_done_callback.snap +++ b/crates/oxc_linter/src/snapshots/no_done_callback.snap @@ -2,233 +2,233 @@ source: crates/oxc_linter/src/tester.rs expression: no_done_callback --- - ⚠ eslint-plugin-jest(no-done-callback): "Function parameter(s) use the `done` argument" + ⚠ eslint-plugin-jest(no-done-callback): Function parameter(s) use the `done` argument ╭─[no_done_callback.tsx:1:1] 1 │ test('something', (...args) => {args[0]();}) · ─────── ╰──── - help: "Return a Promise instead of relying on callback parameter" + help: Return a Promise instead of relying on callback parameter - ⚠ eslint-plugin-jest(no-done-callback): "Function parameter(s) use the `done` argument" + ⚠ eslint-plugin-jest(no-done-callback): Function parameter(s) use the `done` argument ╭─[no_done_callback.tsx:1:1] 1 │ test('something', done => {done();}) · ──── ╰──── - help: "Return a Promise instead of relying on callback parameter" + help: Return a Promise instead of relying on callback parameter - ⚠ eslint-plugin-jest(no-done-callback): "Function parameter(s) use the `done` argument" + ⚠ eslint-plugin-jest(no-done-callback): Function parameter(s) use the `done` argument ╭─[no_done_callback.tsx:1:1] 1 │ test('something', (done,) => {done();}) · ──── ╰──── - help: "Return a Promise instead of relying on callback parameter" + help: Return a Promise instead of relying on callback parameter - ⚠ eslint-plugin-jest(no-done-callback): "Function parameter(s) use the `done` argument" + ⚠ eslint-plugin-jest(no-done-callback): Function parameter(s) use the `done` argument ╭─[no_done_callback.tsx:1:1] 1 │ test('something', finished => {finished();}) · ──────── ╰──── - help: "Return a Promise instead of relying on callback parameter" + help: Return a Promise instead of relying on callback parameter - ⚠ eslint-plugin-jest(no-done-callback): "Function parameter(s) use the `done` argument" + ⚠ eslint-plugin-jest(no-done-callback): Function parameter(s) use the `done` argument ╭─[no_done_callback.tsx:1:1] 1 │ test('something', (done) => {done();}) · ──── ╰──── - help: "Return a Promise instead of relying on callback parameter" + help: Return a Promise instead of relying on callback parameter - ⚠ eslint-plugin-jest(no-done-callback): "Function parameter(s) use the `done` argument" + ⚠ eslint-plugin-jest(no-done-callback): Function parameter(s) use the `done` argument ╭─[no_done_callback.tsx:1:1] 1 │ test('something', done => done()) · ──── ╰──── - help: "Return a Promise instead of relying on callback parameter" + help: Return a Promise instead of relying on callback parameter - ⚠ eslint-plugin-jest(no-done-callback): "Function parameter(s) use the `done` argument" + ⚠ eslint-plugin-jest(no-done-callback): Function parameter(s) use the `done` argument ╭─[no_done_callback.tsx:1:1] 1 │ test('something', (done) => done()) · ──── ╰──── - help: "Return a Promise instead of relying on callback parameter" + help: Return a Promise instead of relying on callback parameter - ⚠ eslint-plugin-jest(no-done-callback): "Function parameter(s) use the `done` argument" + ⚠ eslint-plugin-jest(no-done-callback): Function parameter(s) use the `done` argument ╭─[no_done_callback.tsx:1:1] 1 │ test('something', function(done) {done();}) · ──── ╰──── - help: "Return a Promise instead of relying on callback parameter" + help: Return a Promise instead of relying on callback parameter - ⚠ eslint-plugin-jest(no-done-callback): "Function parameter(s) use the `done` argument" + ⚠ eslint-plugin-jest(no-done-callback): Function parameter(s) use the `done` argument ╭─[no_done_callback.tsx:1:1] 1 │ test('something', function (done) {done();}) · ──── ╰──── - help: "Return a Promise instead of relying on callback parameter" + help: Return a Promise instead of relying on callback parameter - ⚠ eslint-plugin-jest(no-done-callback): "Function parameter(s) use the `done` argument" + ⚠ eslint-plugin-jest(no-done-callback): Function parameter(s) use the `done` argument ╭─[no_done_callback.tsx:1:1] 1 │ test('something', async done => {done();}) · ──── ╰──── - help: "Use await instead of callback in async functions" + help: Use await instead of callback in async functions - ⚠ eslint-plugin-jest(no-done-callback): "Function parameter(s) use the `done` argument" + ⚠ eslint-plugin-jest(no-done-callback): Function parameter(s) use the `done` argument ╭─[no_done_callback.tsx:1:1] 1 │ test('something', async done => done()) · ──── ╰──── - help: "Use await instead of callback in async functions" + help: Use await instead of callback in async functions - ⚠ eslint-plugin-jest(no-done-callback): "Function parameter(s) use the `done` argument" + ⚠ eslint-plugin-jest(no-done-callback): Function parameter(s) use the `done` argument ╭─[no_done_callback.tsx:1:1] 1 │ test('something', async function (done) {done();}) · ──── ╰──── - help: "Use await instead of callback in async functions" + help: Use await instead of callback in async functions - ⚠ eslint-plugin-jest(no-done-callback): "Function parameter(s) use the `done` argument" + ⚠ eslint-plugin-jest(no-done-callback): Function parameter(s) use the `done` argument ╭─[no_done_callback.tsx:1:1] 1 │ 2 │ test('my test', async (done) => { · ──── 3 │ await myAsyncTask(); ╰──── - help: "Use await instead of callback in async functions" + help: Use await instead of callback in async functions - ⚠ eslint-plugin-jest(no-done-callback): "Function parameter(s) use the `done` argument" + ⚠ eslint-plugin-jest(no-done-callback): Function parameter(s) use the `done` argument ╭─[no_done_callback.tsx:1:1] 1 │ 2 │ test('something', (done) => { · ──── 3 │ done(); ╰──── - help: "Return a Promise instead of relying on callback parameter" + help: Return a Promise instead of relying on callback parameter - ⚠ eslint-plugin-jest(no-done-callback): "Function parameter(s) use the `done` argument" + ⚠ eslint-plugin-jest(no-done-callback): Function parameter(s) use the `done` argument ╭─[no_done_callback.tsx:1:1] 1 │ afterEach((...args) => {args[0]();}) · ─────── ╰──── - help: "Return a Promise instead of relying on callback parameter" + help: Return a Promise instead of relying on callback parameter - ⚠ eslint-plugin-jest(no-done-callback): "Function parameter(s) use the `done` argument" + ⚠ eslint-plugin-jest(no-done-callback): Function parameter(s) use the `done` argument ╭─[no_done_callback.tsx:1:1] 1 │ beforeAll(done => {done();}) · ──── ╰──── - help: "Return a Promise instead of relying on callback parameter" + help: Return a Promise instead of relying on callback parameter - ⚠ eslint-plugin-jest(no-done-callback): "Function parameter(s) use the `done` argument" + ⚠ eslint-plugin-jest(no-done-callback): Function parameter(s) use the `done` argument ╭─[no_done_callback.tsx:1:1] 1 │ beforeAll(finished => {finished();}) · ──────── ╰──── - help: "Return a Promise instead of relying on callback parameter" + help: Return a Promise instead of relying on callback parameter - ⚠ eslint-plugin-jest(no-done-callback): "Function parameter(s) use the `done` argument" + ⚠ eslint-plugin-jest(no-done-callback): Function parameter(s) use the `done` argument ╭─[no_done_callback.tsx:1:1] 1 │ beforeEach((done) => {done();}) · ──── ╰──── - help: "Return a Promise instead of relying on callback parameter" + help: Return a Promise instead of relying on callback parameter - ⚠ eslint-plugin-jest(no-done-callback): "Function parameter(s) use the `done` argument" + ⚠ eslint-plugin-jest(no-done-callback): Function parameter(s) use the `done` argument ╭─[no_done_callback.tsx:1:1] 1 │ afterAll(done => done()) · ──── ╰──── - help: "Return a Promise instead of relying on callback parameter" + help: Return a Promise instead of relying on callback parameter - ⚠ eslint-plugin-jest(no-done-callback): "Function parameter(s) use the `done` argument" + ⚠ eslint-plugin-jest(no-done-callback): Function parameter(s) use the `done` argument ╭─[no_done_callback.tsx:1:1] 1 │ afterEach((done) => done()) · ──── ╰──── - help: "Return a Promise instead of relying on callback parameter" + help: Return a Promise instead of relying on callback parameter - ⚠ eslint-plugin-jest(no-done-callback): "Function parameter(s) use the `done` argument" + ⚠ eslint-plugin-jest(no-done-callback): Function parameter(s) use the `done` argument ╭─[no_done_callback.tsx:1:1] 1 │ beforeAll(function(done) {done();}) · ──── ╰──── - help: "Return a Promise instead of relying on callback parameter" + help: Return a Promise instead of relying on callback parameter - ⚠ eslint-plugin-jest(no-done-callback): "Function parameter(s) use the `done` argument" + ⚠ eslint-plugin-jest(no-done-callback): Function parameter(s) use the `done` argument ╭─[no_done_callback.tsx:1:1] 1 │ afterEach(function (done) {done();}) · ──── ╰──── - help: "Return a Promise instead of relying on callback parameter" + help: Return a Promise instead of relying on callback parameter - ⚠ eslint-plugin-jest(no-done-callback): "Function parameter(s) use the `done` argument" + ⚠ eslint-plugin-jest(no-done-callback): Function parameter(s) use the `done` argument ╭─[no_done_callback.tsx:1:1] 1 │ beforeAll(async done => {done();}) · ──── ╰──── - help: "Use await instead of callback in async functions" + help: Use await instead of callback in async functions - ⚠ eslint-plugin-jest(no-done-callback): "Function parameter(s) use the `done` argument" + ⚠ eslint-plugin-jest(no-done-callback): Function parameter(s) use the `done` argument ╭─[no_done_callback.tsx:1:1] 1 │ beforeAll(async done => done()) · ──── ╰──── - help: "Use await instead of callback in async functions" + help: Use await instead of callback in async functions - ⚠ eslint-plugin-jest(no-done-callback): "Function parameter(s) use the `done` argument" + ⚠ eslint-plugin-jest(no-done-callback): Function parameter(s) use the `done` argument ╭─[no_done_callback.tsx:1:1] 1 │ beforeAll(async function (done) {done();}) · ──── ╰──── - help: "Use await instead of callback in async functions" + help: Use await instead of callback in async functions - ⚠ eslint-plugin-jest(no-done-callback): "Function parameter(s) use the `done` argument" + ⚠ eslint-plugin-jest(no-done-callback): Function parameter(s) use the `done` argument ╭─[no_done_callback.tsx:1:1] 1 │ 2 │ afterAll(async (done) => { · ──── 3 │ await myAsyncTask(); ╰──── - help: "Use await instead of callback in async functions" + help: Use await instead of callback in async functions - ⚠ eslint-plugin-jest(no-done-callback): "Function parameter(s) use the `done` argument" + ⚠ eslint-plugin-jest(no-done-callback): Function parameter(s) use the `done` argument ╭─[no_done_callback.tsx:1:1] 1 │ 2 │ beforeEach((done) => { · ──── 3 │ done(); ╰──── - help: "Return a Promise instead of relying on callback parameter" + help: Return a Promise instead of relying on callback parameter - ⚠ eslint-plugin-jest(no-done-callback): "Function parameter(s) use the `done` argument" + ⚠ eslint-plugin-jest(no-done-callback): Function parameter(s) use the `done` argument ╭─[no_done_callback.tsx:3:1] 3 │ 4 │ beforeEach((done) => { · ──── 5 │ done(); ╰──── - help: "Return a Promise instead of relying on callback parameter" + help: Return a Promise instead of relying on callback parameter - ⚠ eslint-plugin-jest(no-done-callback): "Function parameter(s) use the `done` argument" + ⚠ eslint-plugin-jest(no-done-callback): Function parameter(s) use the `done` argument ╭─[no_done_callback.tsx:3:1] 3 │ 4 │ atTheStartOfEachTest((done) => { · ──── 5 │ done(); ╰──── - help: "Return a Promise instead of relying on callback parameter" + help: Return a Promise instead of relying on callback parameter - ⚠ eslint-plugin-jest(no-done-callback): "Function parameter(s) use the `done` argument" + ⚠ eslint-plugin-jest(no-done-callback): Function parameter(s) use the `done` argument ╭─[no_done_callback.tsx:1:1] 1 │ test.each``('something', ({ a, b }, done) => { done(); }) · ──────── ╰──── - help: "Return a Promise instead of relying on callback parameter" + help: Return a Promise instead of relying on callback parameter - ⚠ eslint-plugin-jest(no-done-callback): "Function parameter(s) use the `done` argument" + ⚠ eslint-plugin-jest(no-done-callback): Function parameter(s) use the `done` argument ╭─[no_done_callback.tsx:1:1] 1 │ it.each``('something', ({ a, b }, done) => { done(); }) · ──────── ╰──── - help: "Return a Promise instead of relying on callback parameter" + help: Return a Promise instead of relying on callback parameter diff --git a/crates/oxc_linter/src/snapshots/valid_describe_callback.snap b/crates/oxc_linter/src/snapshots/valid_describe_callback.snap index f6d018eb9..d9152bd46 100644 --- a/crates/oxc_linter/src/snapshots/valid_describe_callback.snap +++ b/crates/oxc_linter/src/snapshots/valid_describe_callback.snap @@ -162,6 +162,18 @@ expression: valid_describe_callback ╰──── help: "Remove return statement in your describe callback" + ⚠ eslint-plugin-jest(valid-describe-callback): "Unexpected return statement in describe callback" + ╭─[valid_describe_callback.tsx:8:1] + 8 │ describe('nested', () => { + 9 │ ╭─▶ return Promise.resolve().then(() => { + 10 │ │ it('breaks', () => { + 11 │ │ throw new Error('Fail') + 12 │ │ }) + 13 │ ╰─▶ }) + 14 │ }) + ╰──── + help: "Remove return statement in your describe callback" + ⚠ eslint-plugin-jest(valid-describe-callback): "Unexpected return statement in describe callback" ╭─[valid_describe_callback.tsx:2:1] 2 │ describe('foo', () => { @@ -175,14 +187,14 @@ expression: valid_describe_callback help: "Remove return statement in your describe callback" ⚠ eslint-plugin-jest(valid-describe-callback): "Unexpected return statement in describe callback" - ╭─[valid_describe_callback.tsx:8:1] - 8 │ describe('nested', () => { - 9 │ ╭─▶ return Promise.resolve().then(() => { - 10 │ │ it('breaks', () => { - 11 │ │ throw new Error('Fail') - 12 │ │ }) - 13 │ ╰─▶ }) - 14 │ }) + ╭─[valid_describe_callback.tsx:5:1] + 5 │ describe('nested', () => { + 6 │ ╭─▶ return Promise.resolve().then(() => { + 7 │ │ it('breaks', () => { + 8 │ │ throw new Error('Fail') + 9 │ │ }) + 10 │ ╰─▶ }) + 11 │ }) ╰──── help: "Remove return statement in your describe callback" @@ -204,18 +216,6 @@ expression: valid_describe_callback ╰──── help: "Remove `async` keyword" - ⚠ eslint-plugin-jest(valid-describe-callback): "Unexpected return statement in describe callback" - ╭─[valid_describe_callback.tsx:5:1] - 5 │ describe('nested', () => { - 6 │ ╭─▶ return Promise.resolve().then(() => { - 7 │ │ it('breaks', () => { - 8 │ │ throw new Error('Fail') - 9 │ │ }) - 10 │ ╰─▶ }) - 11 │ }) - ╰──── - help: "Remove return statement in your describe callback" - ⚠ eslint-plugin-jest(valid-describe-callback): "Unexpected return statement in describe callback" ╭─[valid_describe_callback.tsx:1:1] 1 │ describe('foo', () => test('bar', () => {})) diff --git a/crates/oxc_linter/src/utils/jest.rs b/crates/oxc_linter/src/utils/jest.rs index 16593ab1c..41ffdc0a9 100644 --- a/crates/oxc_linter/src/utils/jest.rs +++ b/crates/oxc_linter/src/utils/jest.rs @@ -26,6 +26,8 @@ mod parse_jest_fn_new; pub use crate::utils::jest::parse_jest_fn_new::{ parse_jest_fn_call as parse_jest_fn_call_new, KnownMemberExpressionParentKind as KnownMemberExpressionParentKindNew, + KnownMemberExpressionProperty as KnownMemberExpressionPropertyNew, + MemberExpressionElement as MemberExpressionElementNew, ParsedExpectFnCall as ParsedExpectFnCallNew, ParsedGeneralJestFnCall as ParsedGeneralJestFnCallNew, ParsedJestFnCall as ParsedJestFnCallNew, }; @@ -117,6 +119,7 @@ pub fn is_type_of_jest_fn_call<'a>( false } +#[allow(unused)] pub fn parse_general_jest_fn_call<'a>( call_expr: &'a CallExpression<'a>, node: &AstNode<'a>, @@ -201,13 +204,13 @@ pub fn collect_possible_jest_call_node<'a, 'b>( vec![] }; - // The longest length of Jest chains is 4, e.g.`expect(1).not.resolved.toBe()`. - // We take 4 ancestors of node and collect all Call Expression. + // The longest length of Jest chains is 4, and it may be a TaggedTemplateExpression, e.g.`it.concurrent.only.each``()`. + // We take 5 ancestors of node and collect all Call Expression. // The invalid Jest Call Expression will be bypassed in `parse_jest_fn_call` reference_id_with_original_list.iter().fold(vec![], |mut acc, id_with_original| { let (reference_id, original) = id_with_original; let mut id = ctx.symbols().get_reference(*reference_id).node_id(); - for _ in 0..4 { + for _ in 0..5 { let parent = ctx.nodes().parent_node(id); if let Some(parent) = parent { let parent_kind = parent.kind();