diff --git a/crates/biome_js_analyze/src/analyzers/nursery/no_focused_tests.rs b/crates/biome_js_analyze/src/analyzers/nursery/no_focused_tests.rs index 169cd3ee733a..a0c7dd5a03d4 100644 --- a/crates/biome_js_analyze/src/analyzers/nursery/no_focused_tests.rs +++ b/crates/biome_js_analyze/src/analyzers/nursery/no_focused_tests.rs @@ -1,3 +1,4 @@ +use crate::JsRuleAction; use biome_analyze::{ context::RuleContext, declare_rule, Ast, FixKind, Rule, RuleDiagnostic, RuleSource, RuleSourceKind, @@ -6,9 +7,7 @@ use biome_console::markup; use biome_diagnostics::Applicability; use biome_js_factory::make; use biome_js_syntax::{JsCallExpression, TextRange}; -use biome_rowan::BatchMutationExt; - -use crate::JsRuleAction; +use biome_rowan::{AstNode, BatchMutationExt, NodeOrToken}; declare_rule! { /// Disallow focused tests. @@ -44,6 +43,7 @@ declare_rule! { } const FUNCTION_NAMES: [&str; 3] = ["only", "fdescribe", "fit"]; +const CALEE_NAMES: [&str; 3] = ["describe", "it", "test"]; impl Rule for NoFocusedTests { type Query = Ast; @@ -53,6 +53,7 @@ impl Rule for NoFocusedTests { fn run(ctx: &RuleContext) -> Self::Signals { let node = ctx.query(); + let callee = node.callee().ok()?; if node.is_test_call_expression().ok()? { let callee = node.callee().ok()?; @@ -63,6 +64,29 @@ impl Rule for NoFocusedTests { return Some(function_name.text_trimmed_range()); } } + } else if let Some(expression) = callee.as_js_computed_member_expression() { + let value_token = expression + .as_fields() + .object + .ok()? + .as_js_identifier_expression()? + .name() + .ok()? + .value_token() + .ok()?; + + if expression.l_brack_token().is_ok() + && expression.r_brack_token().is_ok() + && CALEE_NAMES.contains(&value_token.text_trimmed()) + { + if let Some(literal) = expression.member().ok()?.as_any_js_literal_expression() { + if literal.as_js_string_literal_expression().is_some() + && literal.to_string() == "\"only\"" + { + return Some(expression.syntax().text_trimmed_range()); + } + } + } } None @@ -77,36 +101,46 @@ impl Rule for NoFocusedTests { "Don't focus the test." }, ) - .note("This is likely a change done during debugging or implementation phases, but it's unlikely what you want in production.") - .note("Remove it.") + .note("The 'only' method is often used for debugging or during implementation. It should be removed before deploying to production.") + .note("Consider removing 'only' to ensure all tests are executed.") ) } fn action(ctx: &RuleContext, _: &Self::State) -> Option { let node = ctx.query(); let callee = node.callee().ok()?; - let function_name = callee.get_callee_member_name()?; - let replaced_function; let mut mutation = ctx.root().begin(); - match function_name.text_trimmed() { - "only" => { - let member = callee.as_js_static_member_expression()?; - let member_name = member.member().ok()?; - let operator_token = member.operator_token().ok()?; - mutation.remove_element(member_name.into()); - mutation.remove_element(operator_token.into()); - } - "fit" => { - replaced_function = make::js_reference_identifier(make::ident("it")); - mutation.replace_element(function_name.into(), replaced_function.into()); - } - "fdescribe" => { - replaced_function = make::js_reference_identifier(make::ident("describe")); - mutation.replace_element(function_name.into(), replaced_function.into()); - } - _ => {} + if let Some(function_name) = callee.get_callee_member_name() { + let replaced_function; + match function_name.text_trimmed() { + "only" => { + if let Some(expression) = callee.as_js_static_member_expression() { + let member_name = expression.member().ok()?; + let operator_token = expression.operator_token().ok()?; + mutation.remove_element(member_name.into()); + mutation.remove_element(operator_token.into()); + } + } + "fdescribe" => { + replaced_function = make::js_reference_identifier(make::ident("describe")); + mutation.replace_element(function_name.into(), replaced_function.into()); + } + "fit" => { + replaced_function = make::js_reference_identifier(make::ident("it")); + mutation.replace_element(function_name.into(), replaced_function.into()); + } + _ => {} + }; + } else if let Some(expression) = callee.as_js_computed_member_expression() { + let l_brack = expression.l_brack_token().ok()?; + let r_brack = expression.r_brack_token().ok()?; + let member = expression.member().ok()?; + let expression = member.as_any_js_literal_expression()?; + mutation.remove_element(NodeOrToken::Token(l_brack)); + mutation.remove_element(NodeOrToken::Node(expression.syntax().clone())); + mutation.remove_element(NodeOrToken::Token(r_brack)); }; Some(JsRuleAction { diff --git a/crates/biome_js_analyze/tests/specs/nursery/noFocusedTests/invalid.js b/crates/biome_js_analyze/tests/specs/nursery/noFocusedTests/invalid.js index 4493a5628caf..a4b0150028ed 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noFocusedTests/invalid.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noFocusedTests/invalid.js @@ -1,5 +1,14 @@ -describe.only("test", () => {}); -it.only("test", () => {}); -test.only("test", () => {}); -fdescribe('foo', () => {}); -fit('foo', () => {}); +describe.only("bar", function () {}); +it.only("bar", function () {}); +test.only("bar", function () {}); + +describe.only("bar", () => {}); +it.only("bar", () => {}); +test.only("bar", () => {}); + +describe["only"]("bar", function () {}); +it["only"]("bar", function () {}); +test["only"]("bar", function () {}); + +fdescribe("foo", () => {}); +fit("foo", () => {}); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noFocusedTests/invalid.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noFocusedTests/invalid.js.snap index a984f7497789..9d03d74f0cf0 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noFocusedTests/invalid.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noFocusedTests/invalid.js.snap @@ -4,11 +4,20 @@ expression: invalid.js --- # Input ```jsx -describe.only("test", () => {}); -it.only("test", () => {}); -test.only("test", () => {}); -fdescribe('foo', () => {}); -fit('foo', () => {}); +describe.only("bar", function () {}); +it.only("bar", function () {}); +test.only("bar", function () {}); + +describe.only("bar", () => {}); +it.only("bar", () => {}); +test.only("bar", () => {}); + +describe["only"]("bar", function () {}); +it["only"]("bar", function () {}); +test["only"]("bar", function () {}); + +fdescribe("foo", () => {}); +fit("foo", () => {}); ``` @@ -18,21 +27,21 @@ invalid.js:1:10 lint/nursery/noFocusedTests FIXABLE ━━━━━━━━ ! Don't focus the test. - > 1 │ describe.only("test", () => {}); + > 1 │ describe.only("bar", function () {}); │ ^^^^ - 2 │ it.only("test", () => {}); - 3 │ test.only("test", () => {}); + 2 │ it.only("bar", function () {}); + 3 │ test.only("bar", function () {}); - i This is likely a change done during debugging or implementation phases, but it's unlikely what you want in production. + i The 'only' method is often used for debugging or during implementation. It should be removed before deploying to production. - i Remove it. + i Consider removing 'only' to ensure all tests are executed. i Unsafe fix: Remove focus from test. - 1 │ - describe.only("test",·()·=>·{}); - 1 │ + describe("test",·()·=>·{}); - 2 2 │ it.only("test", () => {}); - 3 3 │ test.only("test", () => {}); + 1 │ - describe.only("bar",·function·()·{}); + 1 │ + describe("bar",·function·()·{}); + 2 2 │ it.only("bar", function () {}); + 3 3 │ test.only("bar", function () {}); ``` @@ -42,23 +51,23 @@ invalid.js:2:4 lint/nursery/noFocusedTests FIXABLE ━━━━━━━━━ ! Don't focus the test. - 1 │ describe.only("test", () => {}); - > 2 │ it.only("test", () => {}); + 1 │ describe.only("bar", function () {}); + > 2 │ it.only("bar", function () {}); │ ^^^^ - 3 │ test.only("test", () => {}); - 4 │ fdescribe('foo', () => {}); + 3 │ test.only("bar", function () {}); + 4 │ - i This is likely a change done during debugging or implementation phases, but it's unlikely what you want in production. + i The 'only' method is often used for debugging or during implementation. It should be removed before deploying to production. - i Remove it. + i Consider removing 'only' to ensure all tests are executed. i Unsafe fix: Remove focus from test. - 1 1 │ describe.only("test", () => {}); - 2 │ - it.only("test",·()·=>·{}); - 2 │ + it("test",·()·=>·{}); - 3 3 │ test.only("test", () => {}); - 4 4 │ fdescribe('foo', () => {}); + 1 1 │ describe.only("bar", function () {}); + 2 │ - it.only("bar",·function·()·{}); + 2 │ + it("bar",·function·()·{}); + 3 3 │ test.only("bar", function () {}); + 4 4 │ ``` @@ -68,79 +77,229 @@ invalid.js:3:6 lint/nursery/noFocusedTests FIXABLE ━━━━━━━━━ ! Don't focus the test. - 1 │ describe.only("test", () => {}); - 2 │ it.only("test", () => {}); - > 3 │ test.only("test", () => {}); + 1 │ describe.only("bar", function () {}); + 2 │ it.only("bar", function () {}); + > 3 │ test.only("bar", function () {}); │ ^^^^ - 4 │ fdescribe('foo', () => {}); - 5 │ fit('foo', () => {}); + 4 │ + 5 │ describe.only("bar", () => {}); - i This is likely a change done during debugging or implementation phases, but it's unlikely what you want in production. + i The 'only' method is often used for debugging or during implementation. It should be removed before deploying to production. - i Remove it. + i Consider removing 'only' to ensure all tests are executed. i Unsafe fix: Remove focus from test. - 1 1 │ describe.only("test", () => {}); - 2 2 │ it.only("test", () => {}); - 3 │ - test.only("test",·()·=>·{}); - 3 │ + test("test",·()·=>·{}); - 4 4 │ fdescribe('foo', () => {}); - 5 5 │ fit('foo', () => {}); + 1 1 │ describe.only("bar", function () {}); + 2 2 │ it.only("bar", function () {}); + 3 │ - test.only("bar",·function·()·{}); + 3 │ + test("bar",·function·()·{}); + 4 4 │ + 5 5 │ describe.only("bar", () => {}); + + +``` + +``` +invalid.js:5:10 lint/nursery/noFocusedTests FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Don't focus the test. + + 3 │ test.only("bar", function () {}); + 4 │ + > 5 │ describe.only("bar", () => {}); + │ ^^^^ + 6 │ it.only("bar", () => {}); + 7 │ test.only("bar", () => {}); + + i The 'only' method is often used for debugging or during implementation. It should be removed before deploying to production. + + i Consider removing 'only' to ensure all tests are executed. + + i Unsafe fix: Remove focus from test. + + 3 3 │ test.only("bar", function () {}); + 4 4 │ + 5 │ - describe.only("bar",·()·=>·{}); + 5 │ + describe("bar",·()·=>·{}); + 6 6 │ it.only("bar", () => {}); + 7 7 │ test.only("bar", () => {}); + + +``` + +``` +invalid.js:6:4 lint/nursery/noFocusedTests FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Don't focus the test. + + 5 │ describe.only("bar", () => {}); + > 6 │ it.only("bar", () => {}); + │ ^^^^ + 7 │ test.only("bar", () => {}); + 8 │ + + i The 'only' method is often used for debugging or during implementation. It should be removed before deploying to production. + + i Consider removing 'only' to ensure all tests are executed. + + i Unsafe fix: Remove focus from test. + + 4 4 │ + 5 5 │ describe.only("bar", () => {}); + 6 │ - it.only("bar",·()·=>·{}); + 6 │ + it("bar",·()·=>·{}); + 7 7 │ test.only("bar", () => {}); + 8 8 │ + + +``` + +``` +invalid.js:7:6 lint/nursery/noFocusedTests FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Don't focus the test. + + 5 │ describe.only("bar", () => {}); + 6 │ it.only("bar", () => {}); + > 7 │ test.only("bar", () => {}); + │ ^^^^ + 8 │ + 9 │ describe["only"]("bar", function () {}); + + i The 'only' method is often used for debugging or during implementation. It should be removed before deploying to production. + + i Consider removing 'only' to ensure all tests are executed. + + i Unsafe fix: Remove focus from test. + + 5 5 │ describe.only("bar", () => {}); + 6 6 │ it.only("bar", () => {}); + 7 │ - test.only("bar",·()·=>·{}); + 7 │ + test("bar",·()·=>·{}); + 8 8 │ + 9 9 │ describe["only"]("bar", function () {}); + + +``` + +``` +invalid.js:9:1 lint/nursery/noFocusedTests FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Don't focus the test. + + 7 │ test.only("bar", () => {}); + 8 │ + > 9 │ describe["only"]("bar", function () {}); + │ ^^^^^^^^^^^^^^^^ + 10 │ it["only"]("bar", function () {}); + 11 │ test["only"]("bar", function () {}); + + i The 'only' method is often used for debugging or during implementation. It should be removed before deploying to production. + + i Consider removing 'only' to ensure all tests are executed. + + i Unsafe fix: Remove focus from test. + + 9 │ describe["only"]("bar",·function·()·{}); + │ -------- + +``` + +``` +invalid.js:10:1 lint/nursery/noFocusedTests FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Don't focus the test. + + 9 │ describe["only"]("bar", function () {}); + > 10 │ it["only"]("bar", function () {}); + │ ^^^^^^^^^^ + 11 │ test["only"]("bar", function () {}); + 12 │ + + i The 'only' method is often used for debugging or during implementation. It should be removed before deploying to production. + + i Consider removing 'only' to ensure all tests are executed. + + i Unsafe fix: Remove focus from test. + + 10 │ it["only"]("bar",·function·()·{}); + │ -------- + +``` + +``` +invalid.js:11:1 lint/nursery/noFocusedTests FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Don't focus the test. + + 9 │ describe["only"]("bar", function () {}); + 10 │ it["only"]("bar", function () {}); + > 11 │ test["only"]("bar", function () {}); + │ ^^^^^^^^^^^^ + 12 │ + 13 │ fdescribe("foo", () => {}); + + i The 'only' method is often used for debugging or during implementation. It should be removed before deploying to production. + + i Consider removing 'only' to ensure all tests are executed. + + i Unsafe fix: Remove focus from test. + 11 │ test["only"]("bar",·function·()·{}); + │ -------- ``` ``` -invalid.js:4:1 lint/nursery/noFocusedTests FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:13:1 lint/nursery/noFocusedTests FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ! Don't focus the test. - 2 │ it.only("test", () => {}); - 3 │ test.only("test", () => {}); - > 4 │ fdescribe('foo', () => {}); - │ ^^^^^^^^^ - 5 │ fit('foo', () => {}); - 6 │ + 11 │ test["only"]("bar", function () {}); + 12 │ + > 13 │ fdescribe("foo", () => {}); + │ ^^^^^^^^^ + 14 │ fit("foo", () => {}); + 15 │ - i This is likely a change done during debugging or implementation phases, but it's unlikely what you want in production. + i The 'only' method is often used for debugging or during implementation. It should be removed before deploying to production. - i Remove it. + i Consider removing 'only' to ensure all tests are executed. i Unsafe fix: Remove focus from test. - 2 2 │ it.only("test", () => {}); - 3 3 │ test.only("test", () => {}); - 4 │ - fdescribe('foo',·()·=>·{}); - 4 │ + describe('foo',·()·=>·{}); - 5 5 │ fit('foo', () => {}); - 6 6 │ + 11 11 │ test["only"]("bar", function () {}); + 12 12 │ + 13 │ - fdescribe("foo",·()·=>·{}); + 13 │ + describe("foo",·()·=>·{}); + 14 14 │ fit("foo", () => {}); + 15 15 │ ``` ``` -invalid.js:5:1 lint/nursery/noFocusedTests FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:14:1 lint/nursery/noFocusedTests FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ! Don't focus the test. - 3 │ test.only("test", () => {}); - 4 │ fdescribe('foo', () => {}); - > 5 │ fit('foo', () => {}); - │ ^^^ - 6 │ + 13 │ fdescribe("foo", () => {}); + > 14 │ fit("foo", () => {}); + │ ^^^ + 15 │ - i This is likely a change done during debugging or implementation phases, but it's unlikely what you want in production. + i The 'only' method is often used for debugging or during implementation. It should be removed before deploying to production. - i Remove it. + i Consider removing 'only' to ensure all tests are executed. i Unsafe fix: Remove focus from test. - 3 3 │ test.only("test", () => {}); - 4 4 │ fdescribe('foo', () => {}); - 5 │ - fit('foo',·()·=>·{}); - 5 │ + it('foo',·()·=>·{}); - 6 6 │ + 12 12 │ + 13 13 │ fdescribe("foo", () => {}); + 14 │ - fit("foo",·()·=>·{}); + 14 │ + it("foo",·()·=>·{}); + 15 15 │ ``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noFocusedTests/valid.js b/crates/biome_js_analyze/tests/specs/nursery/noFocusedTests/valid.js index 7e6f797e7df0..6f65575d3e4e 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noFocusedTests/valid.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noFocusedTests/valid.js @@ -1,5 +1,5 @@ describe.skip("test", () => {}); it.skip("test", () => {}); test.skip("test", () => {}); -xdescribe('foo', () => {}); -xit('foo', () => {}); +xdescribe("test", () => {}); +xit("test", () => {}) diff --git a/crates/biome_js_analyze/tests/specs/nursery/noFocusedTests/valid.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noFocusedTests/valid.js.snap index 24fd3a71ff06..5b148ca50d3d 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noFocusedTests/valid.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noFocusedTests/valid.js.snap @@ -7,8 +7,8 @@ expression: valid.js describe.skip("test", () => {}); it.skip("test", () => {}); test.skip("test", () => {}); -xdescribe('foo', () => {}); -xit('foo', () => {}); +xdescribe("test", () => {}); +xit("test", () => {}) ``` diff --git a/website/src/content/docs/linter/rules/no-focused-tests.md b/website/src/content/docs/linter/rules/no-focused-tests.md index d30e95dcc216..2dc2bc57d3ec 100644 --- a/website/src/content/docs/linter/rules/no-focused-tests.md +++ b/website/src/content/docs/linter/rules/no-focused-tests.md @@ -32,9 +32,9 @@ describe.only("foo", () => {}); ^^^^ 2 │ - This is likely a change done during debugging or implementation phases, but it's unlikely what you want in production. + The 'only' method is often used for debugging or during implementation. It should be removed before deploying to production. - Remove it. + Consider removing 'only' to ensure all tests are executed. Unsafe fix: Remove focus from test. @@ -56,9 +56,9 @@ test.only("foo", () => {}); ^^^^ 2 │ - This is likely a change done during debugging or implementation phases, but it's unlikely what you want in production. + The 'only' method is often used for debugging or during implementation. It should be removed before deploying to production. - Remove it. + Consider removing 'only' to ensure all tests are executed. Unsafe fix: Remove focus from test.