diff --git a/crates/oxc_linter/src/rules/jest/no_alias_methods.rs b/crates/oxc_linter/src/rules/jest/no_alias_methods.rs index bf03649b5..d8859108f 100644 --- a/crates/oxc_linter/src/rules/jest/no_alias_methods.rs +++ b/crates/oxc_linter/src/rules/jest/no_alias_methods.rs @@ -60,7 +60,7 @@ impl Rule for NoAliasMethods { 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_expect_jest_fn_call(call_expr, node, ctx) { + if let Some(jest_fn_call) = parse_expect_jest_fn_call(call_expr, possible_jest_node, ctx) { let parsed_expect_call = jest_fn_call; let Some(matcher) = parsed_expect_call.matcher() else { return; diff --git a/crates/oxc_linter/src/rules/jest/no_interpolation_in_snapshots.rs b/crates/oxc_linter/src/rules/jest/no_interpolation_in_snapshots.rs index 4745c8599..c16721a78 100644 --- a/crates/oxc_linter/src/rules/jest/no_interpolation_in_snapshots.rs +++ b/crates/oxc_linter/src/rules/jest/no_interpolation_in_snapshots.rs @@ -9,7 +9,11 @@ use oxc_diagnostics::{ use oxc_macros::declare_oxc_lint; use oxc_span::Span; -use crate::{context::LintContext, rule::Rule, utils::parse_expect_jest_fn_call, AstNode}; +use crate::{ + context::LintContext, + rule::Rule, + utils::{collect_possible_jest_call_node, parse_expect_jest_fn_call, PossibleJestNode}, +}; #[derive(Debug, Error, Diagnostic)] #[error("eslint-plugin-jest(no-interpolation-in-snapshots): Do not use string interpolation inside of snapshots")] @@ -56,26 +60,35 @@ declare_oxc_lint!( ); impl Rule for NoInterpolationInSnapshots { - 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_expect_jest_fn_call(call_expr, node, ctx) else { return }; - let Some(matcher) = jest_fn_call.matcher() else { - return; - }; - - if matcher.is_name_unequal("toMatchInlineSnapshot") - && matcher.is_name_unequal("toThrowErrorMatchingInlineSnapshot") - { - return; + fn run_once(&self, ctx: &LintContext) { + for possible_jest_node in &collect_possible_jest_call_node(ctx) { + run(possible_jest_node, ctx); } + } +} - // Check all since the optional 'propertyMatchers' argument might be present - // `.toMatchInlineSnapshot(propertyMatchers?, inlineSnapshot)` - for arg in jest_fn_call.args { - if let Argument::Expression(Expression::TemplateLiteral(template_lit)) = arg { - if !template_lit.expressions.is_empty() { - ctx.diagnostic(NoInterpolationInSnapshotsDiagnostic(template_lit.span)); - } +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_expect_jest_fn_call(call_expr, possible_jest_node, ctx) else { + return; + }; + let Some(matcher) = jest_fn_call.matcher() else { + return; + }; + + if matcher.is_name_unequal("toMatchInlineSnapshot") + && matcher.is_name_unequal("toThrowErrorMatchingInlineSnapshot") + { + return; + } + + // Check all since the optional 'propertyMatchers' argument might be present + // `.toMatchInlineSnapshot(propertyMatchers?, inlineSnapshot)` + for arg in jest_fn_call.args { + if let Argument::Expression(Expression::TemplateLiteral(template_lit)) = arg { + if !template_lit.expressions.is_empty() { + ctx.diagnostic(NoInterpolationInSnapshotsDiagnostic(template_lit.span)); } } } diff --git a/crates/oxc_linter/src/rules/jest/no_standalone_expect.rs b/crates/oxc_linter/src/rules/jest/no_standalone_expect.rs index 20a5443c3..89cd4651d 100644 --- a/crates/oxc_linter/src/rules/jest/no_standalone_expect.rs +++ b/crates/oxc_linter/src/rules/jest/no_standalone_expect.rs @@ -13,9 +13,9 @@ use crate::{ context::LintContext, rule::Rule, utils::{ - collect_possible_jest_call_node, get_node_name, parse_expect_jest_fn_call_new, + collect_possible_jest_call_node, get_node_name, parse_expect_jest_fn_call, parse_general_jest_fn_call, JestFnKind, JestGeneralFnKind, - KnownMemberExpressionParentKindNew, ParsedExpectFnCallNew, PossibleJestNode, + KnownMemberExpressionParentKindNew, ParsedExpectFnCall, PossibleJestNode, }, AstNode, }; @@ -88,11 +88,11 @@ impl NoStandaloneExpect { let AstKind::CallExpression(call_expr) = node.kind() else { return; }; - let Some(jest_fn_call) = parse_expect_jest_fn_call_new(call_expr, possible_jest_node, ctx) + let Some(jest_fn_call) = parse_expect_jest_fn_call(call_expr, possible_jest_node, ctx) else { return; }; - let ParsedExpectFnCallNew { head, members, .. } = jest_fn_call; + let ParsedExpectFnCall { head, members, .. } = jest_fn_call; // only report `expect.hasAssertions` & `expect.assertions` member calls if members.len() == 1 diff --git a/crates/oxc_linter/src/rules/jest/valid_expect.rs b/crates/oxc_linter/src/rules/jest/valid_expect.rs index 353ce10ef..552df3fec 100644 --- a/crates/oxc_linter/src/rules/jest/valid_expect.rs +++ b/crates/oxc_linter/src/rules/jest/valid_expect.rs @@ -12,7 +12,9 @@ use oxc_span::{Atom, GetSpan, Span}; use crate::{ context::LintContext, rule::Rule, - utils::{parse_expect_jest_fn_call, ExpectError}, + utils::{ + collect_possible_jest_call_node, parse_expect_jest_fn_call, ExpectError, PossibleJestNode, + }, AstNode, }; @@ -88,9 +90,21 @@ impl Rule for ValidExpect { Self { async_matchers, min_args, max_args, always_await } } - fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + fn run_once(&self, ctx: &LintContext) { + for possible_jest_node in &collect_possible_jest_call_node(ctx) { + self.run(possible_jest_node, ctx); + } + } +} + +impl ValidExpect { + fn run<'a>(&self, 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_expect_jest_fn_call(call_expr, node, ctx) else { return }; + let Some(jest_fn_call) = parse_expect_jest_fn_call(call_expr, possible_jest_node, ctx) + else { + return; + }; let reporting_span = jest_fn_call.expect_error.map_or(call_expr.span, |_| { find_top_most_member_expression(node, ctx).map_or(call_expr.span, GetSpan::span) }); diff --git a/crates/oxc_linter/src/utils/jest.rs b/crates/oxc_linter/src/utils/jest.rs index ecd43b936..a538150b8 100644 --- a/crates/oxc_linter/src/utils/jest.rs +++ b/crates/oxc_linter/src/utils/jest.rs @@ -15,20 +15,18 @@ use crate::LintContext; mod parse_jest_fn; -use crate::utils::jest::parse_jest_fn::ParsedJestFnCall; pub use crate::utils::jest::parse_jest_fn::{ - parse_jest_fn_call, ExpectError, KnownMemberExpressionParentKind, - KnownMemberExpressionProperty, MemberExpressionElement, ParsedExpectFnCall, + parse_jest_fn_call, KnownMemberExpressionParentKind, KnownMemberExpressionProperty, + MemberExpressionElement, }; mod parse_jest_fn_new; pub use crate::utils::jest::parse_jest_fn_new::{ - parse_jest_fn_call as parse_jest_fn_call_new, + parse_jest_fn_call as parse_jest_fn_call_new, ExpectError, KnownMemberExpressionParentKind as KnownMemberExpressionParentKindNew, KnownMemberExpressionProperty as KnownMemberExpressionPropertyNew, - MemberExpressionElement as MemberExpressionElementNew, - ParsedExpectFnCall as ParsedExpectFnCallNew, ParsedGeneralJestFnCall, - ParsedJestFnCall as ParsedJestFnCallNew, + MemberExpressionElement as MemberExpressionElementNew, ParsedExpectFnCall, + ParsedGeneralJestFnCall, ParsedJestFnCall as ParsedJestFnCallNew, }; const JEST_METHOD_NAMES: phf::Set<&'static str> = phf_set![ @@ -132,23 +130,10 @@ pub fn parse_general_jest_fn_call<'a>( } pub fn parse_expect_jest_fn_call<'a>( - call_expr: &'a CallExpression<'a>, - node: &AstNode<'a>, - ctx: &LintContext<'a>, -) -> Option> { - let jest_fn_call = parse_jest_fn_call(call_expr, node, ctx)?; - - if let ParsedJestFnCall::ExpectFnCall(jest_fn_call) = jest_fn_call { - return Some(jest_fn_call); - } - None -} - -pub fn parse_expect_jest_fn_call_new<'a>( call_expr: &'a CallExpression<'a>, possible_jest_node: &PossibleJestNode<'a, '_>, ctx: &LintContext<'a>, -) -> Option> { +) -> Option> { let jest_fn_call = parse_jest_fn_call_new(call_expr, possible_jest_node, ctx)?; if let ParsedJestFnCallNew::ExpectFnCall(jest_fn_call) = jest_fn_call { @@ -188,13 +173,11 @@ pub fn collect_possible_jest_call_node<'a, 'b>( vec![] }; - // 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` + // get the longest valid chain of Jest Call Expression 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..5 { + loop { let parent = ctx.nodes().parent_node(id); if let Some(parent) = parent { let parent_kind = parent.kind(); diff --git a/crates/oxc_linter/src/utils/jest/parse_jest_fn.rs b/crates/oxc_linter/src/utils/jest/parse_jest_fn.rs index 0c2f57adb..af96684a4 100644 --- a/crates/oxc_linter/src/utils/jest/parse_jest_fn.rs +++ b/crates/oxc_linter/src/utils/jest/parse_jest_fn.rs @@ -371,16 +371,6 @@ pub struct ParsedExpectFnCall<'a> { pub expect_error: Option, } -impl<'a> ParsedExpectFnCall<'a> { - pub fn matcher(&self) -> Option<&KnownMemberExpressionProperty<'a>> { - let matcher_index = self.matcher_index?; - self.members.get(matcher_index) - } - pub fn modifiers(&self) -> Vec<&KnownMemberExpressionProperty<'a>> { - self.modifier_indices.iter().filter_map(|i| self.members.get(*i)).collect::>() - } -} - struct ResolvedJestFn<'a> { pub local: &'a Atom, pub original: Option<&'a Atom>, @@ -463,12 +453,6 @@ impl<'a> MemberExpressionElement<'a> { MemberExpression::PrivateFieldExpression(_) => None, } } - pub fn is_string_literal(&self) -> bool { - matches!( - self, - Self::Expression(Expression::StringLiteral(_) | Expression::TemplateLiteral(_)) - ) - } } struct NodeChainParams<'a> {