diff --git a/crates/oxc_linter/src/jest_ast_util.rs b/crates/oxc_linter/src/jest_ast_util.rs index 0152e9f5058b47..e181e977a2c26b 100644 --- a/crates/oxc_linter/src/jest_ast_util.rs +++ b/crates/oxc_linter/src/jest_ast_util.rs @@ -1,51 +1,63 @@ use std::borrow::Cow; -use oxc_ast::ast::{CallExpression, Expression, IdentifierReference}; -use oxc_span::Atom; +use oxc_ast::{ + ast::{CallExpression, Expression, IdentifierName, IdentifierReference, MemberExpression}, + AstKind, +}; +use oxc_semantic::AstNode; +use oxc_span::{Atom, Span}; use crate::context::LintContext; -pub enum JestFnKind { - Hook, - Describe, - Test, - Expect, - Jest, - Unknown, -} +pub fn parse_general_jest_fn_call<'a>( + call_expr: &'a CallExpression<'a>, + node: &AstNode<'a>, + ctx: &LintContext, +) -> Option> { + let jest_fn_call = parse_jest_fn_call(call_expr, node, ctx)?; -impl JestFnKind { - pub fn from(name: &str) -> Self { - match name { - "expect" => Self::Expect, - "jest" => Self::Jest, - "describe" | "fdescribe" | "xdescribe" => Self::Describe, - "fit" | "it" | "test" | "xit" | "xtest" => Self::Test, - "beforeAll" | "beforeEach" | "afterAll" | "afterEach" => Self::Hook, - _ => Self::Unknown, - } + if let ParsedJestFnCall::GeneralJestFnCall(jest_fn_call) = jest_fn_call { + return Some(jest_fn_call); } -} - -pub struct ParsedJestFnCall<'a> { - pub kind: JestFnKind, - pub members: Vec>, - pub raw: Cow<'a, str>, + None } pub fn parse_jest_fn_call<'a>( - call_expr: &'a CallExpression, - ctx: &'a LintContext, + call_expr: &'a CallExpression<'a>, + node: &AstNode<'a>, + ctx: &LintContext, ) -> Option> { let callee = &call_expr.callee; - // if bailed out, we're not a jest function + // If bailed out, we're not jest function let resolved = resolve_to_jest_fn(call_expr, ctx)?; - let chain = get_node_chain(callee); + // only the top level Call expression callee's parent is None, it's not necessary to set it to None, but + // I didn't know how to pass Expression to it. + let chain = get_node_chain(callee, None); + let all_member_expr_except_last = chain + .iter() + .rev() + .skip(1) + .all(|member| matches!(member.parent, Some(Expression::MemberExpression(_)))); + + // Check every link in the chain except the last is a member expression + if !all_member_expr_except_last { + return None; + } + + // Ensure that we're at the "top" of the function call chain otherwise when + // parsing e.g. x().y.z(), we'll incorrectly find & parse "x()" even though + // the full chain is not a valid jest function call chain + if ctx.nodes().parent_node(node.id()).is_some_and(|parent_node| { + matches!(parent_node.kind(), AstKind::CallExpression(_) | AstKind::MemberExpression(_)) + }) { + return None; + } + if let (Some(first), Some(last)) = (chain.first(), chain.last()) { - // if we're an `each()`, ensure we're the outer CallExpression (i.e `.each()()`) - if last == "each" + // If we're an `each()`, ensure we're the outer CallExpression (i.e `.each()()`) + if last.is_name_equal("each") && !matches!( callee, Expression::CallExpression(_) | Expression::TaggedTemplateExpression(_) @@ -54,14 +66,14 @@ pub fn parse_jest_fn_call<'a>( return None; } - if matches!(callee, Expression::TaggedTemplateExpression(_)) && last != "each" { + if matches!(callee, Expression::TaggedTemplateExpression(_)) && last.is_name_unequal("each") + { return None; } - - let kind = JestFnKind::from(first); + let Some(first_name )= first.name() else { return None }; + let kind = JestFnKind::from(&first_name); let mut members = Vec::new(); - let mut iter = chain.into_iter(); - let first = iter.next().expect("first ident name"); + let iter = chain.into_iter().skip(1); let rest = iter; // every member node must have a member expression as their parent @@ -76,17 +88,19 @@ pub fn parse_jest_fn_call<'a>( } else if members.len() == 1 { VALID_JEST_FN_CALL_CHAINS_2 .iter() - .any(|chain| chain[0] == name && chain[1] == members[0]) + .any(|chain| chain[0] == name && members[0].is_name_equal(chain[1])) } else if members.len() == 2 { - VALID_JEST_FN_CALL_CHAINS_3 - .iter() - .any(|chain| chain[0] == name && chain[1] == members[0] && chain[2] == members[1]) + VALID_JEST_FN_CALL_CHAINS_3.iter().any(|chain| { + chain[0] == name + && members[0].is_name_equal(chain[1]) + && members[1].is_name_equal(chain[2]) + }) } else if members.len() == 3 { VALID_JEST_FN_CALL_CHAINS_4.iter().any(|chain| { chain[0] == name - && chain[1] == members[0] - && chain[2] == members[1] - && chain[3] == members[2] + && members[0].is_name_equal(chain[1]) + && members[1].is_name_equal(chain[2]) + && members[2].is_name_equal(chain[3]) }) } else { false @@ -95,22 +109,21 @@ pub fn parse_jest_fn_call<'a>( if !is_valid_jest_call { return None; } - return Some(ParsedJestFnCall { kind, members, raw: first }); + return Some(ParsedJestFnCall::GeneralJestFnCall(ParsedGeneralJestFnCall { + kind, + members, + raw: first_name, + })); } None } -struct ResolvedJestFn<'a> { - pub local: &'a Atom, -} - fn resolve_to_jest_fn<'a>( call_expr: &'a CallExpression, ctx: &'a LintContext, ) -> Option> { let ident = resolve_first_ident(&call_expr.callee)?; - if ctx.semantic().is_reference_to_global_variable(ident) { return Some(ResolvedJestFn { local: &ident.name }); } @@ -128,38 +141,165 @@ fn resolve_first_ident<'a>(expr: &'a Expression) -> Option<&'a IdentifierReferen } } -/// a.b.c -> ["a", "b"] -/// a[`b`] - > ["a", "b"] -/// a["b"] - > ["a", "b"] -/// a[b] - > ["a", "b"] -fn get_node_chain<'a>(expr: &'a Expression) -> Vec> { +#[derive(Clone, Copy)] +pub enum JestFnKind { + Expect, + General(JestGeneralFnKind), + Unknown, +} + +impl JestFnKind { + pub fn from(name: &str) -> Self { + match name { + "expect" => Self::Expect, + "jest" => Self::General(JestGeneralFnKind::Jest), + "describe" | "fdescribe" | "xdescribe" => Self::General(JestGeneralFnKind::Describe), + "fit" | "it" | "test" | "xit" | "xtest" => Self::General(JestGeneralFnKind::Test), + "beforeAll" | "beforeEach" | "afterAll" | "afterEach" => { + Self::General(JestGeneralFnKind::Hook) + } + _ => Self::Unknown, + } + } + + pub fn to_general(self) -> Option { + match self { + Self::General(kind) => Some(kind), + _ => None, + } + } +} + +#[derive(Clone, Copy)] +pub enum JestGeneralFnKind { + Hook, + Describe, + Test, + Jest, +} + +pub enum ParsedJestFnCall<'a> { + GeneralJestFnCall(ParsedGeneralJestFnCall<'a>), + #[allow(unused)] + ExpectFnCall(ParsedExpectFnCall<'a>), +} + +pub struct ParsedGeneralJestFnCall<'a> { + pub kind: JestFnKind, + pub members: Vec>, + pub raw: Cow<'a, str>, +} + +pub struct ParsedExpectFnCall<'a> { + pub kind: JestFnKind, + pub members: Vec>, + pub raw: Cow<'a, str>, + // pub args: Vec<&'a Expression<'a>> + // TODO: add `modifiers`, `matcher` for this struct. +} + +struct ResolvedJestFn<'a> { + pub local: &'a Atom, +} + +pub struct KnownMemberExpressionProperty<'a> { + pub element: MemberExpressionElement<'a>, + pub parent: Option<&'a Expression<'a>>, + pub span: Span, +} + +impl<'a> KnownMemberExpressionProperty<'a> { + pub fn name(&self) -> Option> { + match &self.element { + MemberExpressionElement::Expression(expr) => match expr { + Expression::Identifier(ident) => Some(Cow::Borrowed(ident.name.as_str())), + Expression::StringLiteral(string_literal) => { + Some(Cow::Borrowed(string_literal.value.as_str())) + } + Expression::TemplateLiteral(template_literal) => Some(Cow::Borrowed( + template_literal.quasi().expect("get string content").as_str(), + )), + _ => None, + }, + MemberExpressionElement::IdentName(ident_name) => { + Some(Cow::Borrowed(ident_name.name.as_str())) + } + } + } + pub fn is_name_equal(&self, name: &str) -> bool { + self.name().map_or(false, |n| n == name) + } + pub fn is_name_unequal(&self, name: &str) -> bool { + !self.is_name_equal(name) + } +} + +pub enum MemberExpressionElement<'a> { + Expression(&'a Expression<'a>), + IdentName(&'a IdentifierName), +} + +impl<'a> MemberExpressionElement<'a> { + pub fn from_member_expr( + member_expr: &'a MemberExpression<'a>, + ) -> Option<(Span, MemberExpressionElement<'a>)> { + let Some((span, _)) = member_expr.static_property_info() else { return None }; + match member_expr { + MemberExpression::ComputedMemberExpression(expr) => { + Some((span, Self::Expression(&expr.expression))) + } + MemberExpression::StaticMemberExpression(expr) => { + Some((span, Self::IdentName(&expr.property))) + } + // Jest fn chains don't have private fields, just ignore it. + MemberExpression::PrivateFieldExpression(_) => None, + } + } +} + +/// Port from [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest/blob/a058f22f94774eeea7980ea2d1f24c6808bf3e2c/src/rules/utils/parseJestFnCall.ts#L36-L51) +fn get_node_chain<'a>( + expr: &'a Expression<'a>, + parent: Option<&'a Expression<'a>>, +) -> Vec> { let mut chain = Vec::new(); + match expr { Expression::MemberExpression(member_expr) => { - chain.extend(get_node_chain(member_expr.object())); - if let Some(name) = member_expr.static_property_name() { - chain.push(Cow::Borrowed(name)); + chain.extend(get_node_chain(member_expr.object(), Some(expr))); + if let Some((span, element)) = MemberExpressionElement::from_member_expr(member_expr) { + chain.push(KnownMemberExpressionProperty { element, parent: Some(expr), span }); } } Expression::Identifier(ident) => { - chain.push(Cow::Borrowed(ident.name.as_str())); + chain.push(KnownMemberExpressionProperty { + element: MemberExpressionElement::Expression(expr), + parent, + span: ident.span, + }); } Expression::CallExpression(call_expr) => { - let sub_chain = get_node_chain(&call_expr.callee); + let sub_chain = get_node_chain(&call_expr.callee, Some(expr)); chain.extend(sub_chain); } Expression::TaggedTemplateExpression(tagged_expr) => { - let sub_chain = get_node_chain(&tagged_expr.tag); + let sub_chain = get_node_chain(&tagged_expr.tag, Some(expr)); chain.extend(sub_chain); } Expression::StringLiteral(string_literal) => { - chain.push(Cow::Borrowed(string_literal.value.as_str())); + chain.push(KnownMemberExpressionProperty { + element: MemberExpressionElement::Expression(expr), + parent, + span: string_literal.span, + }); } Expression::TemplateLiteral(template_literal) => { if template_literal.expressions.is_empty() && template_literal.quasis.len() == 1 { - chain.push(Cow::Borrowed( - template_literal.quasi().expect("get string content").as_str(), - )); + chain.push(KnownMemberExpressionProperty { + element: MemberExpressionElement::Expression(expr), + parent, + span: template_literal.span, + }); } } _ => {} diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 5b7521b7599764..abdd4a7afd016a 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -70,6 +70,7 @@ oxc_macros::declare_all_lint_rules! { typescript::no_var_requires, jest::no_disabled_tests, jest::no_test_prefixes, + jest::no_focused_tests, } #[cfg(test)] diff --git a/crates/oxc_linter/src/rules/jest/no_disabled_tests.rs b/crates/oxc_linter/src/rules/jest/no_disabled_tests.rs index 4bdfbf6a5da738..756ef0328c8412 100644 --- a/crates/oxc_linter/src/rules/jest/no_disabled_tests.rs +++ b/crates/oxc_linter/src/rules/jest/no_disabled_tests.rs @@ -8,7 +8,9 @@ use oxc_span::Span; use crate::{ context::LintContext, - jest_ast_util::{parse_jest_fn_call, JestFnKind, ParsedJestFnCall}, + jest_ast_util::{ + parse_general_jest_fn_call, JestFnKind, JestGeneralFnKind, ParsedGeneralJestFnCall, + }, rule::Rule, AstNode, }; @@ -83,12 +85,16 @@ impl Message { impl Rule for NoDisabledTests { 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_jest_fn_call(call_expr, ctx) { - let ParsedJestFnCall { kind, members, raw } = jest_fn_call; + if let Some(jest_fn_call) = parse_general_jest_fn_call(call_expr, node, ctx) { + let ParsedGeneralJestFnCall { kind, members, raw } = jest_fn_call; // `test('foo')` - if matches!(kind, JestFnKind::Test) + let kind = match kind { + JestFnKind::Expect | JestFnKind::Unknown => return, + JestFnKind::General(kind) => kind, + }; + if matches!(kind, JestGeneralFnKind::Test) && call_expr.arguments.len() < 2 - && members.iter().all(|name| name != "todo") + && members.iter().all(|member| member.is_name_unequal("todo")) { let (error, help) = Message::MissingFunction.details(); ctx.diagnostic(NoDisabledTestsDiagnostic(error, help, call_expr.span)); @@ -98,7 +104,7 @@ impl Rule for NoDisabledTests { // the only jest functions that are with "x" are "xdescribe", "xtest", and "xit" // `xdescribe('foo', () => {})` if raw.starts_with('x') { - let (error, help) = if matches!(kind, JestFnKind::Describe) { + let (error, help) = if matches!(kind, JestGeneralFnKind::Describe) { Message::DisabledSuiteWithX.details() } else { Message::DisabledTestWithX.details() @@ -109,8 +115,8 @@ impl Rule for NoDisabledTests { // `it.skip('foo', function () {})'` // `describe.skip('foo', function () {})'` - if members.iter().any(|name| name == "skip") { - let (error, help) = if matches!(kind, JestFnKind::Describe) { + if members.iter().any(|member| member.is_name_equal("skip")) { + let (error, help) = if matches!(kind, JestGeneralFnKind::Describe) { Message::DisabledSuiteWithSkip.details() } else { Message::DisabledTestWithSkip.details() @@ -178,7 +184,6 @@ fn test() { ("describe.skip.each([1, 2, 3])('%s', (a, b) => {});", None), ("xdescribe.each([1, 2, 3])('%s', (a, b) => {});", None), ("describe[`skip`]('foo', function () {})", None), - ("describe[`skip`]('foo', function () {})", None), ("describe['skip']('foo', function () {})", None), ("it.skip('foo', function () {})", None), ("it['skip']('foo', function () {})", None), diff --git a/crates/oxc_linter/src/rules/jest/no_focused_tests.rs b/crates/oxc_linter/src/rules/jest/no_focused_tests.rs new file mode 100644 index 00000000000000..d4152291715188 --- /dev/null +++ b/crates/oxc_linter/src/rules/jest/no_focused_tests.rs @@ -0,0 +1,151 @@ +use oxc_ast::AstKind; +use oxc_diagnostics::{ + miette::{self, Diagnostic}, + thiserror::Error, +}; +use oxc_macros::declare_oxc_lint; +use oxc_span::Span; + +use crate::{ + context::LintContext, + fixer::Fix, + jest_ast_util::{ + parse_general_jest_fn_call, JestFnKind, JestGeneralFnKind, MemberExpressionElement, + ParsedGeneralJestFnCall, + }, + rule::Rule, + AstNode, +}; + +#[derive(Debug, Error, Diagnostic)] +#[error("Unexpected focused test.")] +#[diagnostic(severity(warning), help("Remove focus from test."))] +struct NoFocusedTestsDiagnostic(#[label] pub Span); + +#[derive(Debug, Default, Clone)] +pub struct NoFocusedTests; + +declare_oxc_lint!( + /// ### What it does + /// This rule reminds you to remove `.only` from your tests by raising a warning + /// whenever you are using the exclusivity feature. + /// + /// ### Why is this bad? + /// + /// Jest has a feature that allows you to focus tests by appending `.only` or + /// prepending `f` to a test-suite or a test-case. This feature is really helpful to + /// debug a failing test, so you don’t have to execute all of your tests. After you + /// have fixed your test and before committing the changes you have to remove + /// `.only` to ensure all tests are executed on your build system. + /// + /// ### Example + /// + /// ```javascript + /// describe.only('foo', () => {}); + /// it.only('foo', () => {}); + /// describe['only']('bar', () => {}); + /// it['only']('bar', () => {}); + /// test.only('foo', () => {}); + /// test['only']('bar', () => {}); + /// fdescribe('foo', () => {}); + /// fit('foo', () => {}); + /// fit.each` + /// table + /// `(); + /// ``` + NoFocusedTests, + suspicious +); + +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, raw } = jest_fn_call; + if !matches!( + kind, + JestFnKind::General(JestGeneralFnKind::Describe | JestGeneralFnKind::Test) + ) { + return; + } + + if raw.starts_with('f') { + ctx.diagnostic_with_fix(NoFocusedTestsDiagnostic(call_expr.span), || { + let start = call_expr.span.start; + Fix::delete(Span { start, end: start + 1 }) + }); + + 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, MemberExpressionElement::IdentName(_)) { + span.end + } else { + span.end + 1 + }; + Fix::delete(Span { start, end }) + }); + } + } +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + ("describe()", None), + ("it()", None), + ("describe.skip()", None), + ("it.skip()", None), + ("test()", None), + ("test.skip()", None), + ("var appliedOnly = describe.only; appliedOnly.apply(describe)", None), + ("var calledOnly = it.only; calledOnly.call(it)", None), + ("it.each()()", None), + ("it.each`table`()", None), + ("test.each()()", None), + ("test.each`table`()", None), + ("test.concurrent()", None), + ]; + + let fail = vec![ + ("describe.only()", None), + // TODO: this need set setting like `settings: { jest: { globalAliases: { describe: ['context'] } } },` + // ("context.only()", None), + ("describe.only.each()()", None), + ("describe.only.each`table`()", None), + ("describe[\"only\"]()", None), + ("it.only()", None), + ("it.concurrent.only.each``()", None), + ("it.only.each()()", None), + ("it.only.each`table`()", None), + ("it[\"only\"]()", None), + ("test.only()", None), + ("test.concurrent.only.each()()", None), + ("test.only.each()()", None), + ("test.only.each`table`()", None), + ("test[\"only\"]()", None), + ("fdescribe()", None), + ("fit()", None), + ("fit.each()()", None), + ("fit.each`table`()", None), + ]; + + let fix = vec![ + ("describe.only('foo', () => {})", "describe('foo', () => {})", None), + ("describe['only']('foo', () => {})", "describe('foo', () => {})", None), + ("fdescribe('foo', () => {})", "describe('foo', () => {})", None), + ]; + + let mut tester = Tester::new(NoFocusedTests::NAME, pass, fail); + tester.test_and_snapshot(); + tester.test_fix(fix); +} 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 6dc686ef8e1a5e..bf110f2e28a8ad 100644 --- a/crates/oxc_linter/src/rules/jest/no_test_prefixes.rs +++ b/crates/oxc_linter/src/rules/jest/no_test_prefixes.rs @@ -1,5 +1,3 @@ -use std::borrow::Borrow; - use oxc_ast::{ast::Expression, AstKind}; use oxc_diagnostics::{ miette::{self, Diagnostic}, @@ -11,7 +9,7 @@ use oxc_span::{Atom, GetSpan, Span}; use crate::{ context::LintContext, fixer::Fix, - jest_ast_util::{parse_jest_fn_call, JestFnKind, ParsedJestFnCall}, + jest_ast_util::{parse_general_jest_fn_call, JestGeneralFnKind, ParsedGeneralJestFnCall, KnownMemberExpressionProperty}, rule::Rule, AstNode, }; @@ -50,11 +48,15 @@ declare_oxc_lint!( nursery ); -fn get_preferred_node_names(jest_fn_call: &ParsedJestFnCall) -> Atom { - let ParsedJestFnCall { members, raw, .. } = jest_fn_call; +fn get_preferred_node_names(jest_fn_call: &ParsedGeneralJestFnCall) -> Atom { + let ParsedGeneralJestFnCall { members, raw, .. } = jest_fn_call; let preferred_modifier = if raw.starts_with('f') { "only" } else { "skip" }; - let member_names = members.iter().map(Borrow::borrow).collect::>().join("."); + let member_names = members + .iter() + .filter_map(KnownMemberExpressionProperty::name) + .collect::>() + .join("."); let name_slice = &raw[1..]; if member_names.is_empty() { @@ -66,35 +68,33 @@ fn get_preferred_node_names(jest_fn_call: &ParsedJestFnCall) -> Atom { impl Rule for NoTestPrefixes { 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_jest_fn_call(call_expr, ctx) { - let ParsedJestFnCall { kind, raw, .. } = &jest_fn_call; - - if !matches!(kind, JestFnKind::Describe | JestFnKind::Test) { - return; - } - - if !raw.starts_with('f') && !raw.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), - ); - } + 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, raw, .. } = &jest_fn_call; + let Some(kind) = kind.to_general() else {return}; + + if !matches!(kind, JestGeneralFnKind::Describe | JestGeneralFnKind::Test) { + return; + } + + if !raw.starts_with('f') && !raw.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), + ); } } @@ -132,13 +132,14 @@ fn test() { ("xit.each``('foo', function () {})", None), ("xtest.each``('foo', function () {})", None), ("xit.each([])('foo', function () {})", None), - ("xtest.each([])('foo', function () {})", None), // TODO: Continue work on it when [#510](https://github.com/Boshen/oxc/issues/510) solved - // (r#"import { xit } from '@jest/globals'; - // xit("foo", function () {})"#, None), - // (r#"import { xit as skipThis } from '@jest/globals'; - // skipThis("foo", function () {})"#, None), - // (r#"import { fit as onlyThis } from '@jest/globals'; - // onlyThis("foo", function () {})"#, None) + ("xtest.each([])('foo', function () {})", None), + // TODO: Continue work on it when [#510](https://github.com/Boshen/oxc/issues/510) solved + // (r#"import { xit } from '@jest/globals'; + // xit("foo", function () {})"#, None), + // (r#"import { xit as skipThis } from '@jest/globals'; + // skipThis("foo", function () {})"#, None), + // (r#"import { fit as onlyThis } from '@jest/globals'; + // onlyThis("foo", function () {})"#, None) ]; Tester::new(NoTestPrefixes::NAME, pass, fail).test_and_snapshot(); diff --git a/crates/oxc_linter/src/snapshots/no_disabled_tests.snap b/crates/oxc_linter/src/snapshots/no_disabled_tests.snap index af5e7a22513427..0e5617a7b87d42 100644 --- a/crates/oxc_linter/src/snapshots/no_disabled_tests.snap +++ b/crates/oxc_linter/src/snapshots/no_disabled_tests.snap @@ -30,13 +30,6 @@ expression: no_disabled_tests ╰──── help: "Remove the appending `.skip`" - ⚠ eslint(jest/no-disabled-tests): "Disabled test suite" - ╭─[no_disabled_tests.tsx:1:1] - 1 │ describe[`skip`]('foo', function () {}) - · ─────────────────────────────────────── - ╰──── - help: "Remove the appending `.skip`" - ⚠ eslint(jest/no-disabled-tests): "Disabled test suite" ╭─[no_disabled_tests.tsx:1:1] 1 │ describe['skip']('foo', function () {}) diff --git a/crates/oxc_linter/src/snapshots/no_focused_tests.snap b/crates/oxc_linter/src/snapshots/no_focused_tests.snap new file mode 100644 index 00000000000000..a7afacdfd30935 --- /dev/null +++ b/crates/oxc_linter/src/snapshots/no_focused_tests.snap @@ -0,0 +1,131 @@ +--- +source: crates/oxc_linter/src/tester.rs +expression: no_focused_tests +--- + ⚠ Unexpected focused test. + ╭─[no_focused_tests.tsx:1:1] + 1 │ describe.only() + · ─────────────── + ╰──── + help: Remove focus from test. + + ⚠ Unexpected focused test. + ╭─[no_focused_tests.tsx:1:1] + 1 │ describe.only.each()() + · ────────────────────── + ╰──── + help: Remove focus from test. + + ⚠ Unexpected focused test. + ╭─[no_focused_tests.tsx:1:1] + 1 │ describe.only.each`table`() + · ─────────────────────────── + ╰──── + help: Remove focus from test. + + ⚠ Unexpected focused test. + ╭─[no_focused_tests.tsx:1:1] + 1 │ describe["only"]() + · ────────────────── + ╰──── + help: Remove focus from test. + + ⚠ Unexpected focused test. + ╭─[no_focused_tests.tsx:1:1] + 1 │ it.only() + · ───────── + ╰──── + help: Remove focus from test. + + ⚠ Unexpected focused test. + ╭─[no_focused_tests.tsx:1:1] + 1 │ it.concurrent.only.each``() + · ─────────────────────────── + ╰──── + help: Remove focus from test. + + ⚠ Unexpected focused test. + ╭─[no_focused_tests.tsx:1:1] + 1 │ it.only.each()() + · ──────────────── + ╰──── + help: Remove focus from test. + + ⚠ Unexpected focused test. + ╭─[no_focused_tests.tsx:1:1] + 1 │ it.only.each`table`() + · ───────────────────── + ╰──── + help: Remove focus from test. + + ⚠ Unexpected focused test. + ╭─[no_focused_tests.tsx:1:1] + 1 │ it["only"]() + · ──────────── + ╰──── + help: Remove focus from test. + + ⚠ Unexpected focused test. + ╭─[no_focused_tests.tsx:1:1] + 1 │ test.only() + · ─────────── + ╰──── + help: Remove focus from test. + + ⚠ Unexpected focused test. + ╭─[no_focused_tests.tsx:1:1] + 1 │ test.concurrent.only.each()() + · ───────────────────────────── + ╰──── + help: Remove focus from test. + + ⚠ Unexpected focused test. + ╭─[no_focused_tests.tsx:1:1] + 1 │ test.only.each()() + · ────────────────── + ╰──── + help: Remove focus from test. + + ⚠ Unexpected focused test. + ╭─[no_focused_tests.tsx:1:1] + 1 │ test.only.each`table`() + · ─────────────────────── + ╰──── + help: Remove focus from test. + + ⚠ Unexpected focused test. + ╭─[no_focused_tests.tsx:1:1] + 1 │ test["only"]() + · ────────────── + ╰──── + help: Remove focus from test. + + ⚠ Unexpected focused test. + ╭─[no_focused_tests.tsx:1:1] + 1 │ fdescribe() + · ─────────── + ╰──── + help: Remove focus from test. + + ⚠ Unexpected focused test. + ╭─[no_focused_tests.tsx:1:1] + 1 │ fit() + · ───── + ╰──── + help: Remove focus from test. + + ⚠ Unexpected focused test. + ╭─[no_focused_tests.tsx:1:1] + 1 │ fit.each()() + · ──────────── + ╰──── + help: Remove focus from test. + + ⚠ Unexpected focused test. + ╭─[no_focused_tests.tsx:1:1] + 1 │ fit.each`table`() + · ───────────────── + ╰──── + help: Remove focus from test. + +