feat(linter): no-focused-test(eslint-jest-plugin) (#609)

This commit is contained in:
Wenzhe Wang 2023-07-30 20:57:30 +08:00 committed by GitHub
parent 3b9cc474e9
commit 3cf08a256c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 543 additions and 121 deletions

View file

@ -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<ParsedGeneralJestFnCall<'a>> {
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<Cow<'a, str>>,
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<ParsedJestFnCall<'a>> {
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<ResolvedJestFn<'a>> {
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<Cow<'a, str>> {
#[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<JestGeneralFnKind> {
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<KnownMemberExpressionProperty<'a>>,
pub raw: Cow<'a, str>,
}
pub struct ParsedExpectFnCall<'a> {
pub kind: JestFnKind,
pub members: Vec<KnownMemberExpressionProperty<'a>>,
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<Cow<'a, str>> {
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<KnownMemberExpressionProperty<'a>> {
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,
});
}
}
_ => {}

View file

@ -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)]

View file

@ -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),

View file

@ -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 dont 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);
}

View file

@ -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::<Vec<&str>>().join(".");
let member_names = members
.iter()
.filter_map(KnownMemberExpressionProperty::name)
.collect::<Vec<_>>()
.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;
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, 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),
);
}
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();

View file

@ -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 () {})

View file

@ -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.