From 375870d7784d3ffc1c0fdec620e8006794ca0d39 Mon Sep 17 00:00:00 2001 From: Josh Delsman <12201+voxxit@users.noreply.github.com> Date: Fri, 17 Oct 2025 02:57:08 -0500 Subject: [PATCH 01/24] feat(biome_js_analyze): add 13 Playwright lint rules to nursery Add comprehensive Playwright testing framework support with 13 new lint rules converted from eslint-plugin-playwright. All rules are placed in the nursery category with proper namespacing (noPlaywright* prefix). Rules implemented: - missingPlaywrightAwait: Enforce async Playwright APIs are awaited/returned - noPlaywrightUselessAwait: Disallow unnecessary await on sync methods - noPlaywrightPagePause: Disallow page.pause() debugging calls - noPlaywrightFocusedTest: Disallow .only test annotations - noPlaywrightSkippedTest: Disallow .skip test annotations - noPlaywrightWaitForTimeout: Disallow hardcoded timeouts - noPlaywrightWaitForNavigation: Disallow deprecated waitForNavigation - noPlaywrightWaitForSelector: Disallow waitForSelector in favor of locators - noPlaywrightElementHandle: Disallow element handles (page.$, page.26692) - noPlaywrightEval: Disallow page.$eval and page.$$eval - noPlaywrightForceOption: Disallow { force: true } option - noPlaywrightNetworkidle: Disallow networkidle option - noPlaywrightValidDescribeCallback: Enforce valid describe callbacks Infrastructure changes: - Added EslintPlaywright variant to RuleSource enum - Registered all rules in nursery lint group - Added diagnostic categories for all rules - Comprehensive test coverage with 1813 passing tests Auto-fixes available for: - missingPlaywrightAwait (unsafe) - noPlaywrightUselessAwait (safe) Source: https://github.com/playwright-community/eslint-plugin-playwright --- crates/biome_analyze/src/rule.rs | 6 + .../src/categories.rs | 13 + crates/biome_js_analyze/src/lint/nursery.rs | 15 +- .../lint/nursery/missing_playwright_await.rs | 439 ++++++++++++++++++ .../nursery/no_playwright_element_handle.rs | 121 +++++ .../src/lint/nursery/no_playwright_eval.rs | 108 +++++ .../nursery/no_playwright_focused_test.rs | 131 ++++++ .../nursery/no_playwright_force_option.rs | 153 ++++++ .../lint/nursery/no_playwright_networkidle.rs | 157 +++++++ .../lint/nursery/no_playwright_page_pause.rs | 111 +++++ .../nursery/no_playwright_skipped_test.rs | 130 ++++++ .../nursery/no_playwright_useless_await.rs | 344 ++++++++++++++ .../no_playwright_valid_describe_callback.rs | 174 +++++++ .../no_playwright_wait_for_navigation.rs | 109 +++++ .../no_playwright_wait_for_selector.rs | 110 +++++ .../nursery/no_playwright_wait_for_timeout.rs | 109 +++++ .../invalid/expect-async-matcher.js | 4 + .../invalid/expect-async-matcher.js.snap | 39 ++ .../invalid/expect-poll.js | 4 + .../invalid/expect-poll.js.snap | 13 + .../invalid/test-step.js | 6 + .../invalid/test-step.js.snap | 44 ++ .../missingPlaywrightAwait/valid/awaited.js | 6 + .../valid/awaited.js.snap | 15 + .../valid/promise-all.js | 7 + .../valid/promise-all.js.snap | 16 + .../missingPlaywrightAwait/valid/returned.js | 6 + .../valid/returned.js.snap | 15 + .../invalid/dollar-dollar.js | 2 + .../invalid/dollar-dollar.js.snap | 30 ++ .../invalid/dollar.js | 2 + .../invalid/dollar.js.snap | 30 ++ .../invalid/frame.js | 2 + .../invalid/frame.js.snap | 30 ++ .../valid/locator.js | 8 + .../valid/locator.js.snap | 17 + .../noPlaywrightEval/invalid/eval-all.js | 2 + .../noPlaywrightEval/invalid/eval-all.js.snap | 28 ++ .../nursery/noPlaywrightEval/invalid/eval.js | 2 + .../noPlaywrightEval/invalid/eval.js.snap | 28 ++ .../valid/locator-evaluate.js | 3 + .../valid/locator-evaluate.js.snap | 12 + .../invalid/describe-only.js | 5 + .../invalid/describe-only.js.snap | 35 ++ .../invalid/describe-parallel-only.js | 5 + .../invalid/describe-parallel-only.js.snap | 35 ++ .../invalid/test-only.js | 2 + .../invalid/test-only.js.snap | 28 ++ .../noPlaywrightForceOption/invalid/click.js | 3 + .../invalid/click.js.snap | 29 ++ .../invalid/click.js.snap\\" | 28 ++ .../noPlaywrightForceOption/invalid/fill.js | 3 + .../invalid/fill.js.snap | 29 ++ .../noPlaywrightForceOption/valid/click.js | 5 + .../valid/click.js.snap | 14 + .../noPlaywrightNetworkidle/invalid/goto.js | 3 + .../invalid/goto.js.snap | 29 ++ .../invalid/wait-for-load-state.js | 3 + .../invalid/wait-for-load-state.js.snap | 29 ++ .../noPlaywrightNetworkidle/valid/load.js | 5 + .../valid/load.js.snap | 14 + .../noPlaywrightPagePause/invalid/frame.js | 3 + .../invalid/frame.js.snap | 30 ++ .../noPlaywrightPagePause/invalid/in-test.js | 5 + .../invalid/in-test.js.snap | 34 ++ .../noPlaywrightPagePause/invalid/simple.js | 2 + .../invalid/simple.js.snap | 28 ++ .../noPlaywrightPagePause/valid/click.js | 5 + .../noPlaywrightPagePause/valid/click.js.snap | 14 + .../valid/other-methods.js | 4 + .../valid/other-methods.js.snap | 13 + .../valid/pause-function.js | 7 + .../valid/pause-function.js.snap | 16 + .../invalid/describe-skip.js | 5 + .../invalid/describe-skip.js.snap | 35 ++ .../invalid/test-skip.js | 2 + .../invalid/test-skip.js.snap | 28 ++ .../invalid/expect-sync.js | 2 + .../invalid/expect-sync.js.snap | 32 ++ .../invalid/getByRole.js | 2 + .../invalid/getByRole.js.snap | 32 ++ .../invalid/locator.js | 2 + .../invalid/locator.js.snap | 32 ++ .../invalid/page-frame.js | 4 + .../invalid/page-frame.js.snap | 79 ++++ .../valid/async-methods.js | 5 + .../valid/async-methods.js.snap | 14 + .../valid/sync-no-await.js | 4 + .../valid/sync-no-await.js.snap | 13 + .../invalid/async.js | 5 + .../invalid/async.js.snap | 32 ++ .../invalid/has-params.js | 5 + .../invalid/has-params.js.snap | 32 ++ .../valid/correct.js | 10 + .../valid/correct.js.snap | 19 + .../invalid/simple.js | 2 + .../invalid/simple.js.snap | 28 ++ .../invalid/with-options.js | 3 + .../invalid/with-options.js.snap | 30 ++ .../valid/alternatives.js | 4 + .../valid/alternatives.js.snap | 13 + .../invalid/simple.js | 2 + .../invalid/simple.js.snap | 28 ++ .../invalid/with-state.js | 3 + .../invalid/with-state.js.snap | 30 ++ .../valid/locators.js | 5 + .../valid/locators.js.snap | 14 + .../invalid/in-test.js | 5 + .../invalid/in-test.js.snap | 34 ++ .../invalid/simple.js | 2 + .../invalid/simple.js.snap | 28 ++ .../valid/other-methods.js | 4 + .../valid/other-methods.js.snap | 13 + 113 files changed, 3707 insertions(+), 1 deletion(-) create mode 100644 crates/biome_js_analyze/src/lint/nursery/missing_playwright_await.rs create mode 100644 crates/biome_js_analyze/src/lint/nursery/no_playwright_element_handle.rs create mode 100644 crates/biome_js_analyze/src/lint/nursery/no_playwright_eval.rs create mode 100644 crates/biome_js_analyze/src/lint/nursery/no_playwright_focused_test.rs create mode 100644 crates/biome_js_analyze/src/lint/nursery/no_playwright_force_option.rs create mode 100644 crates/biome_js_analyze/src/lint/nursery/no_playwright_networkidle.rs create mode 100644 crates/biome_js_analyze/src/lint/nursery/no_playwright_page_pause.rs create mode 100644 crates/biome_js_analyze/src/lint/nursery/no_playwright_skipped_test.rs create mode 100644 crates/biome_js_analyze/src/lint/nursery/no_playwright_useless_await.rs create mode 100644 crates/biome_js_analyze/src/lint/nursery/no_playwright_valid_describe_callback.rs create mode 100644 crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_navigation.rs create mode 100644 crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_selector.rs create mode 100644 crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_timeout.rs create mode 100644 crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/expect-async-matcher.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/expect-async-matcher.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/expect-poll.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/expect-poll.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/test-step.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/test-step.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/awaited.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/awaited.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/promise-all.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/promise-all.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/returned.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/returned.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/dollar-dollar.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/dollar-dollar.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/dollar.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/dollar.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/frame.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/frame.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/valid/locator.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/valid/locator.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/eval-all.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/eval-all.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/eval.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/eval.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/valid/locator-evaluate.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/valid/locator-evaluate.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightFocusedTest/invalid/describe-only.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightFocusedTest/invalid/describe-only.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightFocusedTest/invalid/describe-parallel-only.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightFocusedTest/invalid/describe-parallel-only.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightFocusedTest/invalid/test-only.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightFocusedTest/invalid/test-only.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/click.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/click.js.snap create mode 100644 "crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/click.js.snap\\" create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/fill.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/fill.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/valid/click.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/valid/click.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/goto.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/goto.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/wait-for-load-state.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/wait-for-load-state.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/valid/load.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/valid/load.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/frame.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/frame.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/in-test.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/in-test.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/simple.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/simple.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/valid/click.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/valid/click.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/valid/other-methods.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/valid/other-methods.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/valid/pause-function.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/valid/pause-function.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightSkippedTest/invalid/describe-skip.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightSkippedTest/invalid/describe-skip.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightSkippedTest/invalid/test-skip.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightSkippedTest/invalid/test-skip.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/expect-sync.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/expect-sync.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/getByRole.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/getByRole.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/locator.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/locator.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/page-frame.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/page-frame.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/valid/async-methods.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/valid/async-methods.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/valid/sync-no-await.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/valid/sync-no-await.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/async.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/async.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/has-params.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/has-params.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/valid/correct.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/valid/correct.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/invalid/simple.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/invalid/simple.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/invalid/with-options.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/invalid/with-options.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/valid/alternatives.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/valid/alternatives.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForSelector/invalid/simple.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForSelector/invalid/simple.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForSelector/invalid/with-state.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForSelector/invalid/with-state.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForSelector/valid/locators.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForSelector/valid/locators.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/invalid/in-test.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/invalid/in-test.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/invalid/simple.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/invalid/simple.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/valid/other-methods.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/valid/other-methods.js.snap diff --git a/crates/biome_analyze/src/rule.rs b/crates/biome_analyze/src/rule.rs index 8f24fec2602f..7936f46c4cce 100644 --- a/crates/biome_analyze/src/rule.rs +++ b/crates/biome_analyze/src/rule.rs @@ -163,6 +163,8 @@ pub enum RuleSource { EslintPackageJson(&'static str), /// Rules from [Eslint Plugin Package.json Dependencies](https://github.com/idan-at/eslint-plugin-package-json-dependencies) EslintPackageJsonDependencies(&'static str), + /// Rules from [Eslint Plugin Playwright](https://github.com/playwright-community/eslint-plugin-playwright) + EslintPlaywright(&'static str), } impl PartialEq for RuleSource { @@ -213,6 +215,7 @@ impl std::fmt::Display for RuleSource { Self::EslintPackageJsonDependencies(_) => { write!(f, "eslint-plugin-package-json-dependencies") } + Self::EslintPlaywright(_) => write!(f, "eslint-plugin-playwright"), } } } @@ -268,6 +271,7 @@ impl RuleSource { | Self::EslintJsxA11y(rule_name) | Self::EslintJsDoc(rule_name) | Self::EslintPerfectionist(rule_name) + | Self::EslintPlaywright(rule_name) | Self::EslintReact(rule_name) | Self::EslintReactHooks(rule_name) | Self::EslintReactRefresh(rule_name) @@ -337,6 +341,7 @@ impl RuleSource { Self::EslintPackageJsonDependencies(rule_name) => { format!("package-json-dependencies/{rule_name}") } + Self::EslintPlaywright(rule_name) => format!("playwright/{rule_name}"), } } @@ -377,6 +382,7 @@ impl RuleSource { Self::EslintVueJs(rule_name) => format!("https://eslint.vuejs.org/rules/{rule_name}"), Self::EslintPackageJson(rule_name) => format!("https://github.com/JoshuaKGoldberg/eslint-plugin-package-json/blob/main/docs/rules/{rule_name}.md"), Self::EslintPackageJsonDependencies(rule_name) => format!("https://github.com/idan-at/eslint-plugin-package-json-dependencies/blob/master/docs/rules/{rule_name}.md"), + Self::EslintPlaywright(rule_name) => format!("https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/{rule_name}.md"), } } diff --git a/crates/biome_diagnostics_categories/src/categories.rs b/crates/biome_diagnostics_categories/src/categories.rs index 6f96b6c34393..24d2434d172f 100644 --- a/crates/biome_diagnostics_categories/src/categories.rs +++ b/crates/biome_diagnostics_categories/src/categories.rs @@ -172,7 +172,20 @@ define_categories! { "lint/nursery/noMissingGenericFamilyKeyword": "https://biomejs.dev/linter/rules/no-missing-generic-family-keyword", "lint/nursery/noMisusedPromises": "https://biomejs.dev/linter/rules/no-misused-promises", "lint/nursery/noNextAsyncClientComponent": "https://biomejs.dev/linter/rules/no-next-async-client-component", + "lint/nursery/missingPlaywrightAwait": "https://biomejs.dev/linter/rules/missing-playwright-await", "lint/nursery/noNonNullAssertedOptionalChain": "https://biomejs.dev/linter/rules/no-non-null-asserted-optional-chain", + "lint/nursery/noPlaywrightElementHandle": "https://biomejs.dev/linter/rules/no-playwright-element-handle", + "lint/nursery/noPlaywrightEval": "https://biomejs.dev/linter/rules/no-playwright-eval", + "lint/nursery/noPlaywrightFocusedTest": "https://biomejs.dev/linter/rules/no-playwright-focused-test", + "lint/nursery/noPlaywrightForceOption": "https://biomejs.dev/linter/rules/no-playwright-force-option", + "lint/nursery/noPlaywrightNetworkidle": "https://biomejs.dev/linter/rules/no-playwright-networkidle", + "lint/nursery/noPlaywrightUselessAwait": "https://biomejs.dev/linter/rules/no-playwright-useless-await", + "lint/nursery/noPlaywrightPagePause": "https://biomejs.dev/linter/rules/no-playwright-page-pause", + "lint/nursery/noPlaywrightSkippedTest": "https://biomejs.dev/linter/rules/no-playwright-skipped-test", + "lint/nursery/noPlaywrightValidDescribeCallback": "https://biomejs.dev/linter/rules/no-playwright-valid-describe-callback", + "lint/nursery/noPlaywrightWaitForNavigation": "https://biomejs.dev/linter/rules/no-playwright-wait-for-navigation", + "lint/nursery/noPlaywrightWaitForSelector": "https://biomejs.dev/linter/rules/no-playwright-wait-for-selector", + "lint/nursery/noPlaywrightWaitForTimeout": "https://biomejs.dev/linter/rules/no-playwright-wait-for-timeout", "lint/nursery/noQwikUseVisibleTask": "https://biomejs.dev/linter/rules/no-qwik-use-visible-task", "lint/nursery/noReactForwardRef": "https://biomejs.dev/linter/rules/no-react-forward-ref", "lint/nursery/noSecrets": "https://biomejs.dev/linter/rules/no-secrets", diff --git a/crates/biome_js_analyze/src/lint/nursery.rs b/crates/biome_js_analyze/src/lint/nursery.rs index 59670fe8740a..808424f64bad 100644 --- a/crates/biome_js_analyze/src/lint/nursery.rs +++ b/crates/biome_js_analyze/src/lint/nursery.rs @@ -3,8 +3,21 @@ //! Generated file, do not edit by hand, see `xtask/codegen` use biome_analyze::declare_lint_group; +pub mod missing_playwright_await; pub mod no_deprecated_imports; pub mod no_empty_source; +pub mod no_playwright_element_handle; +pub mod no_playwright_eval; +pub mod no_playwright_focused_test; +pub mod no_playwright_force_option; +pub mod no_playwright_useless_await; +pub mod no_playwright_networkidle; +pub mod no_playwright_page_pause; +pub mod no_playwright_skipped_test; +pub mod no_playwright_valid_describe_callback; +pub mod no_playwright_wait_for_navigation; +pub mod no_playwright_wait_for_selector; +pub mod no_playwright_wait_for_timeout; pub mod no_floating_promises; pub mod no_import_cycles; pub mod no_jsx_literals; @@ -37,4 +50,4 @@ pub mod use_qwik_valid_lexical_scope; pub mod use_react_function_components; pub mod use_sorted_classes; pub mod use_vue_multi_word_component_names; -declare_lint_group! { pub Nursery { name : "nursery" , rules : [self :: no_deprecated_imports :: NoDeprecatedImports , self :: no_empty_source :: NoEmptySource , self :: no_floating_promises :: NoFloatingPromises , self :: no_import_cycles :: NoImportCycles , self :: no_jsx_literals :: NoJsxLiterals , self :: no_misused_promises :: NoMisusedPromises , self :: no_next_async_client_component :: NoNextAsyncClientComponent , self :: no_non_null_asserted_optional_chain :: NoNonNullAssertedOptionalChain , self :: no_qwik_use_visible_task :: NoQwikUseVisibleTask , self :: no_react_forward_ref :: NoReactForwardRef , self :: no_secrets :: NoSecrets , self :: no_shadow :: NoShadow , self :: no_unnecessary_conditions :: NoUnnecessaryConditions , self :: no_unresolved_imports :: NoUnresolvedImports , self :: no_unused_expressions :: NoUnusedExpressions , self :: no_useless_catch_binding :: NoUselessCatchBinding , self :: no_useless_undefined :: NoUselessUndefined , self :: no_vue_data_object_declaration :: NoVueDataObjectDeclaration , self :: no_vue_duplicate_keys :: NoVueDuplicateKeys , self :: no_vue_reserved_keys :: NoVueReservedKeys , self :: no_vue_reserved_props :: NoVueReservedProps , self :: use_anchor_href :: UseAnchorHref , self :: use_consistent_arrow_return :: UseConsistentArrowReturn , self :: use_consistent_type_definitions :: UseConsistentTypeDefinitions , self :: use_exhaustive_switch_cases :: UseExhaustiveSwitchCases , self :: use_explicit_type :: UseExplicitType , self :: use_image_size :: UseImageSize , self :: use_max_params :: UseMaxParams , self :: use_qwik_classlist :: UseQwikClasslist , self :: use_qwik_method_usage :: UseQwikMethodUsage , self :: use_qwik_valid_lexical_scope :: UseQwikValidLexicalScope , self :: use_react_function_components :: UseReactFunctionComponents , self :: use_sorted_classes :: UseSortedClasses , self :: use_vue_multi_word_component_names :: UseVueMultiWordComponentNames ,] } } +declare_lint_group! { pub Nursery { name : "nursery" , rules : [self :: missing_playwright_await :: MissingPlaywrightAwait , self :: no_deprecated_imports :: NoDeprecatedImports , self :: no_empty_source :: NoEmptySource , self :: no_floating_promises :: NoFloatingPromises , self :: no_import_cycles :: NoImportCycles , self :: no_jsx_literals :: NoJsxLiterals , self :: no_misused_promises :: NoMisusedPromises , self :: no_next_async_client_component :: NoNextAsyncClientComponent , self :: no_non_null_asserted_optional_chain :: NoNonNullAssertedOptionalChain , self :: no_playwright_element_handle :: NoPlaywrightElementHandle , self :: no_playwright_eval :: NoPlaywrightEval , self :: no_playwright_focused_test :: NoPlaywrightFocusedTest , self :: no_playwright_force_option :: NoPlaywrightForceOption , self :: no_playwright_networkidle :: NoPlaywrightNetworkidle , self :: no_playwright_page_pause :: NoPlaywrightPagePause , self :: no_playwright_skipped_test :: NoPlaywrightSkippedTest , self :: no_playwright_useless_await :: NoPlaywrightUselessAwait , self :: no_playwright_valid_describe_callback :: NoPlaywrightValidDescribeCallback , self :: no_playwright_wait_for_navigation :: NoPlaywrightWaitForNavigation , self :: no_playwright_wait_for_selector :: NoPlaywrightWaitForSelector , self :: no_playwright_wait_for_timeout :: NoPlaywrightWaitForTimeout , self :: no_qwik_use_visible_task :: NoQwikUseVisibleTask , self :: no_react_forward_ref :: NoReactForwardRef , self :: no_secrets :: NoSecrets , self :: no_shadow :: NoShadow , self :: no_unnecessary_conditions :: NoUnnecessaryConditions , self :: no_unresolved_imports :: NoUnresolvedImports , self :: no_unused_expressions :: NoUnusedExpressions , self :: no_useless_catch_binding :: NoUselessCatchBinding , self :: no_useless_undefined :: NoUselessUndefined , self :: no_vue_data_object_declaration :: NoVueDataObjectDeclaration , self :: no_vue_duplicate_keys :: NoVueDuplicateKeys , self :: no_vue_reserved_keys :: NoVueReservedKeys , self :: no_vue_reserved_props :: NoVueReservedProps , self :: use_anchor_href :: UseAnchorHref , self :: use_consistent_arrow_return :: UseConsistentArrowReturn , self :: use_consistent_type_definitions :: UseConsistentTypeDefinitions , self :: use_exhaustive_switch_cases :: UseExhaustiveSwitchCases , self :: use_explicit_type :: UseExplicitType , self :: use_image_size :: UseImageSize , self :: use_max_params :: UseMaxParams , self :: use_qwik_classlist :: UseQwikClasslist , self :: use_qwik_method_usage :: UseQwikMethodUsage , self :: use_qwik_valid_lexical_scope :: UseQwikValidLexicalScope , self :: use_react_function_components :: UseReactFunctionComponents , self :: use_sorted_classes :: UseSortedClasses , self :: use_vue_multi_word_component_names :: UseVueMultiWordComponentNames ,] } } diff --git a/crates/biome_js_analyze/src/lint/nursery/missing_playwright_await.rs b/crates/biome_js_analyze/src/lint/nursery/missing_playwright_await.rs new file mode 100644 index 000000000000..884bc9472822 --- /dev/null +++ b/crates/biome_js_analyze/src/lint/nursery/missing_playwright_await.rs @@ -0,0 +1,439 @@ +use biome_analyze::{ + context::RuleContext, declare_lint_rule, FixKind, Ast, Rule, RuleDiagnostic, RuleSource, +}; +use biome_console::markup; +use biome_diagnostics::Applicability; +use biome_js_factory::make; +use biome_js_syntax::{ + AnyJsExpression, JsArrowFunctionExpression, JsCallExpression, + JsStaticMemberExpression, T, +}; +use biome_rowan::{AstNode, BatchMutationExt}; + +use crate::JsRuleAction; + +declare_lint_rule! { + /// Enforce Playwright async APIs to be awaited or returned. + /// + /// Playwright has asynchronous matchers and methods that must be properly awaited. + /// This rule identifies common mistakes where async Playwright APIs are not properly handled, + /// which can lead to false positives in tests. + /// + /// ## Examples + /// + /// ### Invalid + /// + /// ```js,expect_diagnostic + /// test('example', async ({ page }) => { + /// expect(page).toBeVisible(); + /// }); + /// ``` + /// + /// ```js,expect_diagnostic + /// test('example', async ({ page }) => { + /// test.step('step', async () => {}); + /// }); + /// ``` + /// + /// ### Valid + /// + /// ```js + /// test('example', async ({ page }) => { + /// await expect(page).toBeVisible(); + /// }); + /// ``` + /// + /// ```js + /// test('example', async ({ page }) => { + /// await test.step('step', async () => {}); + /// }); + /// ``` + /// + /// ```js + /// test('example', async ({ page }) => { + /// return expect(page).toBeVisible(); + /// }); + /// ``` + /// + pub MissingPlaywrightAwait { + version: "next", + name: "missingPlaywrightAwait", + language: "js", + sources: &[RuleSource::EslintPlaywright("missing-playwright-await").same()], + recommended: false, + fix_kind: FixKind::Safe, + } +} + +// Playwright async matchers (web-first assertions) +const ASYNC_PLAYWRIGHT_MATCHERS: &[&str] = &[ + "toBeAttached", + "toBeChecked", + "toBeDisabled", + "toBeEditable", + "toBeEmpty", + "toBeEnabled", + "toBeFocused", + "toBeHidden", + "toBeInViewport", + "toBeOK", + "toBeVisible", + "toContainText", + "toHaveAccessibleDescription", + "toHaveAccessibleErrorMessage", + "toHaveAccessibleName", + "toHaveAttribute", + "toHaveClass", + "toHaveCount", + "toHaveCSS", + "toHaveId", + "toHaveJSProperty", + "toHaveScreenshot", + "toHaveText", + "toHaveTitle", + "toHaveURL", + "toHaveValue", + "toHaveValues", + "toContainClass", + "toPass", +]; + +pub enum MissingAwaitType { + ExpectMatcher(String), + ExpectPoll, + TestStep, +} + +impl Rule for MissingPlaywrightAwait { + type Query = Ast; + type State = MissingAwaitType; + type Signals = Option; + type Options = (); + + fn run(ctx: &RuleContext) -> Self::Signals { + let call_expr = ctx.query(); + + // Check for test.step() calls + if is_test_step_call(call_expr) { + if !is_properly_handled(call_expr) { + return Some(MissingAwaitType::TestStep); + } + return None; + } + + // Check for expect calls with async matchers + if let Some(matcher_name) = get_async_expect_matcher(call_expr) { + if !is_properly_handled(call_expr) { + return Some(matcher_name); + } + } + + None + } + + fn diagnostic(ctx: &RuleContext, state: &Self::State) -> Option { + let node = ctx.query(); + + let (message, note) = match state { + MissingAwaitType::ExpectMatcher(matcher) => ( + markup! { + "Async matcher "{{matcher}}" must be awaited or returned." + }, + markup! { + "Add ""await"" before the expect call or return the promise." + }, + ), + MissingAwaitType::ExpectPoll => ( + markup! { + "expect.poll"" must be awaited or returned." + }, + markup! { + "Add ""await"" before the expect call or return the promise." + }, + ), + MissingAwaitType::TestStep => ( + markup! { + "test.step"" must be awaited or returned." + }, + markup! { + "Add ""await"" before the test.step call or return the promise." + }, + ), + }; + + Some( + RuleDiagnostic::new( + rule_category!(), + node.range(), + message, + ) + .note(note), + ) + } + + fn action(ctx: &RuleContext, _: &Self::State) -> Option { + let call_expr = ctx.query(); + + let mut mutation = ctx.root().begin(); + + // Create an await expression + let await_expr = make::js_await_expression( + make::token(T![await]), + call_expr.clone().into(), + ); + + mutation.replace_element( + call_expr.clone().into_syntax().into(), + await_expr.into_syntax().into(), + ); + + Some(JsRuleAction::new( + ctx.metadata().action_category(ctx.category(), ctx.group()), + Applicability::MaybeIncorrect, + markup! { "Add await" }.to_owned(), + mutation, + )) + } +} + +fn is_test_step_call(call_expr: &JsCallExpression) -> bool { + let callee = call_expr.callee().ok(); + + // Check for test.step pattern + if let Some(AnyJsExpression::JsStaticMemberExpression(member)) = callee { + if let Ok(member_name) = member.member() { + if let Some(name) = member_name.as_js_name() { + if let Ok(token) = name.value_token() { + if token.text_trimmed() == "step" { + // Check if object is "test" + if let Ok(object) = member.object() { + if let AnyJsExpression::JsIdentifierExpression(id) = object { + if let Ok(name) = id.name() { + if let Ok(token) = name.value_token() { + return token.text_trimmed() == "test"; + } + } + } + } + } + } + } + } + } + + false +} + +fn get_async_expect_matcher(call_expr: &JsCallExpression) -> Option { + let callee = call_expr.callee().ok()?; + + // Must be a member expression (matcher call) + let member_expr = callee.as_js_static_member_expression()?; + + // Get the matcher name + let member = member_expr.member().ok()?; + let name = member.as_js_name()?; + let token = name.value_token().ok()?; + let matcher_name = token.text_trimmed().to_string(); + + // Check if it's an async Playwright matcher + if !ASYNC_PLAYWRIGHT_MATCHERS.contains(&matcher_name.as_str()) { + return None; + } + + // Walk up the chain to find if this is an expect() call + let object = member_expr.object().ok()?; + + // Check for expect.poll + if has_poll_in_chain(&object) { + return Some(MissingAwaitType::ExpectPoll); + } + + // Check if the chain starts with expect + if has_expect_in_chain(&object) { + return Some(MissingAwaitType::ExpectMatcher(matcher_name)); + } + + None +} + +fn has_poll_in_chain(expr: &AnyJsExpression) -> bool { + match expr { + AnyJsExpression::JsStaticMemberExpression(member) => { + if let Ok(member_name) = member.member() { + if let Some(name) = member_name.as_js_name() { + if let Ok(token) = name.value_token() { + if token.text_trimmed() == "poll" { + return true; + } + } + } + } + if let Ok(object) = member.object() { + return has_poll_in_chain(&object); + } + false + } + AnyJsExpression::JsCallExpression(call) => { + if let Ok(callee) = call.callee() { + has_poll_in_chain(&callee) + } else { + false + } + } + _ => false, + } +} + +fn has_expect_in_chain(expr: &AnyJsExpression) -> bool { + match expr { + AnyJsExpression::JsCallExpression(call) => { + if let Ok(callee) = call.callee() { + match callee { + AnyJsExpression::JsIdentifierExpression(id) => { + if let Ok(name) = id.name() { + if let Ok(token) = name.value_token() { + return token.text_trimmed() == "expect"; + } + } + false + } + AnyJsExpression::JsStaticMemberExpression(member) => { + // Could be expect.soft or similar + if let Ok(object) = member.object() { + if let AnyJsExpression::JsIdentifierExpression(id) = object { + if let Ok(name) = id.name() { + if let Ok(token) = name.value_token() { + return token.text_trimmed() == "expect"; + } + } + } + } + false + } + _ => false, + } + } else { + false + } + } + AnyJsExpression::JsStaticMemberExpression(member) => { + if let Ok(object) = member.object() { + has_expect_in_chain(&object) + } else { + false + } + } + _ => false, + } +} + +fn is_properly_handled(call_expr: &JsCallExpression) -> bool { + let parent = call_expr.syntax().parent(); + + // Check if it's awaited + if let Some(parent) = &parent { + if parent.kind() == biome_js_syntax::JsSyntaxKind::JS_AWAIT_EXPRESSION { + return true; + } + } + + // Check if it's in a return statement + let mut current = parent; + while let Some(node) = current { + match node.kind() { + biome_js_syntax::JsSyntaxKind::JS_RETURN_STATEMENT => { + return true; + } + biome_js_syntax::JsSyntaxKind::JS_ARROW_FUNCTION_EXPRESSION => { + // If it's an arrow function with expression body, it's implicitly returned + if let Some(arrow) = JsArrowFunctionExpression::cast_ref(&node) { + if let Ok(body) = arrow.body() { + if body.as_any_js_expression().is_some() { + return true; + } + } + } + break; + } + _ => {} + } + current = node.parent(); + } + + // Check if it's in Promise.all + if is_in_promise_all(call_expr) { + return true; + } + + // Check if it's assigned to a variable that's later awaited + // This is complex and would require flow analysis, skipping for now + + false +} + +fn is_in_promise_all(call_expr: &JsCallExpression) -> bool { + let mut current = call_expr.syntax().parent(); + + while let Some(node) = current { + // Check if we're in an array expression + if node.kind() == biome_js_syntax::JsSyntaxKind::JS_ARRAY_EXPRESSION { + // Check if the array is an argument to Promise.all + if let Some(parent) = node.parent() { + if parent.kind() == biome_js_syntax::JsSyntaxKind::JS_CALL_ARGUMENT_LIST { + if let Some(call_args_parent) = parent.parent() { + if call_args_parent.kind() == biome_js_syntax::JsSyntaxKind::JS_CALL_ARGUMENTS { + if let Some(promise_call) = call_args_parent.parent() { + if let Some(call) = JsCallExpression::cast_ref(&promise_call) { + if is_promise_all(&call) { + return true; + } + } + } + } + } + } + } + } + + // Stop at function boundaries + if matches!( + node.kind(), + biome_js_syntax::JsSyntaxKind::JS_FUNCTION_EXPRESSION + | biome_js_syntax::JsSyntaxKind::JS_ARROW_FUNCTION_EXPRESSION + ) { + break; + } + + current = node.parent(); + } + + false +} + +fn is_promise_all(call: &JsCallExpression) -> bool { + if let Ok(callee) = call.callee() { + if let Some(member) = callee.as_js_static_member_expression() { + if let Ok(member_name) = member.member() { + if let Some(name) = member_name.as_js_name() { + if let Ok(token) = name.value_token() { + if token.text_trimmed() == "all" { + // Check if object is Promise + if let Ok(object) = member.object() { + if let AnyJsExpression::JsIdentifierExpression(id) = object { + if let Ok(name) = id.name() { + if let Ok(token) = name.value_token() { + return token.text_trimmed() == "Promise"; + } + } + } + } + } + } + } + } + } + } + false +} + diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_element_handle.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_element_handle.rs new file mode 100644 index 000000000000..34bcb9b76b37 --- /dev/null +++ b/crates/biome_js_analyze/src/lint/nursery/no_playwright_element_handle.rs @@ -0,0 +1,121 @@ +use biome_analyze::{ + context::RuleContext, declare_lint_rule, Ast, Rule, RuleDiagnostic, RuleSource, +}; +use biome_console::markup; +use biome_js_syntax::{JsCallExpression, JsStaticMemberExpression}; +use biome_rowan::AstNode; + +declare_lint_rule! { + /// Disallow usage of element handles (`page.$()` and `page.$$()`). + /// + /// Element handles are discouraged in Playwright. Use locators instead, which auto-wait + /// and are more reliable. Locators represent a way to find elements at any moment, + /// while element handles are references to specific elements that may become stale. + /// + /// ## Examples + /// + /// ### Invalid + /// + /// ```js,expect_diagnostic + /// const button = await page.$('button'); + /// ``` + /// + /// ```js,expect_diagnostic + /// const buttons = await page.$$('.btn'); + /// ``` + /// + /// ```js,expect_diagnostic + /// const element = await frame.$('#element'); + /// ``` + /// + /// ### Valid + /// + /// ```js + /// const button = page.locator('button'); + /// await button.click(); + /// ``` + /// + /// ```js + /// const buttons = page.locator('.btn'); + /// await expect(buttons).toHaveCount(3); + /// ``` + /// + /// ```js + /// await page.getByRole('button', { name: 'Submit' }).click(); + /// ``` + /// + pub NoPlaywrightElementHandle { + version: "next", + name: "noPlaywrightElementHandle", + language: "js", + sources: &[RuleSource::EslintPlaywright("no-element-handle").same()], + recommended: false, + } +} + +impl Rule for NoPlaywrightElementHandle { + type Query = Ast; + type State = String; + type Signals = Option; + type Options = (); + + fn run(ctx: &RuleContext) -> Self::Signals { + let call_expr = ctx.query(); + let callee = call_expr.callee().ok()?; + + let member_expr = JsStaticMemberExpression::cast_ref(callee.syntax())?; + + let member_name = member_expr.member().ok()?; + let member_text = member_name.as_js_name()?.value_token().ok()?; + let member_str = member_text.text_trimmed(); + + // Check if the method is $ or $$ + if member_str != "$" && member_str != "$$" { + return None; + } + + let object = member_expr.object().ok()?; + let object_text = match object { + biome_js_syntax::AnyJsExpression::JsIdentifierExpression(id) => { + id.name().ok()?.value_token().ok()?.text_trimmed().to_string() + } + biome_js_syntax::AnyJsExpression::JsStaticMemberExpression(member) => { + member.member().ok()?.as_js_name()?.value_token().ok()?.text_trimmed().to_string() + } + _ => return None, + }; + + // Check if it's "page" or "frame" or ends with "Page" or "Frame" + if object_text == "page" + || object_text == "frame" + || object_text.ends_with("Page") + || object_text.ends_with("Frame") { + Some(member_str.to_string()) + } else { + None + } + } + + fn diagnostic(ctx: &RuleContext, state: &Self::State) -> Option { + let node = ctx.query(); + Some( + RuleDiagnostic::new( + rule_category!(), + node.range(), + markup! { + "Unexpected use of element handles." + }, + ) + .note(markup! { + "Element handles like ""page."{{state}}"()"" are discouraged." + }) + .note(markup! { + "Use ""page.locator()"" or other locator methods like ""getByRole()"" instead." + }) + .note(markup! { + "Locators auto-wait and are more reliable than element handles." + }), + ) + } +} + diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_eval.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_eval.rs new file mode 100644 index 000000000000..de9ff31db8c3 --- /dev/null +++ b/crates/biome_js_analyze/src/lint/nursery/no_playwright_eval.rs @@ -0,0 +1,108 @@ +use biome_analyze::{ + context::RuleContext, declare_lint_rule, Ast, Rule, RuleDiagnostic, RuleSource, +}; +use biome_console::markup; +use biome_js_syntax::{JsCallExpression, JsStaticMemberExpression}; +use biome_rowan::AstNode; + +declare_lint_rule! { + /// Disallow usage of `page.$eval()` and `page.$$eval()`. + /// + /// These methods are discouraged in favor of `locator.evaluate()` and `locator.evaluateAll()`. + /// Locator-based evaluation is more reliable and follows Playwright's recommended patterns. + /// + /// ## Examples + /// + /// ### Invalid + /// + /// ```js,expect_diagnostic + /// await page.$eval('.foo', el => el.textContent); + /// ``` + /// + /// ```js,expect_diagnostic + /// const texts = await page.$$eval('.foo', els => els.map(el => el.textContent)); + /// ``` + /// + /// ### Valid + /// + /// ```js + /// const text = await page.locator('.foo').evaluate(el => el.textContent); + /// ``` + /// + /// ```js + /// const texts = await page.locator('.foo').evaluateAll(els => els.map(el => el.textContent)); + /// ``` + /// + pub NoPlaywrightEval { + version: "next", + name: "noPlaywrightEval", + language: "js", + sources: &[RuleSource::EslintPlaywright("no-eval").same()], + recommended: false, + } +} + +impl Rule for NoPlaywrightEval { + type Query = Ast; + type State = String; + type Signals = Option; + type Options = (); + + fn run(ctx: &RuleContext) -> Self::Signals { + let call_expr = ctx.query(); + let callee = call_expr.callee().ok()?; + + let member_expr = JsStaticMemberExpression::cast_ref(callee.syntax())?; + + let member_name = member_expr.member().ok()?; + let member_text = member_name.as_js_name()?.value_token().ok()?; + let member_str = member_text.text_trimmed(); + + // Check if the method is $eval or $$eval + if member_str != "$eval" && member_str != "$$eval" { + return None; + } + + let object = member_expr.object().ok()?; + let object_text = match object { + biome_js_syntax::AnyJsExpression::JsIdentifierExpression(id) => { + id.name().ok()?.value_token().ok()?.text_trimmed().to_string() + } + biome_js_syntax::AnyJsExpression::JsStaticMemberExpression(member) => { + member.member().ok()?.as_js_name()?.value_token().ok()?.text_trimmed().to_string() + } + _ => return None, + }; + + if object_text == "page" + || object_text == "frame" + || object_text.ends_with("Page") + || object_text.ends_with("Frame") { + Some(member_str.to_string()) + } else { + None + } + } + + fn diagnostic(ctx: &RuleContext, state: &Self::State) -> Option { + let node = ctx.query(); + let is_eval = state == "$eval"; + + Some( + RuleDiagnostic::new( + rule_category!(), + node.range(), + markup! { + "Unexpected use of ""page."{{state}}"()""." + }, + ) + .note(markup! { + "Use "{if is_eval { "locator.evaluate()" } else { "locator.evaluateAll()" }}" instead." + }) + .note(markup! { + "Locator-based evaluation is more reliable and follows Playwright's recommended patterns." + }), + ) + } +} + diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_focused_test.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_focused_test.rs new file mode 100644 index 000000000000..b3164d68e8a6 --- /dev/null +++ b/crates/biome_js_analyze/src/lint/nursery/no_playwright_focused_test.rs @@ -0,0 +1,131 @@ +use biome_analyze::{ + context::RuleContext, declare_lint_rule, Ast, Rule, RuleDiagnostic, RuleSource, +}; +use biome_console::markup; +use biome_js_syntax::{JsCallExpression, JsStaticMemberExpression}; +use biome_rowan::AstNode; + +declare_lint_rule! { + /// Disallow usage of `.only` annotation in Playwright tests. + /// + /// Focused tests using `.only` should not be committed to version control. + /// They cause the test runner to skip all other tests, which can lead to + /// incomplete test runs in CI/CD pipelines. + /// + /// ## Examples + /// + /// ### Invalid + /// + /// ```js,expect_diagnostic + /// test.only('focus this test', async ({ page }) => {}); + /// ``` + /// + /// ```js,expect_diagnostic + /// test.describe.only('focus suite', () => { + /// test('one', async ({ page }) => {}); + /// }); + /// ``` + /// + /// ### Valid + /// + /// ```js + /// test('this test', async ({ page }) => {}); + /// ``` + /// + /// ```js + /// test.describe('test suite', () => { + /// test('one', async ({ page }) => {}); + /// }); + /// ``` + /// + pub NoPlaywrightFocusedTest { + version: "next", + name: "noPlaywrightFocusedTest", + language: "js", + sources: &[RuleSource::EslintPlaywright("no-focused-test").same()], + recommended: false, + } +} + +impl Rule for NoPlaywrightFocusedTest { + type Query = Ast; + type State = (); + type Signals = Option; + type Options = (); + + fn run(ctx: &RuleContext) -> Self::Signals { + let call_expr = ctx.query(); + let callee = call_expr.callee().ok()?; + + // Check if this is a member expression like test.only() or describe.only() + let member_expr = JsStaticMemberExpression::cast_ref(callee.syntax())?; + + // Check if the member being accessed is "only" + let member_name = member_expr.member().ok()?; + let member_text = member_name.as_js_name()?.value_token().ok()?; + + if member_text.text_trimmed() != "only" { + return None; + } + + // Check if the object is test/describe or a chain like test.describe + let object = member_expr.object().ok()?; + + fn is_test_or_describe_object(expr: &biome_js_syntax::AnyJsExpression) -> bool { + match expr { + biome_js_syntax::AnyJsExpression::JsIdentifierExpression(id) => { + if let Ok(name) = id.name() { + if let Ok(token) = name.value_token() { + let text = token.text_trimmed(); + return text == "test" || text == "describe"; + } + } + false + } + biome_js_syntax::AnyJsExpression::JsStaticMemberExpression(member) => { + // Handle test.describe.only or similar chains + if let Ok(member_name) = member.member() { + if let Some(name) = member_name.as_js_name() { + if let Ok(token) = name.value_token() { + let text = token.text_trimmed(); + if text == "describe" || text == "serial" || text == "parallel" { + if let Ok(obj) = member.object() { + return is_test_or_describe_object(&obj); + } + } + } + } + } + false + } + _ => false, + } + } + + if is_test_or_describe_object(&object) { + Some(()) + } else { + None + } + } + + fn diagnostic(ctx: &RuleContext, _: &Self::State) -> Option { + let node = ctx.query(); + Some( + RuleDiagnostic::new( + rule_category!(), + node.range(), + markup! { + "Unexpected focused test using "".only"" annotation." + }, + ) + .note(markup! { + "Focused tests should not be committed to version control as they prevent other tests from running." + }) + .note(markup! { + "Remove the "".only"" annotation to run all tests." + }), + ) + } +} + diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_force_option.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_force_option.rs new file mode 100644 index 000000000000..c70648ae09a2 --- /dev/null +++ b/crates/biome_js_analyze/src/lint/nursery/no_playwright_force_option.rs @@ -0,0 +1,153 @@ +use biome_analyze::{ + context::RuleContext, declare_lint_rule, Ast, Rule, RuleDiagnostic, RuleSource, +}; +use biome_console::markup; +use biome_js_syntax::{AnyJsExpression, JsCallExpression, JsObjectExpression}; +use biome_rowan::{AstNode, AstNodeList}; + +declare_lint_rule! { + /// Disallow usage of the `{ force: true }` option. + /// + /// Playwright's `force` option bypasses actionability checks and can lead to unreliable tests. + /// Instead of using `{ force: true }`, you should fix the underlying issue that requires forcing the action. + /// + /// ## Examples + /// + /// ### Invalid + /// + /// ```js,expect_diagnostic + /// await page.locator('button').click({ force: true }); + /// ``` + /// + /// ```js,expect_diagnostic + /// await page.locator('check').check({ force: true }); + /// ``` + /// + /// ```js,expect_diagnostic + /// await page.locator('input').fill('text', { force: true }); + /// ``` + /// + /// ### Valid + /// + /// ```js + /// await page.locator('button').click(); + /// ``` + /// + /// ```js + /// await page.locator('check').check(); + /// ``` + /// + /// ```js + /// await page.locator('input').fill('text'); + /// ``` + /// + pub NoPlaywrightForceOption { + version: "next", + name: "noPlaywrightForceOption", + language: "js", + sources: &[RuleSource::EslintPlaywright("no-force-option").same()], + recommended: false, + } +} + +const METHODS_WITH_FORCE: &[&str] = &[ + "check", + "uncheck", + "click", + "dblclick", + "dragTo", + "fill", + "hover", + "selectOption", + "selectText", + "setChecked", + "tap", +]; + +impl Rule for NoPlaywrightForceOption { + type Query = Ast; + type State = (); + type Signals = Option; + type Options = (); + + fn run(ctx: &RuleContext) -> Self::Signals { + let call_expr = ctx.query(); + let callee = call_expr.callee().ok()?; + + // Check if this is a method call + let member_expr = biome_js_syntax::JsStaticMemberExpression::cast_ref(callee.syntax())?; + let member_name = member_expr.member().ok()?; + let member_text = member_name.as_js_name()?.value_token().ok()?; + let method_name = member_text.text_trimmed(); + + // Check if it's one of the methods that support force option + if !METHODS_WITH_FORCE.contains(&method_name) { + return None; + } + + // Check the arguments for { force: true } + let args = call_expr.arguments().ok()?; + + for arg in args.args().into_iter().flatten() { + if let Some(expr) = arg.as_any_js_expression() { + if let AnyJsExpression::JsObjectExpression(obj_expr) = expr { + if has_force_true(&obj_expr) { + return Some(()); + } + } + } + } + + None + } + + fn diagnostic(ctx: &RuleContext, _: &Self::State) -> Option { + let node = ctx.query(); + Some( + RuleDiagnostic::new( + rule_category!(), + node.range(), + markup! { + "Unexpected use of ""{ force: true }"" option." + }, + ) + .note(markup! { + "The ""force"" option bypasses actionability checks and can lead to unreliable tests." + }) + .note(markup! { + "Fix the underlying issue instead of forcing the action." + }), + ) + } +} + +fn has_force_true(obj_expr: &JsObjectExpression) -> bool { + for member in obj_expr.members().into_iter().flatten() { + if let Some(prop) = member.as_js_property_object_member() { + // Check if property name is 'force' + if let Ok(name) = prop.name() { + if let Some(name_node) = name.as_js_literal_member_name() { + if let Ok(name_token) = name_node.value() { + if name_token.text_trimmed() == "force" { + // Check if value is true + if let Ok(value) = prop.value() { + if let Some(literal) = value.as_any_js_literal_expression() { + if let Some(bool_lit) = literal.as_js_boolean_literal_expression() { + if let Ok(value_token) = bool_lit.value_token() { + if value_token.text_trimmed() == "true" { + return true; + } + } + } + } + } + } + } + } + } + } + } + + false +} + diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_networkidle.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_networkidle.rs new file mode 100644 index 000000000000..b072395eb639 --- /dev/null +++ b/crates/biome_js_analyze/src/lint/nursery/no_playwright_networkidle.rs @@ -0,0 +1,157 @@ +use biome_analyze::{ + context::RuleContext, declare_lint_rule, Ast, Rule, RuleDiagnostic, RuleSource, +}; +use biome_console::markup; +use biome_js_syntax::{ + AnyJsExpression, JsCallExpression, JsObjectExpression, JsStaticMemberExpression, +}; +use biome_rowan::{AstNode, AstNodeList}; + +declare_lint_rule! { + /// Disallow usage of the `networkidle` option. + /// + /// Using `networkidle` is discouraged in favor of using web-first assertions. + /// The `networkidle` event is unreliable and can lead to flaky tests. + /// + /// ## Examples + /// + /// ### Invalid + /// + /// ```js,expect_diagnostic + /// await page.waitForLoadState('networkidle'); + /// ``` + /// + /// ```js,expect_diagnostic + /// await page.goto('https://example.com', { waitUntil: 'networkidle' }); + /// ``` + /// + /// ### Valid + /// + /// ```js + /// await page.waitForLoadState('load'); + /// ``` + /// + /// ```js + /// await page.goto('https://example.com'); + /// await page.locator('.content').waitFor(); + /// ``` + /// + pub NoPlaywrightNetworkidle { + version: "next", + name: "noPlaywrightNetworkidle", + language: "js", + sources: &[RuleSource::EslintPlaywright("no-networkidle").same()], + recommended: false, + } +} + +impl Rule for NoPlaywrightNetworkidle { + type Query = Ast; + type State = (); + type Signals = Option; + type Options = (); + + fn run(ctx: &RuleContext) -> Self::Signals { + let call_expr = ctx.query(); + let callee = call_expr.callee().ok()?; + + let member_expr = JsStaticMemberExpression::cast_ref(callee.syntax())?; + let member_name = member_expr.member().ok()?; + let member_text = member_name.as_js_name()?.value_token().ok()?; + let method_name = member_text.text_trimmed(); + + // Check if it's one of the navigation methods + let is_navigation_method = matches!( + method_name, + "goto" | "goBack" | "goForward" | "reload" | "setContent" | "waitForURL" + ); + + // For waitForLoadState, check if first argument is 'networkidle' + if method_name == "waitForLoadState" { + let args = call_expr.arguments().ok()?; + let [Some(first_arg)] = args.get_arguments_by_index([0]) else { + return None; + }; + + if let Some(expr) = first_arg.as_any_js_expression() { + if let Some(literal) = expr.as_any_js_literal_expression() { + if let Some(string_lit) = literal.as_js_string_literal_expression() { + let value = string_lit.inner_string_text().ok()?; + if value.text() == "networkidle" { + return Some(()); + } + } + } + } + return None; + } + + // For navigation methods, check if options object has waitUntil: 'networkidle' + if is_navigation_method { + let args = call_expr.arguments().ok()?; + + // Navigation methods typically have options as the second argument + for arg in args.args().into_iter().flatten() { + if let Some(expr) = arg.as_any_js_expression() { + if let AnyJsExpression::JsObjectExpression(obj_expr) = expr { + if has_networkidle_option(&obj_expr) { + return Some(()); + } + } + } + } + } + + None + } + + fn diagnostic(ctx: &RuleContext, _: &Self::State) -> Option { + let node = ctx.query(); + Some( + RuleDiagnostic::new( + rule_category!(), + node.range(), + markup! { + "Unexpected use of ""networkidle"" option." + }, + ) + .note(markup! { + "The ""networkidle"" event is unreliable and can lead to flaky tests." + }) + .note(markup! { + "Use web-first assertions or wait for specific elements instead." + }), + ) + } +} + +fn has_networkidle_option(obj_expr: &JsObjectExpression) -> bool { + for member in obj_expr.members().into_iter().flatten() { + if let Some(prop) = member.as_js_property_object_member() { + // Check if property name is 'waitUntil' + if let Ok(name) = prop.name() { + if let Some(name_node) = name.as_js_literal_member_name() { + if let Ok(name_token) = name_node.value() { + if name_token.text_trimmed() == "waitUntil" { + // Check if value is 'networkidle' + if let Ok(value) = prop.value() { + if let Some(literal_expr) = value.as_any_js_literal_expression() { + if let Some(string_lit) = literal_expr.as_js_string_literal_expression() { + if let Ok(inner) = string_lit.inner_string_text() { + if inner.text() == "networkidle" { + return true; + } + } + } + } + } + } + } + } + } + } + } + + false +} + diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_page_pause.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_page_pause.rs new file mode 100644 index 000000000000..bfa9af446c69 --- /dev/null +++ b/crates/biome_js_analyze/src/lint/nursery/no_playwright_page_pause.rs @@ -0,0 +1,111 @@ +use biome_analyze::{ + context::RuleContext, declare_lint_rule, Ast, Rule, RuleDiagnostic, RuleSource, +}; +use biome_console::markup; +use biome_js_syntax::{JsCallExpression, JsStaticMemberExpression}; +use biome_rowan::AstNode; + +declare_lint_rule! { + /// Disallow using `page.pause()`. + /// + /// Playwright's `page.pause()` is a debugging utility that should not be committed to version control. + /// It pauses test execution and opens the Playwright Inspector, which is useful during development + /// but should not be present in production test code. + /// + /// ## Examples + /// + /// ### Invalid + /// + /// ```js,expect_diagnostic + /// await page.pause(); + /// ``` + /// + /// ```js,expect_diagnostic + /// test('example', async ({ page }) => { + /// await page.click('button'); + /// await page.pause(); + /// }); + /// ``` + /// + /// ### Valid + /// + /// ```js + /// test('example', async ({ page }) => { + /// await page.click('button'); + /// await page.waitForSelector('.result'); + /// }); + /// ``` + /// + pub NoPlaywrightPagePause { + version: "next", + name: "noPlaywrightPagePause", + language: "js", + sources: &[RuleSource::EslintPlaywright("no-page-pause").same()], + recommended: false, + } +} + +impl Rule for NoPlaywrightPagePause { + type Query = Ast; + type State = (); + type Signals = Option; + type Options = (); + + fn run(ctx: &RuleContext) -> Self::Signals { + let call_expr = ctx.query(); + let callee = call_expr.callee().ok()?; + + // Check if this is a member expression (e.g., page.pause()) + let member_expr = JsStaticMemberExpression::cast_ref(callee.syntax())?; + + // Check if the member being accessed is "pause" + let member_name = member_expr.member().ok()?; + let member_text = member_name.as_js_name()?.value_token().ok()?; + + if member_text.text_trimmed() != "pause" { + return None; + } + + // Check if the object is "page" or "frame" + let object = member_expr.object().ok()?; + let object_text = match object { + biome_js_syntax::AnyJsExpression::JsIdentifierExpression(id) => { + id.name().ok()?.value_token().ok()?.text_trimmed().to_string() + } + biome_js_syntax::AnyJsExpression::JsStaticMemberExpression(member) => { + // Handle cases like "context.page.pause()" + member.member().ok()?.as_js_name()?.value_token().ok()?.text_trimmed().to_string() + } + _ => return None, + }; + + // Check if it's "page" or "frame" or ends with "Page" or "Frame" (for variable names) + if object_text == "page" + || object_text == "frame" + || object_text.ends_with("Page") + || object_text.ends_with("Frame") { + Some(()) + } else { + None + } + } + + fn diagnostic(ctx: &RuleContext, _: &Self::State) -> Option { + let node = ctx.query(); + Some( + RuleDiagnostic::new( + rule_category!(), + node.range(), + markup! { + "Unexpected use of ""page.pause()""." + }, + ) + .note(markup! { + "page.pause()"" is a debugging utility and should not be committed to version control." + }) + .note(markup! { + "Remove the ""pause()"" call or use a proper debugging strategy." + }), + ) + } +} diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_skipped_test.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_skipped_test.rs new file mode 100644 index 000000000000..ff43f1cdb4c0 --- /dev/null +++ b/crates/biome_js_analyze/src/lint/nursery/no_playwright_skipped_test.rs @@ -0,0 +1,130 @@ +use biome_analyze::{ + context::RuleContext, declare_lint_rule, Ast, Rule, RuleDiagnostic, RuleSource, +}; +use biome_console::markup; +use biome_js_syntax::{JsCallExpression, JsStaticMemberExpression}; +use biome_rowan::AstNode; + +declare_lint_rule! { + /// Disallow usage of `.skip` annotation in Playwright tests. + /// + /// Skipped tests using `.skip` should typically not be committed to version control. + /// They indicate incomplete work that should either be completed or removed. + /// + /// ## Examples + /// + /// ### Invalid + /// + /// ```js,expect_diagnostic + /// test.skip('skip this test', async ({ page }) => {}); + /// ``` + /// + /// ```js,expect_diagnostic + /// test.describe.skip('skip suite', () => { + /// test('one', async ({ page }) => {}); + /// }); + /// ``` + /// + /// ### Valid + /// + /// ```js + /// test('this test', async ({ page }) => {}); + /// ``` + /// + /// ```js + /// test.describe('test suite', () => { + /// test('one', async ({ page }) => {}); + /// }); + /// ``` + /// + pub NoPlaywrightSkippedTest { + version: "next", + name: "noPlaywrightSkippedTest", + language: "js", + sources: &[RuleSource::EslintPlaywright("no-skipped-test").same()], + recommended: false, + } +} + +impl Rule for NoPlaywrightSkippedTest { + type Query = Ast; + type State = (); + type Signals = Option; + type Options = (); + + fn run(ctx: &RuleContext) -> Self::Signals { + let call_expr = ctx.query(); + let callee = call_expr.callee().ok()?; + + // Check if this is a member expression like test.skip() or describe.skip() + let member_expr = JsStaticMemberExpression::cast_ref(callee.syntax())?; + + // Check if the member being accessed is "skip" + let member_name = member_expr.member().ok()?; + let member_text = member_name.as_js_name()?.value_token().ok()?; + + if member_text.text_trimmed() != "skip" { + return None; + } + + // Check if the object is test/describe or a chain like test.describe + let object = member_expr.object().ok()?; + + fn is_test_or_describe_object(expr: &biome_js_syntax::AnyJsExpression) -> bool { + match expr { + biome_js_syntax::AnyJsExpression::JsIdentifierExpression(id) => { + if let Ok(name) = id.name() { + if let Ok(token) = name.value_token() { + let text = token.text_trimmed(); + return text == "test" || text == "describe"; + } + } + false + } + biome_js_syntax::AnyJsExpression::JsStaticMemberExpression(member) => { + // Handle test.describe.skip or similar chains + if let Ok(member_name) = member.member() { + if let Some(name) = member_name.as_js_name() { + if let Ok(token) = name.value_token() { + let text = token.text_trimmed(); + if text == "describe" || text == "serial" || text == "parallel" { + if let Ok(obj) = member.object() { + return is_test_or_describe_object(&obj); + } + } + } + } + } + false + } + _ => false, + } + } + + if is_test_or_describe_object(&object) { + Some(()) + } else { + None + } + } + + fn diagnostic(ctx: &RuleContext, _: &Self::State) -> Option { + let node = ctx.query(); + Some( + RuleDiagnostic::new( + rule_category!(), + node.range(), + markup! { + "Unexpected skipped test using "".skip"" annotation." + }, + ) + .note(markup! { + "Skipped tests should not be committed to version control." + }) + .note(markup! { + "Either remove the test or complete the implementation and remove the "".skip"" annotation." + }), + ) + } +} + diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_useless_await.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_useless_await.rs new file mode 100644 index 000000000000..f893ac57d1b4 --- /dev/null +++ b/crates/biome_js_analyze/src/lint/nursery/no_playwright_useless_await.rs @@ -0,0 +1,344 @@ +use biome_analyze::{ + context::RuleContext, declare_lint_rule, FixKind, Ast, Rule, RuleDiagnostic, RuleSource, +}; +use biome_console::markup; +use biome_diagnostics::Applicability; +use biome_js_syntax::{ + AnyJsExpression, JsAwaitExpression, JsCallExpression, JsStaticMemberExpression, +}; +use biome_rowan::{AstNode, BatchMutationExt}; + +use crate::JsRuleAction; + +declare_lint_rule! { + /// Disallow unnecessary `await` for Playwright methods that don't return promises. + /// + /// Some Playwright methods are frequently, yet incorrectly, awaited when they return + /// synchronous values. This includes locator methods, which return locators (not promises), + /// and synchronous expect matchers. + /// + /// ## Examples + /// + /// ### Invalid + /// + /// ```js,expect_diagnostic + /// await page.locator('.my-element'); + /// ``` + /// + /// ```js,expect_diagnostic + /// await page.getByRole('button'); + /// ``` + /// + /// ```js,expect_diagnostic + /// await expect(1).toBe(1); + /// ``` + /// + /// ### Valid + /// + /// ```js + /// page.locator('.my-element'); + /// await page.locator('.my-element').click(); + /// ``` + /// + /// ```js + /// page.getByRole('button'); + /// await page.getByRole('button').click(); + /// ``` + /// + /// ```js + /// expect(1).toBe(1); + /// await expect(page.locator('.foo')).toBeVisible(); + /// ``` + /// + pub NoPlaywrightUselessAwait { + version: "next", + name: "noPlaywrightUselessAwait", + language: "js", + sources: &[RuleSource::EslintPlaywright("no-useless-await").same()], + recommended: false, + fix_kind: FixKind::Safe, + } +} + +// Locator methods that return Locator (synchronous) +const LOCATOR_METHODS: &[&str] = &[ + "and", + "first", + "getByAltText", + "getByLabel", + "getByPlaceholder", + "getByRole", + "getByTestId", + "getByText", + "getByTitle", + "last", + "locator", + "nth", + "or", +]; + +// Page/Frame methods that are synchronous +const SYNC_PAGE_METHODS: &[&str] = &[ + "childFrames", + "frame", + "frameLocator", + "frames", + "isClosed", + "isDetached", + "mainFrame", + "name", + "on", + "page", + "parentFrame", + "setDefaultNavigationTimeout", + "setDefaultTimeout", + "url", + "video", + "viewportSize", + "workers", +]; + +// Synchronous expect matchers (not Playwright-specific web-first assertions) +const SYNC_EXPECT_MATCHERS: &[&str] = &[ + "toBe", + "toBeCloseTo", + "toBeDefined", + "toBeFalsy", + "toBeGreaterThan", + "toBeGreaterThanOrEqual", + "toBeInstanceOf", + "toBeLessThan", + "toBeLessThanOrEqual", + "toBeNaN", + "toBeNull", + "toBeTruthy", + "toBeUndefined", + "toContain", + "toContainEqual", + "toEqual", + "toHaveLength", + "toHaveProperty", + "toMatch", + "toMatchObject", + "toStrictEqual", + "toThrow", + "toThrowError", +]; + +impl Rule for NoPlaywrightUselessAwait { + type Query = Ast; + type State = (); + type Signals = Option; + type Options = (); + + fn run(ctx: &RuleContext) -> Self::Signals { + let await_expr = ctx.query(); + let argument = await_expr.argument().ok()?; + + // Check if the awaited expression is a call expression + let call_expr = argument.as_js_call_expression()?; + let callee = call_expr.callee().ok()?; + + // Check for member expressions (method calls) + if let Some(member_expr) = callee.as_js_static_member_expression() { + let member_name = member_expr.member().ok()?; + let member_token = member_name.as_js_name()?.value_token().ok()?; + let method_name = member_token.text_trimmed(); + + // Check if it's a locator method + if LOCATOR_METHODS.contains(&method_name) { + return Some(()); + } + + // Check if it's a sync page method + if SYNC_PAGE_METHODS.contains(&method_name) { + // Verify it's called on page/frame + let object = member_expr.object().ok()?; + if is_page_or_frame(&object) { + return Some(()); + } + } + } + + // Check for expect calls with sync matchers + if is_sync_expect_call(&call_expr) { + return Some(()); + } + + None + } + + fn diagnostic(ctx: &RuleContext, _: &Self::State) -> Option { + let node = ctx.query(); + Some( + RuleDiagnostic::new( + rule_category!(), + node.range(), + markup! { + "Unnecessary ""await"" expression." + }, + ) + .note(markup! { + "This method does not return a Promise." + }) + .note(markup! { + "Remove the ""await"" keyword." + }), + ) + } + + fn action(ctx: &RuleContext, _: &Self::State) -> Option { + let await_expr = ctx.query(); + let argument = await_expr.argument().ok()?; + + let mut mutation = ctx.root().begin(); + // Replace the entire await expression with just its argument + mutation.replace_element( + await_expr.clone().into_syntax().into(), + argument.into_syntax().into(), + ); + + Some(JsRuleAction::new( + ctx.metadata().action_category(ctx.category(), ctx.group()), + Applicability::Always, + markup! { "Remove unnecessary await" }.to_owned(), + mutation, + )) + } +} + +fn is_page_or_frame(expr: &AnyJsExpression) -> bool { + match expr { + AnyJsExpression::JsIdentifierExpression(id) => { + if let Ok(name) = id.name() { + if let Ok(token) = name.value_token() { + let text = token.text_trimmed(); + return text == "page" + || text == "frame" + || text.ends_with("Page") + || text.ends_with("Frame"); + } + } + false + } + AnyJsExpression::JsStaticMemberExpression(member) => { + if let Ok(member_name) = member.member() { + if let Some(name) = member_name.as_js_name() { + if let Ok(token) = name.value_token() { + let text = token.text_trimmed(); + return text == "page" + || text == "frame" + || text.ends_with("Page") + || text.ends_with("Frame"); + } + } + } + false + } + _ => false, + } +} + +fn is_sync_expect_call(call_expr: &JsCallExpression) -> bool { + let callee = call_expr.callee().ok(); + + // Check if this is an expect().matcher() pattern + // The call should be a member expression where the object is expect() + let member_expr = match callee { + Some(AnyJsExpression::JsStaticMemberExpression(member)) => member, + _ => return false, + }; + + // Get the matcher name + let member = match member_expr.member().ok() { + Some(m) => m, + None => return false, + }; + let name = match member.as_js_name() { + Some(n) => n, + None => return false, + }; + let token = match name.value_token().ok() { + Some(t) => t, + None => return false, + }; + let matcher_name = token.text_trimmed().to_string(); + + if !SYNC_EXPECT_MATCHERS.contains(&matcher_name.as_str()) { + return false; + } + + // Check if the object is an expect() call + let object = member_expr.object().ok(); + if let Some(AnyJsExpression::JsCallExpression(expect_call)) = object { + let expect_callee = expect_call.callee().ok(); + + // Check if it's expect (not expect.poll or expect with resolves/rejects) + match expect_callee { + Some(AnyJsExpression::JsIdentifierExpression(id)) => { + if let Ok(name) = id.name() { + if let Ok(token) = name.value_token() { + if token.text_trimmed() == "expect" { + // Make sure there's no "poll", "resolves", or "rejects" in the chain + return !has_async_modifier(&expect_call, call_expr); + } + } + } + } + Some(AnyJsExpression::JsStaticMemberExpression(expect_member)) => { + // Check for expect.soft, but not expect.poll + if let Ok(member) = expect_member.member() { + if let Some(name) = member.as_js_name() { + if let Ok(token) = name.value_token() { + let member_text = token.text_trimmed(); + // soft is OK, poll makes it async + if member_text == "soft" { + return !has_async_modifier(&expect_call, call_expr); + } + } + } + } + } + _ => {} + } + } + + false +} + +fn has_async_modifier(expect_call: &JsCallExpression, final_call: &JsCallExpression) -> bool { + // Walk the chain from expect_call to final_call looking for "poll", "resolves", "rejects" + let mut current = final_call.syntax().clone(); + let expect_syntax = expect_call.syntax(); + + while current != *expect_syntax { + if let Some(member) = JsStaticMemberExpression::cast_ref(¤t) { + if let Ok(member_name) = member.member() { + if let Some(name) = member_name.as_js_name() { + if let Ok(token) = name.value_token() { + let text = token.text_trimmed(); + if text == "poll" || text == "resolves" || text == "rejects" { + return true; + } + } + } + } + if let Some(parent) = member.syntax().parent() { + current = parent; + } else { + break; + } + } else if let Some(call) = JsCallExpression::cast_ref(¤t) { + if let Some(parent) = call.syntax().parent() { + current = parent; + } else { + break; + } + } else { + break; + } + } + + false +} + diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_valid_describe_callback.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_valid_describe_callback.rs new file mode 100644 index 000000000000..f5cfb76d4797 --- /dev/null +++ b/crates/biome_js_analyze/src/lint/nursery/no_playwright_valid_describe_callback.rs @@ -0,0 +1,174 @@ +use biome_analyze::{ + context::RuleContext, declare_lint_rule, Ast, Rule, RuleDiagnostic, RuleSource, +}; +use biome_console::markup; +use biome_js_syntax::{ + AnyJsExpression, JsCallExpression, +}; +use biome_rowan::{AstNode, AstSeparatedList}; + +declare_lint_rule! { + /// Enforce valid `describe()` callback. + /// + /// Using an improper `describe()` callback function can lead to unexpected test errors. + /// This rule validates that describe callbacks are proper synchronous functions without parameters. + /// + /// ## Examples + /// + /// ### Invalid + /// + /// ```js,expect_diagnostic + /// test.describe('suite', async () => { + /// test('one', async ({ page }) => {}); + /// }); + /// ``` + /// + /// ```js,expect_diagnostic + /// test.describe('suite', (done) => { + /// test('one', async ({ page }) => {}); + /// }); + /// ``` + /// + /// ### Valid + /// + /// ```js + /// test.describe('suite', () => { + /// test('one', async ({ page }) => {}); + /// test('two', async ({ page }) => {}); + /// }); + /// ``` + /// + /// ```js + /// describe('suite', function() { + /// test('one', async ({ page }) => {}); + /// }); + /// ``` + /// + pub NoPlaywrightValidDescribeCallback { + version: "next", + name: "noPlaywrightValidDescribeCallback", + language: "js", + sources: &[RuleSource::EslintPlaywright("valid-describe-callback").same()], + recommended: false, + } +} + +pub enum InvalidReason { + Async, + HasParameters, +} + +impl Rule for NoPlaywrightValidDescribeCallback { + type Query = Ast; + type State = InvalidReason; + type Signals = Option; + type Options = (); + + fn run(ctx: &RuleContext) -> Self::Signals { + let call_expr = ctx.query(); + let callee = call_expr.callee().ok()?; + + // Check if this is a describe call + let is_describe = match callee { + AnyJsExpression::JsIdentifierExpression(id) => { + let name = id.name().ok()?; + let token = name.value_token().ok()?; + token.text_trimmed() == "describe" + } + AnyJsExpression::JsStaticMemberExpression(member) => { + let member_name = member.member().ok()?; + let member_text = member_name.as_js_name()?.value_token().ok()?; + + if member_text.text_trimmed() == "describe" { + // Check if object is "test" + let object = member.object().ok()?; + if let AnyJsExpression::JsIdentifierExpression(id) = object { + let name = id.name().ok()?; + let token = name.value_token().ok()?; + token.text_trimmed() == "test" + } else { + false + } + } else { + false + } + } + _ => false, + }; + + if !is_describe { + return None; + } + + // Get the callback argument (should be the second argument for describe calls) + let args = call_expr.arguments().ok()?; + let [_, Some(callback_arg)] = args.get_arguments_by_index([0, 1]) else { + return None; + }; + let callback_expr = callback_arg.as_any_js_expression()?; + + // Check if it's a function + match callback_expr { + AnyJsExpression::JsArrowFunctionExpression(arrow) => { + // Check if async + if arrow.async_token().is_some() { + return Some(InvalidReason::Async); + } + + // Check if has parameters + if let Ok(params) = arrow.parameters() { + let has_params = match params { + biome_js_syntax::AnyJsArrowFunctionParameters::AnyJsBinding(_) => true, + biome_js_syntax::AnyJsArrowFunctionParameters::JsParameters(p) => { + p.items().len() > 0 + } + }; + if has_params { + return Some(InvalidReason::HasParameters); + } + } + } + AnyJsExpression::JsFunctionExpression(func) => { + // Check if async + if func.async_token().is_some() { + return Some(InvalidReason::Async); + } + + // Check if has parameters + if let Ok(params) = func.parameters() { + if params.items().len() > 0 { + return Some(InvalidReason::HasParameters); + } + } + } + _ => return None, // Not a function, but we won't report this + } + + None + } + + fn diagnostic(ctx: &RuleContext, state: &Self::State) -> Option { + let node = ctx.query(); + + let (message, note) = match state { + InvalidReason::Async => ( + markup! { "Describe callback should not be ""async""." }, + markup! { "Remove the ""async"" keyword from the describe callback." }, + ), + InvalidReason::HasParameters => ( + markup! { "Describe callback should not have parameters." }, + markup! { "Remove parameters from the describe callback." }, + ), + }; + + Some( + RuleDiagnostic::new( + rule_category!(), + node.range(), + message, + ) + .note(note), + ) + } +} + diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_navigation.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_navigation.rs new file mode 100644 index 000000000000..cac5888690d6 --- /dev/null +++ b/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_navigation.rs @@ -0,0 +1,109 @@ +use biome_analyze::{ + context::RuleContext, declare_lint_rule, Ast, Rule, RuleDiagnostic, RuleSource, +}; +use biome_console::markup; +use biome_js_syntax::{JsCallExpression, JsStaticMemberExpression}; +use biome_rowan::AstNode; + +declare_lint_rule! { + /// Disallow using `page.waitForNavigation()`. + /// + /// Playwright's `page.waitForNavigation()` is deprecated and should be replaced with more reliable + /// alternatives like `page.waitForURL()` or `page.waitForLoadState()`. + /// + /// ## Examples + /// + /// ### Invalid + /// + /// ```js,expect_diagnostic + /// await page.waitForNavigation(); + /// ``` + /// + /// ```js,expect_diagnostic + /// await page.click('button'); + /// await page.waitForNavigation({ waitUntil: 'networkidle' }); + /// ``` + /// + /// ### Valid + /// + /// ```js + /// await page.waitForURL('/home'); + /// ``` + /// + /// ```js + /// await page.waitForLoadState('networkidle'); + /// ``` + /// + /// ```js + /// await page.goto('/home'); + /// ``` + /// + pub NoPlaywrightWaitForNavigation { + version: "next", + name: "noPlaywrightWaitForNavigation", + language: "js", + sources: &[RuleSource::EslintPlaywright("no-wait-for-navigation").same()], + recommended: false, + } +} + +impl Rule for NoPlaywrightWaitForNavigation { + type Query = Ast; + type State = (); + type Signals = Option; + type Options = (); + + fn run(ctx: &RuleContext) -> Self::Signals { + let call_expr = ctx.query(); + let callee = call_expr.callee().ok()?; + + let member_expr = JsStaticMemberExpression::cast_ref(callee.syntax())?; + + let member_name = member_expr.member().ok()?; + let member_text = member_name.as_js_name()?.value_token().ok()?; + + if member_text.text_trimmed() != "waitForNavigation" { + return None; + } + + let object = member_expr.object().ok()?; + let object_text = match object { + biome_js_syntax::AnyJsExpression::JsIdentifierExpression(id) => { + id.name().ok()?.value_token().ok()?.text_trimmed().to_string() + } + biome_js_syntax::AnyJsExpression::JsStaticMemberExpression(member) => { + member.member().ok()?.as_js_name()?.value_token().ok()?.text_trimmed().to_string() + } + _ => return None, + }; + + if object_text == "page" + || object_text == "frame" + || object_text.ends_with("Page") + || object_text.ends_with("Frame") { + Some(()) + } else { + None + } + } + + fn diagnostic(ctx: &RuleContext, _: &Self::State) -> Option { + let node = ctx.query(); + Some( + RuleDiagnostic::new( + rule_category!(), + node.range(), + markup! { + "Unexpected use of ""page.waitForNavigation()""." + }, + ) + .note(markup! { + "waitForNavigation()"" is deprecated. Use ""page.waitForURL()"" or ""page.waitForLoadState()"" instead." + }) + .note(markup! { + "These alternatives are more reliable and provide better control over navigation waiting." + }), + ) + } +} + diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_selector.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_selector.rs new file mode 100644 index 000000000000..6ce046abbc3e --- /dev/null +++ b/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_selector.rs @@ -0,0 +1,110 @@ +use biome_analyze::{ + context::RuleContext, declare_lint_rule, Ast, Rule, RuleDiagnostic, RuleSource, +}; +use biome_console::markup; +use biome_js_syntax::{JsCallExpression, JsStaticMemberExpression}; +use biome_rowan::AstNode; + +declare_lint_rule! { + /// Disallow using `page.waitForSelector()`. + /// + /// Playwright's `page.waitForSelector()` is discouraged in favor of more reliable locator-based APIs. + /// Using locators with assertions or actions automatically waits for elements to be ready. + /// + /// ## Examples + /// + /// ### Invalid + /// + /// ```js,expect_diagnostic + /// await page.waitForSelector('.submit-button'); + /// ``` + /// + /// ```js,expect_diagnostic + /// await page.waitForSelector('#dialog', { state: 'visible' }); + /// await page.click('#dialog .button'); + /// ``` + /// + /// ### Valid + /// + /// ```js + /// await page.locator('.submit-button').click(); + /// ``` + /// + /// ```js + /// await expect(page.locator('#dialog')).toBeVisible(); + /// ``` + /// + /// ```js + /// const button = page.getByRole('button', { name: 'Submit' }); + /// await button.click(); + /// ``` + /// + pub NoPlaywrightWaitForSelector { + version: "next", + name: "noPlaywrightWaitForSelector", + language: "js", + sources: &[RuleSource::EslintPlaywright("no-wait-for-selector").same()], + recommended: false, + } +} + +impl Rule for NoPlaywrightWaitForSelector { + type Query = Ast; + type State = (); + type Signals = Option; + type Options = (); + + fn run(ctx: &RuleContext) -> Self::Signals { + let call_expr = ctx.query(); + let callee = call_expr.callee().ok()?; + + let member_expr = JsStaticMemberExpression::cast_ref(callee.syntax())?; + + let member_name = member_expr.member().ok()?; + let member_text = member_name.as_js_name()?.value_token().ok()?; + + if member_text.text_trimmed() != "waitForSelector" { + return None; + } + + let object = member_expr.object().ok()?; + let object_text = match object { + biome_js_syntax::AnyJsExpression::JsIdentifierExpression(id) => { + id.name().ok()?.value_token().ok()?.text_trimmed().to_string() + } + biome_js_syntax::AnyJsExpression::JsStaticMemberExpression(member) => { + member.member().ok()?.as_js_name()?.value_token().ok()?.text_trimmed().to_string() + } + _ => return None, + }; + + if object_text == "page" + || object_text == "frame" + || object_text.ends_with("Page") + || object_text.ends_with("Frame") { + Some(()) + } else { + None + } + } + + fn diagnostic(ctx: &RuleContext, _: &Self::State) -> Option { + let node = ctx.query(); + Some( + RuleDiagnostic::new( + rule_category!(), + node.range(), + markup! { + "Unexpected use of ""page.waitForSelector()""." + }, + ) + .note(markup! { + "Use locator-based ""page.locator()"" or ""page.getByRole()"" APIs instead." + }) + .note(markup! { + "Locators automatically wait for elements to be ready, making explicit waits unnecessary." + }), + ) + } +} + diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_timeout.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_timeout.rs new file mode 100644 index 000000000000..aca2d99cbaca --- /dev/null +++ b/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_timeout.rs @@ -0,0 +1,109 @@ +use biome_analyze::{ + context::RuleContext, declare_lint_rule, Ast, Rule, RuleDiagnostic, RuleSource, +}; +use biome_console::markup; +use biome_js_syntax::{JsCallExpression, JsStaticMemberExpression}; +use biome_rowan::AstNode; + +declare_lint_rule! { + /// Disallow using `page.waitForTimeout()`. + /// + /// Playwright provides methods like `page.waitForLoadState()`, `page.waitForURL()`, + /// and `page.waitForFunction()` which are better alternatives to using hardcoded timeouts. + /// These methods wait for specific conditions and are more reliable than arbitrary timeouts. + /// + /// ## Examples + /// + /// ### Invalid + /// + /// ```js,expect_diagnostic + /// await page.waitForTimeout(5000); + /// ``` + /// + /// ```js,expect_diagnostic + /// await page.waitForTimeout(1000); + /// ``` + /// + /// ### Valid + /// + /// ```js + /// await page.waitForLoadState(); + /// ``` + /// + /// ```js + /// await page.waitForURL('/home'); + /// ``` + /// + /// ```js + /// await page.waitForFunction(() => window.innerWidth < 100); + /// ``` + /// + pub NoPlaywrightWaitForTimeout { + version: "next", + name: "noPlaywrightWaitForTimeout", + language: "js", + sources: &[RuleSource::EslintPlaywright("no-wait-for-timeout").same()], + recommended: false, + } +} + +impl Rule for NoPlaywrightWaitForTimeout { + type Query = Ast; + type State = (); + type Signals = Option; + type Options = (); + + fn run(ctx: &RuleContext) -> Self::Signals { + let call_expr = ctx.query(); + let callee = call_expr.callee().ok()?; + + let member_expr = JsStaticMemberExpression::cast_ref(callee.syntax())?; + + let member_name = member_expr.member().ok()?; + let member_text = member_name.as_js_name()?.value_token().ok()?; + + if member_text.text_trimmed() != "waitForTimeout" { + return None; + } + + let object = member_expr.object().ok()?; + let object_text = match object { + biome_js_syntax::AnyJsExpression::JsIdentifierExpression(id) => { + id.name().ok()?.value_token().ok()?.text_trimmed().to_string() + } + biome_js_syntax::AnyJsExpression::JsStaticMemberExpression(member) => { + member.member().ok()?.as_js_name()?.value_token().ok()?.text_trimmed().to_string() + } + _ => return None, + }; + + if object_text == "page" + || object_text == "frame" + || object_text.ends_with("Page") + || object_text.ends_with("Frame") { + Some(()) + } else { + None + } + } + + fn diagnostic(ctx: &RuleContext, _: &Self::State) -> Option { + let node = ctx.query(); + Some( + RuleDiagnostic::new( + rule_category!(), + node.range(), + markup! { + "Unexpected use of ""page.waitForTimeout()""." + }, + ) + .note(markup! { + "Prefer using built-in wait methods like ""waitForLoadState()"", ""waitForURL()"", or ""waitForFunction()"" instead." + }) + .note(markup! { + "Hardcoded timeouts are flaky and make tests slower. Use conditions that wait for specific events." + }), + ) + } +} + diff --git a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/expect-async-matcher.js b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/expect-async-matcher.js new file mode 100644 index 000000000000..4407eebd327f --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/expect-async-matcher.js @@ -0,0 +1,4 @@ +test('example', async ({ page }) => { + expect(page).toBeVisible(); +}); + diff --git a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/expect-async-matcher.js.snap b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/expect-async-matcher.js.snap new file mode 100644 index 000000000000..8ac3c010bebb --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/expect-async-matcher.js.snap @@ -0,0 +1,39 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 152 +expression: expect-async-matcher.js +--- +# Input +```js +test('example', async ({ page }) => { + expect(page).toBeVisible(); +}); + + +``` + +# Diagnostics +``` +expect-async-matcher.js:2:5 lint/nursery/missingPlaywrightAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Async matcher toBeVisible must be awaited or returned. + + 1 │ test('example', async ({ page }) => { + > 2 │ expect(page).toBeVisible(); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^ + 3 │ }); + 4 │ + + i Add await before the expect call or return the promise. + + i Unsafe fix: Add await + + 1 1 │ test('example', async ({ page }) => { + 2 │ - ····expect(page).toBeVisible(); + 2 │ + ····await + 3 │ + ····expect(page).toBeVisible(); + 3 4 │ }); + 4 5 │ + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/expect-poll.js b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/expect-poll.js new file mode 100644 index 000000000000..d3c6a39982fe --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/expect-poll.js @@ -0,0 +1,4 @@ +test('example', async () => { + expect.poll(() => foo).toBe(true); +}); + diff --git a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/expect-poll.js.snap b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/expect-poll.js.snap new file mode 100644 index 000000000000..d7a3c166b590 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/expect-poll.js.snap @@ -0,0 +1,13 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 152 +expression: expect-poll.js +--- +# Input +```js +test('example', async () => { + expect.poll(() => foo).toBe(true); +}); + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/test-step.js b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/test-step.js new file mode 100644 index 000000000000..4b9cc8b33d18 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/test-step.js @@ -0,0 +1,6 @@ +test('example', async ({ page }) => { + test.step('clicks button', async () => { + await page.click('button'); + }); +}); + diff --git a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/test-step.js.snap b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/test-step.js.snap new file mode 100644 index 000000000000..9bc75a94e2da --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/test-step.js.snap @@ -0,0 +1,44 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 152 +expression: test-step.js +--- +# Input +```js +test('example', async ({ page }) => { + test.step('clicks button', async () => { + await page.click('button'); + }); +}); + + +``` + +# Diagnostics +``` +test-step.js:2:5 lint/nursery/missingPlaywrightAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i test.step must be awaited or returned. + + 1 │ test('example', async ({ page }) => { + > 2 │ test.step('clicks button', async () => { + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + > 3 │ await page.click('button'); + > 4 │ }); + │ ^^ + 5 │ }); + 6 │ + + i Add await before the test.step call or return the promise. + + i Unsafe fix: Add await + + 1 1 │ test('example', async ({ page }) => { + 2 │ - ····test.step('clicks·button',·async·()·=>·{ + 2 │ + ····await + 3 │ + ····test.step('clicks·button',·async·()·=>·{ + 3 4 │ await page.click('button'); + 4 5 │ }); + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/awaited.js b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/awaited.js new file mode 100644 index 000000000000..b77d986538d5 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/awaited.js @@ -0,0 +1,6 @@ +test('example', async ({ page }) => { + await expect(page).toBeVisible(); + await test.step('step', async () => {}); + await expect.poll(() => foo).toBe(true); +}); + diff --git a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/awaited.js.snap b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/awaited.js.snap new file mode 100644 index 000000000000..75b002489bab --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/awaited.js.snap @@ -0,0 +1,15 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 152 +expression: awaited.js +--- +# Input +```js +test('example', async ({ page }) => { + await expect(page).toBeVisible(); + await test.step('step', async () => {}); + await expect.poll(() => foo).toBe(true); +}); + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/promise-all.js b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/promise-all.js new file mode 100644 index 000000000000..aa28d8f317de --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/promise-all.js @@ -0,0 +1,7 @@ +test('example', async ({ page }) => { + await Promise.all([ + expect(page.locator('.one')).toBeVisible(), + expect(page.locator('.two')).toBeVisible() + ]); +}); + diff --git a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/promise-all.js.snap b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/promise-all.js.snap new file mode 100644 index 000000000000..70c2648dc396 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/promise-all.js.snap @@ -0,0 +1,16 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 152 +expression: promise-all.js +--- +# Input +```js +test('example', async ({ page }) => { + await Promise.all([ + expect(page.locator('.one')).toBeVisible(), + expect(page.locator('.two')).toBeVisible() + ]); +}); + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/returned.js b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/returned.js new file mode 100644 index 000000000000..588f34a1820b --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/returned.js @@ -0,0 +1,6 @@ +test('example', async ({ page }) => { + return expect(page).toBeVisible(); +}); + +test('arrow', async ({ page }) => expect(page).toBeVisible()); + diff --git a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/returned.js.snap b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/returned.js.snap new file mode 100644 index 000000000000..606f7c3a2832 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/returned.js.snap @@ -0,0 +1,15 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 152 +expression: returned.js +--- +# Input +```js +test('example', async ({ page }) => { + return expect(page).toBeVisible(); +}); + +test('arrow', async ({ page }) => expect(page).toBeVisible()); + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/dollar-dollar.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/dollar-dollar.js new file mode 100644 index 000000000000..f39696719c28 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/dollar-dollar.js @@ -0,0 +1,2 @@ +const buttons = await page.$$('.btn'); + diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/dollar-dollar.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/dollar-dollar.js.snap new file mode 100644 index 000000000000..cc6723b2d3e7 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/dollar-dollar.js.snap @@ -0,0 +1,30 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 152 +expression: dollar-dollar.js +--- +# Input +```js +const buttons = await page.$$('.btn'); + + +``` + +# Diagnostics +``` +dollar-dollar.js:1:23 lint/nursery/noPlaywrightElementHandle ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected use of element handles. + + > 1 │ const buttons = await page.$$('.btn'); + │ ^^^^^^^^^^^^^^^ + 2 │ + + i Element handles like page.$$() are discouraged. + + i Use page.locator() or other locator methods like getByRole() instead. + + i Locators auto-wait and are more reliable than element handles. + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/dollar.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/dollar.js new file mode 100644 index 000000000000..37678605f932 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/dollar.js @@ -0,0 +1,2 @@ +const button = await page.$('button'); + diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/dollar.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/dollar.js.snap new file mode 100644 index 000000000000..5e75193b979f --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/dollar.js.snap @@ -0,0 +1,30 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 152 +expression: dollar.js +--- +# Input +```js +const button = await page.$('button'); + + +``` + +# Diagnostics +``` +dollar.js:1:22 lint/nursery/noPlaywrightElementHandle ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected use of element handles. + + > 1 │ const button = await page.$('button'); + │ ^^^^^^^^^^^^^^^^ + 2 │ + + i Element handles like page.$() are discouraged. + + i Use page.locator() or other locator methods like getByRole() instead. + + i Locators auto-wait and are more reliable than element handles. + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/frame.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/frame.js new file mode 100644 index 000000000000..fd5a901bf9c2 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/frame.js @@ -0,0 +1,2 @@ +const element = await frame.$('#element'); + diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/frame.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/frame.js.snap new file mode 100644 index 000000000000..0e49347117db --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/frame.js.snap @@ -0,0 +1,30 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 152 +expression: frame.js +--- +# Input +```js +const element = await frame.$('#element'); + + +``` + +# Diagnostics +``` +frame.js:1:23 lint/nursery/noPlaywrightElementHandle ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected use of element handles. + + > 1 │ const element = await frame.$('#element'); + │ ^^^^^^^^^^^^^^^^^^^ + 2 │ + + i Element handles like page.$() are discouraged. + + i Use page.locator() or other locator methods like getByRole() instead. + + i Locators auto-wait and are more reliable than element handles. + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/valid/locator.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/valid/locator.js new file mode 100644 index 000000000000..2d844638047c --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/valid/locator.js @@ -0,0 +1,8 @@ +const button = page.locator('button'); +await button.click(); + +const buttons = page.locator('.btn'); +await expect(buttons).toHaveCount(3); + +await page.getByRole('button', { name: 'Submit' }).click(); + diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/valid/locator.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/valid/locator.js.snap new file mode 100644 index 000000000000..82584ecb9a28 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/valid/locator.js.snap @@ -0,0 +1,17 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 152 +expression: locator.js +--- +# Input +```js +const button = page.locator('button'); +await button.click(); + +const buttons = page.locator('.btn'); +await expect(buttons).toHaveCount(3); + +await page.getByRole('button', { name: 'Submit' }).click(); + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/eval-all.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/eval-all.js new file mode 100644 index 000000000000..3c05cd63d19b --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/eval-all.js @@ -0,0 +1,2 @@ +const texts = await page.$$eval('.foo', els => els.map(el => el.textContent)); + diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/eval-all.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/eval-all.js.snap new file mode 100644 index 000000000000..421ffd7924e3 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/eval-all.js.snap @@ -0,0 +1,28 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 152 +expression: eval-all.js +--- +# Input +```js +const texts = await page.$$eval('.foo', els => els.map(el => el.textContent)); + + +``` + +# Diagnostics +``` +eval-all.js:1:21 lint/nursery/noPlaywrightEval ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected use of page.$$eval(). + + > 1 │ const texts = await page.$$eval('.foo', els => els.map(el => el.textContent)); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 2 │ + + i Use locator.evaluateAll() instead. + + i Locator-based evaluation is more reliable and follows Playwright's recommended patterns. + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/eval.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/eval.js new file mode 100644 index 000000000000..702d6df01aea --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/eval.js @@ -0,0 +1,2 @@ +await page.$eval('.foo', el => el.textContent); + diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/eval.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/eval.js.snap new file mode 100644 index 000000000000..9046a2c123d6 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/eval.js.snap @@ -0,0 +1,28 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 152 +expression: eval.js +--- +# Input +```js +await page.$eval('.foo', el => el.textContent); + + +``` + +# Diagnostics +``` +eval.js:1:7 lint/nursery/noPlaywrightEval ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected use of page.$eval(). + + > 1 │ await page.$eval('.foo', el => el.textContent); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 2 │ + + i Use locator.evaluate() instead. + + i Locator-based evaluation is more reliable and follows Playwright's recommended patterns. + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/valid/locator-evaluate.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/valid/locator-evaluate.js new file mode 100644 index 000000000000..434d29c63576 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/valid/locator-evaluate.js @@ -0,0 +1,3 @@ +const text = await page.locator('.foo').evaluate(el => el.textContent); +const texts = await page.locator('.foo').evaluateAll(els => els.map(el => el.textContent)); + diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/valid/locator-evaluate.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/valid/locator-evaluate.js.snap new file mode 100644 index 000000000000..4805d424a45c --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/valid/locator-evaluate.js.snap @@ -0,0 +1,12 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 152 +expression: locator-evaluate.js +--- +# Input +```js +const text = await page.locator('.foo').evaluate(el => el.textContent); +const texts = await page.locator('.foo').evaluateAll(els => els.map(el => el.textContent)); + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightFocusedTest/invalid/describe-only.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightFocusedTest/invalid/describe-only.js new file mode 100644 index 000000000000..2b18e341690a --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightFocusedTest/invalid/describe-only.js @@ -0,0 +1,5 @@ +test.describe.only('focus suite', () => { + test('one', async ({ page }) => {}); + test('two', async ({ page }) => {}); +}); + diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightFocusedTest/invalid/describe-only.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightFocusedTest/invalid/describe-only.js.snap new file mode 100644 index 000000000000..14b7e6a25634 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightFocusedTest/invalid/describe-only.js.snap @@ -0,0 +1,35 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 152 +expression: describe-only.js +--- +# Input +```js +test.describe.only('focus suite', () => { + test('one', async ({ page }) => {}); + test('two', async ({ page }) => {}); +}); + + +``` + +# Diagnostics +``` +describe-only.js:1:1 lint/nursery/noPlaywrightFocusedTest ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected focused test using .only annotation. + + > 1 │ test.describe.only('focus suite', () => { + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + > 2 │ test('one', async ({ page }) => {}); + > 3 │ test('two', async ({ page }) => {}); + > 4 │ }); + │ ^^ + 5 │ + + i Focused tests should not be committed to version control as they prevent other tests from running. + + i Remove the .only annotation to run all tests. + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightFocusedTest/invalid/describe-parallel-only.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightFocusedTest/invalid/describe-parallel-only.js new file mode 100644 index 000000000000..4c2e33dca173 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightFocusedTest/invalid/describe-parallel-only.js @@ -0,0 +1,5 @@ +test.describe.parallel.only('focus two tests in parallel mode', () => { + test('one', async ({ page }) => {}); + test('two', async ({ page }) => {}); +}); + diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightFocusedTest/invalid/describe-parallel-only.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightFocusedTest/invalid/describe-parallel-only.js.snap new file mode 100644 index 000000000000..520df026971a --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightFocusedTest/invalid/describe-parallel-only.js.snap @@ -0,0 +1,35 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 152 +expression: describe-parallel-only.js +--- +# Input +```js +test.describe.parallel.only('focus two tests in parallel mode', () => { + test('one', async ({ page }) => {}); + test('two', async ({ page }) => {}); +}); + + +``` + +# Diagnostics +``` +describe-parallel-only.js:1:1 lint/nursery/noPlaywrightFocusedTest ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected focused test using .only annotation. + + > 1 │ test.describe.parallel.only('focus two tests in parallel mode', () => { + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + > 2 │ test('one', async ({ page }) => {}); + > 3 │ test('two', async ({ page }) => {}); + > 4 │ }); + │ ^^ + 5 │ + + i Focused tests should not be committed to version control as they prevent other tests from running. + + i Remove the .only annotation to run all tests. + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightFocusedTest/invalid/test-only.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightFocusedTest/invalid/test-only.js new file mode 100644 index 000000000000..5bace96b9226 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightFocusedTest/invalid/test-only.js @@ -0,0 +1,2 @@ +test.only('focus this test', async ({ page }) => {}); + diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightFocusedTest/invalid/test-only.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightFocusedTest/invalid/test-only.js.snap new file mode 100644 index 000000000000..3764bef27690 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightFocusedTest/invalid/test-only.js.snap @@ -0,0 +1,28 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 152 +expression: test-only.js +--- +# Input +```js +test.only('focus this test', async ({ page }) => {}); + + +``` + +# Diagnostics +``` +test-only.js:1:1 lint/nursery/noPlaywrightFocusedTest ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected focused test using .only annotation. + + > 1 │ test.only('focus this test', async ({ page }) => {}); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 2 │ + + i Focused tests should not be committed to version control as they prevent other tests from running. + + i Remove the .only annotation to run all tests. + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/click.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/click.js new file mode 100644 index 000000000000..161bf72fbcd3 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/click.js @@ -0,0 +1,3 @@ +await page.locator('button').click({ force: true }); + + diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/click.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/click.js.snap new file mode 100644 index 000000000000..65ae29187720 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/click.js.snap @@ -0,0 +1,29 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 152 +expression: click.js +--- +# Input +```js +await page.locator('button').click({ force: true }); + + + +``` + +# Diagnostics +``` +click.js:1:7 lint/nursery/noPlaywrightForceOption ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected use of { force: true } option. + + > 1 │ await page.locator('button').click({ force: true }); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 2 │ + + i The force option bypasses actionability checks and can lead to unreliable tests. + + i Fix the underlying issue instead of forcing the action. + + +``` diff --git "a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/click.js.snap\\" "b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/click.js.snap\\" new file mode 100644 index 000000000000..9cf9ba0f11a5 --- /dev/null +++ "b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/click.js.snap\\" @@ -0,0 +1,28 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 152 +expression: click.js +--- +# Input +```js +await page.locator('button').click({ force: true }); + + +``` + +# Diagnostics +``` +click.js:1:7 lint/nursery/noPlaywrightForceOption ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected use of { force: true } option. + + > 1 │ await page.locator('button').click({ force: true }); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 2 │ + + i The force option bypasses actionability checks and can lead to unreliable tests. + + i Fix the underlying issue instead of forcing the action. + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/fill.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/fill.js new file mode 100644 index 000000000000..12c6deca2821 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/fill.js @@ -0,0 +1,3 @@ +await page.locator('input').fill('text', { force: true }); + + diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/fill.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/fill.js.snap new file mode 100644 index 000000000000..e5bff191ebbc --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/fill.js.snap @@ -0,0 +1,29 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 152 +expression: fill.js +--- +# Input +```js +await page.locator('input').fill('text', { force: true }); + + + +``` + +# Diagnostics +``` +fill.js:1:7 lint/nursery/noPlaywrightForceOption ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected use of { force: true } option. + + > 1 │ await page.locator('input').fill('text', { force: true }); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 2 │ + + i The force option bypasses actionability checks and can lead to unreliable tests. + + i Fix the underlying issue instead of forcing the action. + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/valid/click.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/valid/click.js new file mode 100644 index 000000000000..cb2e6186037a --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/valid/click.js @@ -0,0 +1,5 @@ +await page.locator('button').click(); +await page.locator('check').check(); +await page.locator('input').fill('text'); + + diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/valid/click.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/valid/click.js.snap new file mode 100644 index 000000000000..bf9f482c2fc2 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/valid/click.js.snap @@ -0,0 +1,14 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 152 +expression: click.js +--- +# Input +```js +await page.locator('button').click(); +await page.locator('check').check(); +await page.locator('input').fill('text'); + + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/goto.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/goto.js new file mode 100644 index 000000000000..b1495d10aee3 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/goto.js @@ -0,0 +1,3 @@ +await page.goto('https://example.com', { waitUntil: 'networkidle' }); + + diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/goto.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/goto.js.snap new file mode 100644 index 000000000000..3dd4025db38f --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/goto.js.snap @@ -0,0 +1,29 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 152 +expression: goto.js +--- +# Input +```js +await page.goto('https://example.com', { waitUntil: 'networkidle' }); + + + +``` + +# Diagnostics +``` +goto.js:1:7 lint/nursery/noPlaywrightNetworkidle ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected use of networkidle option. + + > 1 │ await page.goto('https://example.com', { waitUntil: 'networkidle' }); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 2 │ + + i The networkidle event is unreliable and can lead to flaky tests. + + i Use web-first assertions or wait for specific elements instead. + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/wait-for-load-state.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/wait-for-load-state.js new file mode 100644 index 000000000000..46c4a5436271 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/wait-for-load-state.js @@ -0,0 +1,3 @@ +await page.waitForLoadState('networkidle'); + + diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/wait-for-load-state.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/wait-for-load-state.js.snap new file mode 100644 index 000000000000..5cb43ce0c56f --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/wait-for-load-state.js.snap @@ -0,0 +1,29 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 152 +expression: wait-for-load-state.js +--- +# Input +```js +await page.waitForLoadState('networkidle'); + + + +``` + +# Diagnostics +``` +wait-for-load-state.js:1:7 lint/nursery/noPlaywrightNetworkidle ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected use of networkidle option. + + > 1 │ await page.waitForLoadState('networkidle'); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 2 │ + + i The networkidle event is unreliable and can lead to flaky tests. + + i Use web-first assertions or wait for specific elements instead. + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/valid/load.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/valid/load.js new file mode 100644 index 000000000000..1fdd1e732479 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/valid/load.js @@ -0,0 +1,5 @@ +await page.waitForLoadState('load'); +await page.goto('https://example.com'); +await page.locator('.content').waitFor(); + + diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/valid/load.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/valid/load.js.snap new file mode 100644 index 000000000000..b03ff5162d38 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/valid/load.js.snap @@ -0,0 +1,14 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 152 +expression: load.js +--- +# Input +```js +await page.waitForLoadState('load'); +await page.goto('https://example.com'); +await page.locator('.content').waitFor(); + + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/frame.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/frame.js new file mode 100644 index 000000000000..f9cb9c794f7f --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/frame.js @@ -0,0 +1,3 @@ +const frame = page.frame('iframe'); +await frame.pause(); + diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/frame.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/frame.js.snap new file mode 100644 index 000000000000..0011d4fb336d --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/frame.js.snap @@ -0,0 +1,30 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 152 +expression: frame.js +--- +# Input +```js +const frame = page.frame('iframe'); +await frame.pause(); + + +``` + +# Diagnostics +``` +frame.js:2:7 lint/nursery/noPlaywrightPagePause ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected use of page.pause(). + + 1 │ const frame = page.frame('iframe'); + > 2 │ await frame.pause(); + │ ^^^^^^^^^^^^^ + 3 │ + + i page.pause() is a debugging utility and should not be committed to version control. + + i Remove the pause() call or use a proper debugging strategy. + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/in-test.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/in-test.js new file mode 100644 index 000000000000..d6ce5b7b5b73 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/in-test.js @@ -0,0 +1,5 @@ +test('example', async ({ page }) => { + await page.click('button'); + await page.pause(); +}); + diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/in-test.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/in-test.js.snap new file mode 100644 index 000000000000..853c28686845 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/in-test.js.snap @@ -0,0 +1,34 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 152 +expression: in-test.js +--- +# Input +```js +test('example', async ({ page }) => { + await page.click('button'); + await page.pause(); +}); + + +``` + +# Diagnostics +``` +in-test.js:3:11 lint/nursery/noPlaywrightPagePause ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected use of page.pause(). + + 1 │ test('example', async ({ page }) => { + 2 │ await page.click('button'); + > 3 │ await page.pause(); + │ ^^^^^^^^^^^^ + 4 │ }); + 5 │ + + i page.pause() is a debugging utility and should not be committed to version control. + + i Remove the pause() call or use a proper debugging strategy. + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/simple.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/simple.js new file mode 100644 index 000000000000..cbb22482e37e --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/simple.js @@ -0,0 +1,2 @@ +await page.pause(); + diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/simple.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/simple.js.snap new file mode 100644 index 000000000000..322ee2c54f12 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/simple.js.snap @@ -0,0 +1,28 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 152 +expression: simple.js +--- +# Input +```js +await page.pause(); + + +``` + +# Diagnostics +``` +simple.js:1:7 lint/nursery/noPlaywrightPagePause ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected use of page.pause(). + + > 1 │ await page.pause(); + │ ^^^^^^^^^^^^ + 2 │ + + i page.pause() is a debugging utility and should not be committed to version control. + + i Remove the pause() call or use a proper debugging strategy. + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/valid/click.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/valid/click.js new file mode 100644 index 000000000000..a0466ab9edcd --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/valid/click.js @@ -0,0 +1,5 @@ +test('example', async ({ page }) => { + await page.click('button'); + await page.waitForSelector('.result'); +}); + diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/valid/click.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/valid/click.js.snap new file mode 100644 index 000000000000..3772f6c214b8 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/valid/click.js.snap @@ -0,0 +1,14 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 152 +expression: click.js +--- +# Input +```js +test('example', async ({ page }) => { + await page.click('button'); + await page.waitForSelector('.result'); +}); + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/valid/other-methods.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/valid/other-methods.js new file mode 100644 index 000000000000..d2a09c6361b8 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/valid/other-methods.js @@ -0,0 +1,4 @@ +await page.goto('https://example.com'); +await page.fill('input', 'text'); +await page.screenshot(); + diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/valid/other-methods.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/valid/other-methods.js.snap new file mode 100644 index 000000000000..01c681c25694 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/valid/other-methods.js.snap @@ -0,0 +1,13 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 152 +expression: other-methods.js +--- +# Input +```js +await page.goto('https://example.com'); +await page.fill('input', 'text'); +await page.screenshot(); + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/valid/pause-function.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/valid/pause-function.js new file mode 100644 index 000000000000..93bed2cd851d --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/valid/pause-function.js @@ -0,0 +1,7 @@ +// This should be valid - pause() is a function, not page.pause() +function pause() { + console.log('pausing'); +} + +pause(); + diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/valid/pause-function.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/valid/pause-function.js.snap new file mode 100644 index 000000000000..8461d4c173f2 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/valid/pause-function.js.snap @@ -0,0 +1,16 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 152 +expression: pause-function.js +--- +# Input +```js +// This should be valid - pause() is a function, not page.pause() +function pause() { + console.log('pausing'); +} + +pause(); + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightSkippedTest/invalid/describe-skip.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightSkippedTest/invalid/describe-skip.js new file mode 100644 index 000000000000..1e87679c8d8e --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightSkippedTest/invalid/describe-skip.js @@ -0,0 +1,5 @@ +test.describe.skip('skip suite', () => { + test('one', async ({ page }) => {}); + test('two', async ({ page }) => {}); +}); + diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightSkippedTest/invalid/describe-skip.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightSkippedTest/invalid/describe-skip.js.snap new file mode 100644 index 000000000000..65a8de46a2cd --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightSkippedTest/invalid/describe-skip.js.snap @@ -0,0 +1,35 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 152 +expression: describe-skip.js +--- +# Input +```js +test.describe.skip('skip suite', () => { + test('one', async ({ page }) => {}); + test('two', async ({ page }) => {}); +}); + + +``` + +# Diagnostics +``` +describe-skip.js:1:1 lint/nursery/noPlaywrightSkippedTest ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected skipped test using .skip annotation. + + > 1 │ test.describe.skip('skip suite', () => { + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + > 2 │ test('one', async ({ page }) => {}); + > 3 │ test('two', async ({ page }) => {}); + > 4 │ }); + │ ^^ + 5 │ + + i Skipped tests should not be committed to version control. + + i Either remove the test or complete the implementation and remove the .skip annotation. + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightSkippedTest/invalid/test-skip.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightSkippedTest/invalid/test-skip.js new file mode 100644 index 000000000000..851ef37c899b --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightSkippedTest/invalid/test-skip.js @@ -0,0 +1,2 @@ +test.skip('skip this test', async ({ page }) => {}); + diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightSkippedTest/invalid/test-skip.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightSkippedTest/invalid/test-skip.js.snap new file mode 100644 index 000000000000..fe2d38474727 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightSkippedTest/invalid/test-skip.js.snap @@ -0,0 +1,28 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 152 +expression: test-skip.js +--- +# Input +```js +test.skip('skip this test', async ({ page }) => {}); + + +``` + +# Diagnostics +``` +test-skip.js:1:1 lint/nursery/noPlaywrightSkippedTest ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected skipped test using .skip annotation. + + > 1 │ test.skip('skip this test', async ({ page }) => {}); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 2 │ + + i Skipped tests should not be committed to version control. + + i Either remove the test or complete the implementation and remove the .skip annotation. + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/expect-sync.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/expect-sync.js new file mode 100644 index 000000000000..571ed5323f4f --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/expect-sync.js @@ -0,0 +1,2 @@ +await expect(1).toBe(1); + diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/expect-sync.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/expect-sync.js.snap new file mode 100644 index 000000000000..3f1b2abd3f47 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/expect-sync.js.snap @@ -0,0 +1,32 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 152 +expression: expect-sync.js +--- +# Input +```js +await expect(1).toBe(1); + + +``` + +# Diagnostics +``` +expect-sync.js:1:1 lint/nursery/noPlaywrightUselessAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unnecessary await expression. + + > 1 │ await expect(1).toBe(1); + │ ^^^^^^^^^^^^^^^^^^^^^^^ + 2 │ + + i This method does not return a Promise. + + i Remove the await keyword. + + i Safe fix: Remove unnecessary await + + 1 │ await·expect(1).toBe(1); + │ ------ + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/getByRole.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/getByRole.js new file mode 100644 index 000000000000..bf688e110fda --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/getByRole.js @@ -0,0 +1,2 @@ +await page.getByRole('button'); + diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/getByRole.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/getByRole.js.snap new file mode 100644 index 000000000000..fe33b956d3b4 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/getByRole.js.snap @@ -0,0 +1,32 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 152 +expression: getByRole.js +--- +# Input +```js +await page.getByRole('button'); + + +``` + +# Diagnostics +``` +getByRole.js:1:1 lint/nursery/noPlaywrightUselessAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unnecessary await expression. + + > 1 │ await page.getByRole('button'); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 2 │ + + i This method does not return a Promise. + + i Remove the await keyword. + + i Safe fix: Remove unnecessary await + + 1 │ await·page.getByRole('button'); + │ ------ + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/locator.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/locator.js new file mode 100644 index 000000000000..2e461c837956 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/locator.js @@ -0,0 +1,2 @@ +await page.locator('.my-element'); + diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/locator.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/locator.js.snap new file mode 100644 index 000000000000..269691b9447b --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/locator.js.snap @@ -0,0 +1,32 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 152 +expression: locator.js +--- +# Input +```js +await page.locator('.my-element'); + + +``` + +# Diagnostics +``` +locator.js:1:1 lint/nursery/noPlaywrightUselessAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unnecessary await expression. + + > 1 │ await page.locator('.my-element'); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 2 │ + + i This method does not return a Promise. + + i Remove the await keyword. + + i Safe fix: Remove unnecessary await + + 1 │ await·page.locator('.my-element'); + │ ------ + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/page-frame.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/page-frame.js new file mode 100644 index 000000000000..5149f37fae6a --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/page-frame.js @@ -0,0 +1,4 @@ +await page.frame('iframe'); +await page.frames(); +await page.url(); + diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/page-frame.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/page-frame.js.snap new file mode 100644 index 000000000000..f0c9e7930aa3 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/page-frame.js.snap @@ -0,0 +1,79 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 152 +expression: page-frame.js +--- +# Input +```js +await page.frame('iframe'); +await page.frames(); +await page.url(); + + +``` + +# Diagnostics +``` +page-frame.js:1:1 lint/nursery/noPlaywrightUselessAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unnecessary await expression. + + > 1 │ await page.frame('iframe'); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^ + 2 │ await page.frames(); + 3 │ await page.url(); + + i This method does not return a Promise. + + i Remove the await keyword. + + i Safe fix: Remove unnecessary await + + 1 │ await·page.frame('iframe'); + │ ------ + +``` + +``` +page-frame.js:2:1 lint/nursery/noPlaywrightUselessAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unnecessary await expression. + + 1 │ await page.frame('iframe'); + > 2 │ await page.frames(); + │ ^^^^^^^^^^^^^^^^^^^ + 3 │ await page.url(); + 4 │ + + i This method does not return a Promise. + + i Remove the await keyword. + + i Safe fix: Remove unnecessary await + + 2 │ await·page.frames(); + │ ------ + +``` + +``` +page-frame.js:3:1 lint/nursery/noPlaywrightUselessAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unnecessary await expression. + + 1 │ await page.frame('iframe'); + 2 │ await page.frames(); + > 3 │ await page.url(); + │ ^^^^^^^^^^^^^^^^ + 4 │ + + i This method does not return a Promise. + + i Remove the await keyword. + + i Safe fix: Remove unnecessary await + + 3 │ await·page.url(); + │ ------ + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/valid/async-methods.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/valid/async-methods.js new file mode 100644 index 000000000000..319691319bc6 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/valid/async-methods.js @@ -0,0 +1,5 @@ +await page.locator('.my-element').click(); +await page.goto('https://example.com'); +await expect(page.locator('.foo')).toBeVisible(); +await expect.poll(() => foo).toBe(true); + diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/valid/async-methods.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/valid/async-methods.js.snap new file mode 100644 index 000000000000..7ca9ba039cf0 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/valid/async-methods.js.snap @@ -0,0 +1,14 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 152 +expression: async-methods.js +--- +# Input +```js +await page.locator('.my-element').click(); +await page.goto('https://example.com'); +await expect(page.locator('.foo')).toBeVisible(); +await expect.poll(() => foo).toBe(true); + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/valid/sync-no-await.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/valid/sync-no-await.js new file mode 100644 index 000000000000..6566793cf3b4 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/valid/sync-no-await.js @@ -0,0 +1,4 @@ +page.locator('.my-element'); +page.getByRole('button'); +expect(1).toBe(1); + diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/valid/sync-no-await.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/valid/sync-no-await.js.snap new file mode 100644 index 000000000000..6dfa34061541 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/valid/sync-no-await.js.snap @@ -0,0 +1,13 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 152 +expression: sync-no-await.js +--- +# Input +```js +page.locator('.my-element'); +page.getByRole('button'); +expect(1).toBe(1); + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/async.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/async.js new file mode 100644 index 000000000000..03eb46689518 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/async.js @@ -0,0 +1,5 @@ +test.describe('suite', async () => { + test('one', async ({ page }) => {}); +}); + + diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/async.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/async.js.snap new file mode 100644 index 000000000000..986a2444398c --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/async.js.snap @@ -0,0 +1,32 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 152 +expression: async.js +--- +# Input +```js +test.describe('suite', async () => { + test('one', async ({ page }) => {}); +}); + + + +``` + +# Diagnostics +``` +async.js:1:1 lint/nursery/noPlaywrightValidDescribeCallback ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Describe callback should not be async. + + > 1 │ test.describe('suite', async () => { + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + > 2 │ test('one', async ({ page }) => {}); + > 3 │ }); + │ ^^ + 4 │ + + i Remove the async keyword from the describe callback. + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/has-params.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/has-params.js new file mode 100644 index 000000000000..f432661fef42 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/has-params.js @@ -0,0 +1,5 @@ +test.describe('suite', (done) => { + test('one', async ({ page }) => {}); +}); + + diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/has-params.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/has-params.js.snap new file mode 100644 index 000000000000..d1c8e565a3a5 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/has-params.js.snap @@ -0,0 +1,32 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 152 +expression: has-params.js +--- +# Input +```js +test.describe('suite', (done) => { + test('one', async ({ page }) => {}); +}); + + + +``` + +# Diagnostics +``` +has-params.js:1:1 lint/nursery/noPlaywrightValidDescribeCallback ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Describe callback should not have parameters. + + > 1 │ test.describe('suite', (done) => { + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + > 2 │ test('one', async ({ page }) => {}); + > 3 │ }); + │ ^^ + 4 │ + + i Remove parameters from the describe callback. + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/valid/correct.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/valid/correct.js new file mode 100644 index 000000000000..68e1e6a2a8de --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/valid/correct.js @@ -0,0 +1,10 @@ +test.describe('suite', () => { + test('one', async ({ page }) => {}); + test('two', async ({ page }) => {}); +}); + +describe('suite', function() { + test('one', async ({ page }) => {}); +}); + + diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/valid/correct.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/valid/correct.js.snap new file mode 100644 index 000000000000..7ce115644972 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/valid/correct.js.snap @@ -0,0 +1,19 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 152 +expression: correct.js +--- +# Input +```js +test.describe('suite', () => { + test('one', async ({ page }) => {}); + test('two', async ({ page }) => {}); +}); + +describe('suite', function() { + test('one', async ({ page }) => {}); +}); + + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/invalid/simple.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/invalid/simple.js new file mode 100644 index 000000000000..d1ae89aaed0d --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/invalid/simple.js @@ -0,0 +1,2 @@ +await page.waitForNavigation(); + diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/invalid/simple.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/invalid/simple.js.snap new file mode 100644 index 000000000000..ce6cecbc880c --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/invalid/simple.js.snap @@ -0,0 +1,28 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 152 +expression: simple.js +--- +# Input +```js +await page.waitForNavigation(); + + +``` + +# Diagnostics +``` +simple.js:1:7 lint/nursery/noPlaywrightWaitForNavigation ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected use of page.waitForNavigation(). + + > 1 │ await page.waitForNavigation(); + │ ^^^^^^^^^^^^^^^^^^^^^^^^ + 2 │ + + i waitForNavigation() is deprecated. Use page.waitForURL() or page.waitForLoadState() instead. + + i These alternatives are more reliable and provide better control over navigation waiting. + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/invalid/with-options.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/invalid/with-options.js new file mode 100644 index 000000000000..070a720f1003 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/invalid/with-options.js @@ -0,0 +1,3 @@ +await page.click('button'); +await page.waitForNavigation({ waitUntil: 'networkidle' }); + diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/invalid/with-options.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/invalid/with-options.js.snap new file mode 100644 index 000000000000..f013b4dc8c7d --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/invalid/with-options.js.snap @@ -0,0 +1,30 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 152 +expression: with-options.js +--- +# Input +```js +await page.click('button'); +await page.waitForNavigation({ waitUntil: 'networkidle' }); + + +``` + +# Diagnostics +``` +with-options.js:2:7 lint/nursery/noPlaywrightWaitForNavigation ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected use of page.waitForNavigation(). + + 1 │ await page.click('button'); + > 2 │ await page.waitForNavigation({ waitUntil: 'networkidle' }); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 3 │ + + i waitForNavigation() is deprecated. Use page.waitForURL() or page.waitForLoadState() instead. + + i These alternatives are more reliable and provide better control over navigation waiting. + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/valid/alternatives.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/valid/alternatives.js new file mode 100644 index 000000000000..79b01cc7bd4e --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/valid/alternatives.js @@ -0,0 +1,4 @@ +await page.waitForURL('/home'); +await page.waitForLoadState('networkidle'); +await page.goto('/home'); + diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/valid/alternatives.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/valid/alternatives.js.snap new file mode 100644 index 000000000000..130aca382ae6 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/valid/alternatives.js.snap @@ -0,0 +1,13 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 152 +expression: alternatives.js +--- +# Input +```js +await page.waitForURL('/home'); +await page.waitForLoadState('networkidle'); +await page.goto('/home'); + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForSelector/invalid/simple.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForSelector/invalid/simple.js new file mode 100644 index 000000000000..dd0c5bbe8941 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForSelector/invalid/simple.js @@ -0,0 +1,2 @@ +await page.waitForSelector('.submit-button'); + diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForSelector/invalid/simple.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForSelector/invalid/simple.js.snap new file mode 100644 index 000000000000..ef674a3c8a85 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForSelector/invalid/simple.js.snap @@ -0,0 +1,28 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 152 +expression: simple.js +--- +# Input +```js +await page.waitForSelector('.submit-button'); + + +``` + +# Diagnostics +``` +simple.js:1:7 lint/nursery/noPlaywrightWaitForSelector ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected use of page.waitForSelector(). + + > 1 │ await page.waitForSelector('.submit-button'); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 2 │ + + i Use locator-based page.locator() or page.getByRole() APIs instead. + + i Locators automatically wait for elements to be ready, making explicit waits unnecessary. + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForSelector/invalid/with-state.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForSelector/invalid/with-state.js new file mode 100644 index 000000000000..ca69b4010007 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForSelector/invalid/with-state.js @@ -0,0 +1,3 @@ +await page.waitForSelector('#dialog', { state: 'visible' }); +await page.click('#dialog .button'); + diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForSelector/invalid/with-state.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForSelector/invalid/with-state.js.snap new file mode 100644 index 000000000000..0392523664fe --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForSelector/invalid/with-state.js.snap @@ -0,0 +1,30 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 152 +expression: with-state.js +--- +# Input +```js +await page.waitForSelector('#dialog', { state: 'visible' }); +await page.click('#dialog .button'); + + +``` + +# Diagnostics +``` +with-state.js:1:7 lint/nursery/noPlaywrightWaitForSelector ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected use of page.waitForSelector(). + + > 1 │ await page.waitForSelector('#dialog', { state: 'visible' }); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 2 │ await page.click('#dialog .button'); + 3 │ + + i Use locator-based page.locator() or page.getByRole() APIs instead. + + i Locators automatically wait for elements to be ready, making explicit waits unnecessary. + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForSelector/valid/locators.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForSelector/valid/locators.js new file mode 100644 index 000000000000..b8499282ab09 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForSelector/valid/locators.js @@ -0,0 +1,5 @@ +await page.locator('.submit-button').click(); +await expect(page.locator('#dialog')).toBeVisible(); +const button = page.getByRole('button', { name: 'Submit' }); +await button.click(); + diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForSelector/valid/locators.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForSelector/valid/locators.js.snap new file mode 100644 index 000000000000..15f288943037 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForSelector/valid/locators.js.snap @@ -0,0 +1,14 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 152 +expression: locators.js +--- +# Input +```js +await page.locator('.submit-button').click(); +await expect(page.locator('#dialog')).toBeVisible(); +const button = page.getByRole('button', { name: 'Submit' }); +await button.click(); + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/invalid/in-test.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/invalid/in-test.js new file mode 100644 index 000000000000..03d6728f28ef --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/invalid/in-test.js @@ -0,0 +1,5 @@ +test('wait', async ({ page }) => { + await page.click('button'); + await page.waitForTimeout(1000); +}); + diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/invalid/in-test.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/invalid/in-test.js.snap new file mode 100644 index 000000000000..d34e388d0e05 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/invalid/in-test.js.snap @@ -0,0 +1,34 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 152 +expression: in-test.js +--- +# Input +```js +test('wait', async ({ page }) => { + await page.click('button'); + await page.waitForTimeout(1000); +}); + + +``` + +# Diagnostics +``` +in-test.js:3:11 lint/nursery/noPlaywrightWaitForTimeout ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected use of page.waitForTimeout(). + + 1 │ test('wait', async ({ page }) => { + 2 │ await page.click('button'); + > 3 │ await page.waitForTimeout(1000); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^ + 4 │ }); + 5 │ + + i Prefer using built-in wait methods like waitForLoadState(), waitForURL(), or waitForFunction() instead. + + i Hardcoded timeouts are flaky and make tests slower. Use conditions that wait for specific events. + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/invalid/simple.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/invalid/simple.js new file mode 100644 index 000000000000..48de15e92214 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/invalid/simple.js @@ -0,0 +1,2 @@ +await page.waitForTimeout(5000); + diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/invalid/simple.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/invalid/simple.js.snap new file mode 100644 index 000000000000..82397701d722 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/invalid/simple.js.snap @@ -0,0 +1,28 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 152 +expression: simple.js +--- +# Input +```js +await page.waitForTimeout(5000); + + +``` + +# Diagnostics +``` +simple.js:1:7 lint/nursery/noPlaywrightWaitForTimeout ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected use of page.waitForTimeout(). + + > 1 │ await page.waitForTimeout(5000); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^ + 2 │ + + i Prefer using built-in wait methods like waitForLoadState(), waitForURL(), or waitForFunction() instead. + + i Hardcoded timeouts are flaky and make tests slower. Use conditions that wait for specific events. + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/valid/other-methods.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/valid/other-methods.js new file mode 100644 index 000000000000..576527a28c92 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/valid/other-methods.js @@ -0,0 +1,4 @@ +await page.waitForLoadState(); +await page.waitForURL('/home'); +await page.waitForFunction(() => window.innerWidth < 100); + diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/valid/other-methods.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/valid/other-methods.js.snap new file mode 100644 index 000000000000..483eb4cdf238 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/valid/other-methods.js.snap @@ -0,0 +1,13 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 152 +expression: other-methods.js +--- +# Input +```js +await page.waitForLoadState(); +await page.waitForURL('/home'); +await page.waitForFunction(() => window.innerWidth < 100); + + +``` From a275448811f674aa56ed7364611e0b8ea9ebe2a5 Mon Sep 17 00:00:00 2001 From: Josh Delsman <12201+voxxit@users.noreply.github.com> Date: Fri, 17 Oct 2025 03:16:19 -0500 Subject: [PATCH 02/24] Update crates/biome_analyze/src/rule.rs Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- crates/biome_analyze/src/rule.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/biome_analyze/src/rule.rs b/crates/biome_analyze/src/rule.rs index 7936f46c4cce..cda426d5d35d 100644 --- a/crates/biome_analyze/src/rule.rs +++ b/crates/biome_analyze/src/rule.rs @@ -382,7 +382,7 @@ impl RuleSource { Self::EslintVueJs(rule_name) => format!("https://eslint.vuejs.org/rules/{rule_name}"), Self::EslintPackageJson(rule_name) => format!("https://github.com/JoshuaKGoldberg/eslint-plugin-package-json/blob/main/docs/rules/{rule_name}.md"), Self::EslintPackageJsonDependencies(rule_name) => format!("https://github.com/idan-at/eslint-plugin-package-json-dependencies/blob/master/docs/rules/{rule_name}.md"), - Self::EslintPlaywright(rule_name) => format!("https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/{rule_name}.md"), + Self::EslintPlaywright(rule_name) => format!("https://github.com/playwright-community/eslint-plugin-playwright/blob/main/docs/rules/{rule_name}.md"), } } From a0737d662661d389762ff6792022882561b9dee6 Mon Sep 17 00:00:00 2001 From: Josh Delsman <12201+voxxit@users.noreply.github.com> Date: Fri, 17 Oct 2025 03:17:00 -0500 Subject: [PATCH 03/24] Update crates/biome_js_analyze/src/lint/nursery/missing_playwright_await.rs Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .../src/lint/nursery/missing_playwright_await.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/biome_js_analyze/src/lint/nursery/missing_playwright_await.rs b/crates/biome_js_analyze/src/lint/nursery/missing_playwright_await.rs index 884bc9472822..43b27d9e430b 100644 --- a/crates/biome_js_analyze/src/lint/nursery/missing_playwright_await.rs +++ b/crates/biome_js_analyze/src/lint/nursery/missing_playwright_await.rs @@ -61,7 +61,7 @@ declare_lint_rule! { language: "js", sources: &[RuleSource::EslintPlaywright("missing-playwright-await").same()], recommended: false, - fix_kind: FixKind::Safe, + fix_kind: FixKind::Unsafe, } } From c353d562255409c9f4dea566280c27238f0b804a Mon Sep 17 00:00:00 2001 From: Josh Delsman <12201+voxxit@users.noreply.github.com> Date: Fri, 17 Oct 2025 03:24:00 -0500 Subject: [PATCH 04/24] refactor(biome_js_analyze): enhance missingPlaywrightAwait rule and add tests Refactor the missingPlaywrightAwait lint rule to improve its logic for checking if call expressions are properly awaited or returned. The function is_properly_handled now verifies both direct awaits and Promise.all usage. Additionally, introduce a new test case to validate the behavior of the rule when encountering Promise.all without awaits, ensuring accurate diagnostics are provided. New test file: - promise-all-not-awaited.js - promise-all-not-awaited.js.snap This update enhances the linting capabilities for Playwright async APIs. --- .../lint/nursery/missing_playwright_await.rs | 26 ++++--- .../invalid/promise-all-not-awaited.js | 7 ++ .../invalid/promise-all-not-awaited.js.snap | 70 +++++++++++++++++++ 3 files changed, 95 insertions(+), 8 deletions(-) create mode 100644 crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/promise-all-not-awaited.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/promise-all-not-awaited.js.snap diff --git a/crates/biome_js_analyze/src/lint/nursery/missing_playwright_await.rs b/crates/biome_js_analyze/src/lint/nursery/missing_playwright_await.rs index 43b27d9e430b..019b9cb130ae 100644 --- a/crates/biome_js_analyze/src/lint/nursery/missing_playwright_await.rs +++ b/crates/biome_js_analyze/src/lint/nursery/missing_playwright_await.rs @@ -5,8 +5,7 @@ use biome_console::markup; use biome_diagnostics::Applicability; use biome_js_factory::make; use biome_js_syntax::{ - AnyJsExpression, JsArrowFunctionExpression, JsCallExpression, - JsStaticMemberExpression, T, + AnyJsExpression, JsArrowFunctionExpression, JsCallExpression, T, }; use biome_rowan::{AstNode, BatchMutationExt}; @@ -328,7 +327,8 @@ fn has_expect_in_chain(expr: &AnyJsExpression) -> bool { } } -fn is_properly_handled(call_expr: &JsCallExpression) -> bool { +/// Checks if a call expression is directly awaited or returned (without checking Promise.all) +fn is_call_awaited_or_returned(call_expr: &JsCallExpression) -> bool { let parent = call_expr.syntax().parent(); // Check if it's awaited @@ -361,18 +361,28 @@ fn is_properly_handled(call_expr: &JsCallExpression) -> bool { current = node.parent(); } - // Check if it's in Promise.all - if is_in_promise_all(call_expr) { + false +} + +fn is_properly_handled(call_expr: &JsCallExpression) -> bool { + // Check if it's directly awaited or returned + if is_call_awaited_or_returned(call_expr) { return true; } + // Check if it's in Promise.all - if so, verify that the Promise.all call itself is properly handled + if let Some(promise_all_call) = find_enclosing_promise_all(call_expr) { + // Check if the Promise.all call is awaited or returned + return is_call_awaited_or_returned(&promise_all_call); + } + // Check if it's assigned to a variable that's later awaited // This is complex and would require flow analysis, skipping for now false } -fn is_in_promise_all(call_expr: &JsCallExpression) -> bool { +fn find_enclosing_promise_all(call_expr: &JsCallExpression) -> Option { let mut current = call_expr.syntax().parent(); while let Some(node) = current { @@ -386,7 +396,7 @@ fn is_in_promise_all(call_expr: &JsCallExpression) -> bool { if let Some(promise_call) = call_args_parent.parent() { if let Some(call) = JsCallExpression::cast_ref(&promise_call) { if is_promise_all(&call) { - return true; + return Some(call); } } } @@ -408,7 +418,7 @@ fn is_in_promise_all(call_expr: &JsCallExpression) -> bool { current = node.parent(); } - false + None } fn is_promise_all(call: &JsCallExpression) -> bool { diff --git a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/promise-all-not-awaited.js b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/promise-all-not-awaited.js new file mode 100644 index 000000000000..44b609d054f1 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/promise-all-not-awaited.js @@ -0,0 +1,7 @@ +test('example', async ({ page }) => { + Promise.all([ + expect(page.locator('.one')).toBeVisible(), + expect(page.locator('.two')).toBeVisible() + ]); +}); + diff --git a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/promise-all-not-awaited.js.snap b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/promise-all-not-awaited.js.snap new file mode 100644 index 000000000000..3cfc16f2dcfc --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/promise-all-not-awaited.js.snap @@ -0,0 +1,70 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: promise-all-not-awaited.js +--- +# Input +```js +test('example', async ({ page }) => { + Promise.all([ + expect(page.locator('.one')).toBeVisible(), + expect(page.locator('.two')).toBeVisible() + ]); +}); + + +``` + +# Diagnostics +``` +promise-all-not-awaited.js:3:9 lint/nursery/missingPlaywrightAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━ + + i Async matcher toBeVisible must be awaited or returned. + + 1 │ test('example', async ({ page }) => { + 2 │ Promise.all([ + > 3 │ expect(page.locator('.one')).toBeVisible(), + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 4 │ expect(page.locator('.two')).toBeVisible() + 5 │ ]); + + i Add await before the expect call or return the promise. + + i Unsafe fix: Add await + + 1 1 │ test('example', async ({ page }) => { + 2 2 │ Promise.all([ + 3 │ - ········expect(page.locator('.one')).toBeVisible(), + 3 │ + ········await + 4 │ + ········expect(page.locator('.one')).toBeVisible(), + 4 5 │ expect(page.locator('.two')).toBeVisible() + 5 6 │ ]); + + +``` + +``` +promise-all-not-awaited.js:4:9 lint/nursery/missingPlaywrightAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━ + + i Async matcher toBeVisible must be awaited or returned. + + 2 │ Promise.all([ + 3 │ expect(page.locator('.one')).toBeVisible(), + > 4 │ expect(page.locator('.two')).toBeVisible() + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 5 │ ]); + 6 │ }); + + i Add await before the expect call or return the promise. + + i Unsafe fix: Add await + + 2 2 │ Promise.all([ + 3 3 │ expect(page.locator('.one')).toBeVisible(), + 4 │ - ········expect(page.locator('.two')).toBeVisible() + 4 │ + ········await + 5 │ + ········expect(page.locator('.two')).toBeVisible() + 5 6 │ ]); + 6 7 │ }); + + +``` From 9051d4af7ded27f66c5652ca54e79ca0480c4d5a Mon Sep 17 00:00:00 2001 From: Josh Delsman <12201+voxxit@users.noreply.github.com> Date: Fri, 17 Oct 2025 04:01:05 -0500 Subject: [PATCH 05/24] feat(biome_js_analyze): enhance missingPlaywrightAwait rule to check for describe calls Add functionality to the missingPlaywrightAwait lint rule to enforce that "describe" calls are awaited or returned. Introduce a new type for handling "describe" in the MissingAwaitType enum. Update test cases to reflect changes in the rule, ensuring that both valid and invalid usages of "describe" are properly tested. Adjust existing test cases to use `page.locator('body')` for consistency. New test cases include: - invalid/expect-async-matcher.js - valid/awaited.js - valid/returned.js This update improves linting accuracy for Playwright async APIs. --- .../lint/nursery/missing_playwright_await.rs | 56 +++++++ .../invalid/describe-not-awaited.js | 24 +++ .../invalid/describe-not-awaited.js.snap | 148 ++++++++++++++++++ .../invalid/expect-async-matcher.js | 2 +- .../invalid/expect-async-matcher.js.snap | 11 +- .../missingPlaywrightAwait/valid/awaited.js | 2 +- .../valid/awaited.js.snap | 3 +- .../valid/describe-awaited.js | 24 +++ .../valid/describe-awaited.js.snap | 32 ++++ .../missingPlaywrightAwait/valid/returned.js | 4 +- .../valid/returned.js.snap | 5 +- 11 files changed, 296 insertions(+), 15 deletions(-) create mode 100644 crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/describe-not-awaited.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/describe-not-awaited.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/describe-awaited.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/describe-awaited.js.snap diff --git a/crates/biome_js_analyze/src/lint/nursery/missing_playwright_await.rs b/crates/biome_js_analyze/src/lint/nursery/missing_playwright_await.rs index 019b9cb130ae..16dc05a6c3ce 100644 --- a/crates/biome_js_analyze/src/lint/nursery/missing_playwright_await.rs +++ b/crates/biome_js_analyze/src/lint/nursery/missing_playwright_await.rs @@ -11,6 +11,43 @@ use biome_rowan::{AstNode, BatchMutationExt}; use crate::JsRuleAction; +fn is_describe_like_callee(expr: &AnyJsExpression) -> bool { + // Walk the member chain, collecting identifiers + fn collect_chain(e: &AnyJsExpression, out: &mut Vec) { + match e { + AnyJsExpression::JsIdentifierExpression(id) => { + if let Ok(name) = id.name() { + if let Ok(tok) = name.value_token() { + out.push(tok.text_trimmed().to_string()); + } + } + } + AnyJsExpression::JsStaticMemberExpression(m) => { + if let Ok(member) = m.member() { + if let Some(name) = member.as_js_name() { + if let Ok(tok) = name.value_token() { + out.push(tok.text_trimmed().to_string()); + } + } + } + if let Ok(obj) = m.object() { + collect_chain(&obj, out); + } + } + _ => {} + } + } + + let mut parts = Vec::new(); + collect_chain(expr, &mut parts); + + // e.g. ["only", "describe", "test"] (collected from right-to-left) + // Check that "describe" is present and chain is rooted at "describe" or "test" + let has_describe = parts.iter().any(|p| p == "describe"); + let rooted_ok = parts.iter().any(|p| p == "describe" || p == "test"); + has_describe && rooted_ok +} + declare_lint_rule! { /// Enforce Playwright async APIs to be awaited or returned. /// @@ -101,6 +138,7 @@ pub enum MissingAwaitType { ExpectMatcher(String), ExpectPoll, TestStep, + Describe, } impl Rule for MissingPlaywrightAwait { @@ -112,6 +150,16 @@ impl Rule for MissingPlaywrightAwait { fn run(ctx: &RuleContext) -> Self::Signals { let call_expr = ctx.query(); + // Check for describe-like calls + if let Ok(callee) = call_expr.callee() { + if is_describe_like_callee(&callee) { + if !is_properly_handled(call_expr) { + return Some(MissingAwaitType::Describe); + } + return None; + } + } + // Check for test.step() calls if is_test_step_call(call_expr) { if !is_properly_handled(call_expr) { @@ -158,6 +206,14 @@ impl Rule for MissingPlaywrightAwait { "Add ""await"" before the test.step call or return the promise." }, ), + MissingAwaitType::Describe => ( + markup! { + "describe"" call must be awaited or returned." + }, + markup! { + "Add ""await"" before the describe call or return the promise." + }, + ), }; Some( diff --git a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/describe-not-awaited.js b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/describe-not-awaited.js new file mode 100644 index 000000000000..56450c669815 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/describe-not-awaited.js @@ -0,0 +1,24 @@ +test('example', async ({ page }) => { + describe('suite', () => { + // test content + }); +}); + +test('example', async ({ page }) => { + describe.only('suite', () => { + // test content + }); +}); + +test('example', async ({ page }) => { + test.describe('suite', () => { + // test content + }); +}); + +test('example', async ({ page }) => { + test.describe.parallel('suite', () => { + // test content + }); +}); + diff --git a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/describe-not-awaited.js.snap b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/describe-not-awaited.js.snap new file mode 100644 index 000000000000..bcd3499c34ff --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/describe-not-awaited.js.snap @@ -0,0 +1,148 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: describe-not-awaited.js +--- +# Input +```js +test('example', async ({ page }) => { + describe('suite', () => { + // test content + }); +}); + +test('example', async ({ page }) => { + describe.only('suite', () => { + // test content + }); +}); + +test('example', async ({ page }) => { + test.describe('suite', () => { + // test content + }); +}); + +test('example', async ({ page }) => { + test.describe.parallel('suite', () => { + // test content + }); +}); + + +``` + +# Diagnostics +``` +describe-not-awaited.js:2:5 lint/nursery/missingPlaywrightAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i describe call must be awaited or returned. + + 1 │ test('example', async ({ page }) => { + > 2 │ describe('suite', () => { + │ ^^^^^^^^^^^^^^^^^^^^^^^^^ + > 3 │ // test content + > 4 │ }); + │ ^^ + 5 │ }); + 6 │ + + i Add await before the describe call or return the promise. + + i Unsafe fix: Add await + + 1 1 │ test('example', async ({ page }) => { + 2 │ - ····describe('suite',·()·=>·{ + 2 │ + ····await + 3 │ + ····describe('suite',·()·=>·{ + 3 4 │ // test content + 4 5 │ }); + + +``` + +``` +describe-not-awaited.js:8:5 lint/nursery/missingPlaywrightAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i describe call must be awaited or returned. + + 7 │ test('example', async ({ page }) => { + > 8 │ describe.only('suite', () => { + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + > 9 │ // test content + > 10 │ }); + │ ^^ + 11 │ }); + 12 │ + + i Add await before the describe call or return the promise. + + i Unsafe fix: Add await + + 6 6 │ + 7 7 │ test('example', async ({ page }) => { + 8 │ - ····describe.only('suite',·()·=>·{ + 8 │ + ····await + 9 │ + ····describe.only('suite',·()·=>·{ + 9 10 │ // test content + 10 11 │ }); + + +``` + +``` +describe-not-awaited.js:14:5 lint/nursery/missingPlaywrightAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━ + + i describe call must be awaited or returned. + + 13 │ test('example', async ({ page }) => { + > 14 │ test.describe('suite', () => { + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + > 15 │ // test content + > 16 │ }); + │ ^^ + 17 │ }); + 18 │ + + i Add await before the describe call or return the promise. + + i Unsafe fix: Add await + + 12 12 │ + 13 13 │ test('example', async ({ page }) => { + 14 │ - ····test.describe('suite',·()·=>·{ + 14 │ + ····await + 15 │ + ····test.describe('suite',·()·=>·{ + 15 16 │ // test content + 16 17 │ }); + + +``` + +``` +describe-not-awaited.js:20:5 lint/nursery/missingPlaywrightAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━ + + i describe call must be awaited or returned. + + 19 │ test('example', async ({ page }) => { + > 20 │ test.describe.parallel('suite', () => { + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + > 21 │ // test content + > 22 │ }); + │ ^^ + 23 │ }); + 24 │ + + i Add await before the describe call or return the promise. + + i Unsafe fix: Add await + + 18 18 │ + 19 19 │ test('example', async ({ page }) => { + 20 │ - ····test.describe.parallel('suite',·()·=>·{ + 20 │ + ····await + 21 │ + ····test.describe.parallel('suite',·()·=>·{ + 21 22 │ // test content + 22 23 │ }); + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/expect-async-matcher.js b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/expect-async-matcher.js index 4407eebd327f..8a9748fc274b 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/expect-async-matcher.js +++ b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/expect-async-matcher.js @@ -1,4 +1,4 @@ test('example', async ({ page }) => { - expect(page).toBeVisible(); + expect(page.locator('body')).toBeVisible(); }); diff --git a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/expect-async-matcher.js.snap b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/expect-async-matcher.js.snap index 8ac3c010bebb..7ecfe1352d6a 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/expect-async-matcher.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/expect-async-matcher.js.snap @@ -1,12 +1,11 @@ --- source: crates/biome_js_analyze/tests/spec_tests.rs -assertion_line: 152 expression: expect-async-matcher.js --- # Input ```js test('example', async ({ page }) => { - expect(page).toBeVisible(); + expect(page.locator('body')).toBeVisible(); }); @@ -19,8 +18,8 @@ expect-async-matcher.js:2:5 lint/nursery/missingPlaywrightAwait FIXABLE ━━ i Async matcher toBeVisible must be awaited or returned. 1 │ test('example', async ({ page }) => { - > 2 │ expect(page).toBeVisible(); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^ + > 2 │ expect(page.locator('body')).toBeVisible(); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 3 │ }); 4 │ @@ -29,9 +28,9 @@ expect-async-matcher.js:2:5 lint/nursery/missingPlaywrightAwait FIXABLE ━━ i Unsafe fix: Add await 1 1 │ test('example', async ({ page }) => { - 2 │ - ····expect(page).toBeVisible(); + 2 │ - ····expect(page.locator('body')).toBeVisible(); 2 │ + ····await - 3 │ + ····expect(page).toBeVisible(); + 3 │ + ····expect(page.locator('body')).toBeVisible(); 3 4 │ }); 4 5 │ diff --git a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/awaited.js b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/awaited.js index b77d986538d5..0483bc0be360 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/awaited.js +++ b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/awaited.js @@ -1,5 +1,5 @@ test('example', async ({ page }) => { - await expect(page).toBeVisible(); + await expect(page.locator('body')).toBeVisible(); await test.step('step', async () => {}); await expect.poll(() => foo).toBe(true); }); diff --git a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/awaited.js.snap b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/awaited.js.snap index 75b002489bab..7760f7433dd0 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/awaited.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/awaited.js.snap @@ -1,12 +1,11 @@ --- source: crates/biome_js_analyze/tests/spec_tests.rs -assertion_line: 152 expression: awaited.js --- # Input ```js test('example', async ({ page }) => { - await expect(page).toBeVisible(); + await expect(page.locator('body')).toBeVisible(); await test.step('step', async () => {}); await expect.poll(() => foo).toBe(true); }); diff --git a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/describe-awaited.js b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/describe-awaited.js new file mode 100644 index 000000000000..2032c4e25b29 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/describe-awaited.js @@ -0,0 +1,24 @@ +test('example', async ({ page }) => { + await describe('suite', () => { + // test content + }); +}); + +test('example', async ({ page }) => { + await describe.only('suite', () => { + // test content + }); +}); + +test('example', async ({ page }) => { + await test.describe('suite', () => { + // test content + }); +}); + +test('example', async ({ page }) => { + await test.describe.parallel('suite', () => { + // test content + }); +}); + diff --git a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/describe-awaited.js.snap b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/describe-awaited.js.snap new file mode 100644 index 000000000000..2951171db47c --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/describe-awaited.js.snap @@ -0,0 +1,32 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: describe-awaited.js +--- +# Input +```js +test('example', async ({ page }) => { + await describe('suite', () => { + // test content + }); +}); + +test('example', async ({ page }) => { + await describe.only('suite', () => { + // test content + }); +}); + +test('example', async ({ page }) => { + await test.describe('suite', () => { + // test content + }); +}); + +test('example', async ({ page }) => { + await test.describe.parallel('suite', () => { + // test content + }); +}); + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/returned.js b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/returned.js index 588f34a1820b..599763181855 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/returned.js +++ b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/returned.js @@ -1,6 +1,6 @@ test('example', async ({ page }) => { - return expect(page).toBeVisible(); + return expect(page.locator('body')).toBeVisible(); }); -test('arrow', async ({ page }) => expect(page).toBeVisible()); +test('arrow', async ({ page }) => expect(page.locator('body')).toBeVisible()); diff --git a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/returned.js.snap b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/returned.js.snap index 606f7c3a2832..1c90ed90792f 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/returned.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/returned.js.snap @@ -1,15 +1,14 @@ --- source: crates/biome_js_analyze/tests/spec_tests.rs -assertion_line: 152 expression: returned.js --- # Input ```js test('example', async ({ page }) => { - return expect(page).toBeVisible(); + return expect(page.locator('body')).toBeVisible(); }); -test('arrow', async ({ page }) => expect(page).toBeVisible()); +test('arrow', async ({ page }) => expect(page.locator('body')).toBeVisible()); ``` From 8873983fb1747d6810b1e904a810e497fb3dfac3 Mon Sep 17 00:00:00 2001 From: Josh Delsman <12201+voxxit@users.noreply.github.com> Date: Fri, 17 Oct 2025 04:22:00 -0500 Subject: [PATCH 06/24] fix(biome_js_analyze): refine missingPlaywrightAwait rule logic and update tests Enhance the missingPlaywrightAwait lint rule to ensure that arrow function calls are only considered awaited if they match the exact body of the function. This change improves the accuracy of the linting process for Playwright async APIs. Additionally, update test cases to reflect the new logic, including modifications to the valid alternatives for waitForLoadState in the Playwright navigation tests. This update aims to provide more precise diagnostics and improve the overall linting experience. --- .../src/lint/nursery/missing_playwright_await.rs | 10 +++++++--- .../valid/alternatives.js | 2 +- .../valid/alternatives.js.snap | 3 +-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/crates/biome_js_analyze/src/lint/nursery/missing_playwright_await.rs b/crates/biome_js_analyze/src/lint/nursery/missing_playwright_await.rs index 16dc05a6c3ce..f586bb022eb2 100644 --- a/crates/biome_js_analyze/src/lint/nursery/missing_playwright_await.rs +++ b/crates/biome_js_analyze/src/lint/nursery/missing_playwright_await.rs @@ -402,11 +402,15 @@ fn is_call_awaited_or_returned(call_expr: &JsCallExpression) -> bool { return true; } biome_js_syntax::JsSyntaxKind::JS_ARROW_FUNCTION_EXPRESSION => { - // If it's an arrow function with expression body, it's implicitly returned + // If it's an arrow function with expression body, check if our call is exactly the body if let Some(arrow) = JsArrowFunctionExpression::cast_ref(&node) { if let Ok(body) = arrow.body() { - if body.as_any_js_expression().is_some() { - return true; + if let Some(body_expr) = body.as_any_js_expression() { + // Only return true if the call expression is exactly the arrow body + // (not just nested somewhere inside it) + if call_expr.syntax().text_trimmed_range() == body_expr.syntax().text_trimmed_range() { + return true; + } } } } diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/valid/alternatives.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/valid/alternatives.js index 79b01cc7bd4e..524aa345b687 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/valid/alternatives.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/valid/alternatives.js @@ -1,4 +1,4 @@ await page.waitForURL('/home'); -await page.waitForLoadState('networkidle'); +await page.waitForLoadState('load'); await page.goto('/home'); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/valid/alternatives.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/valid/alternatives.js.snap index 130aca382ae6..96cb0b53a266 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/valid/alternatives.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/valid/alternatives.js.snap @@ -1,12 +1,11 @@ --- source: crates/biome_js_analyze/tests/spec_tests.rs -assertion_line: 152 expression: alternatives.js --- # Input ```js await page.waitForURL('/home'); -await page.waitForLoadState('networkidle'); +await page.waitForLoadState('load'); await page.goto('/home'); From 8e425da8d35429d9ba6766ac86487b891e180b52 Mon Sep 17 00:00:00 2001 From: Josh Delsman <12201+voxxit@users.noreply.github.com> Date: Fri, 17 Oct 2025 04:23:35 -0500 Subject: [PATCH 07/24] refactor(biome_js_analyze): remove describe-like callee check from missingPlaywrightAwait rule Eliminate the check for "describe"-like calls in the missingPlaywrightAwait lint rule to streamline its logic. This change focuses the rule on enforcing that Playwright async APIs are awaited or returned without the additional complexity of handling describe calls. No changes to test cases were necessary as a result of this refactor, maintaining the existing linting functionality for Playwright async APIs. --- .../lint/nursery/missing_playwright_await.rs | 48 ------------------- 1 file changed, 48 deletions(-) diff --git a/crates/biome_js_analyze/src/lint/nursery/missing_playwright_await.rs b/crates/biome_js_analyze/src/lint/nursery/missing_playwright_await.rs index f586bb022eb2..690e87539225 100644 --- a/crates/biome_js_analyze/src/lint/nursery/missing_playwright_await.rs +++ b/crates/biome_js_analyze/src/lint/nursery/missing_playwright_await.rs @@ -11,43 +11,6 @@ use biome_rowan::{AstNode, BatchMutationExt}; use crate::JsRuleAction; -fn is_describe_like_callee(expr: &AnyJsExpression) -> bool { - // Walk the member chain, collecting identifiers - fn collect_chain(e: &AnyJsExpression, out: &mut Vec) { - match e { - AnyJsExpression::JsIdentifierExpression(id) => { - if let Ok(name) = id.name() { - if let Ok(tok) = name.value_token() { - out.push(tok.text_trimmed().to_string()); - } - } - } - AnyJsExpression::JsStaticMemberExpression(m) => { - if let Ok(member) = m.member() { - if let Some(name) = member.as_js_name() { - if let Ok(tok) = name.value_token() { - out.push(tok.text_trimmed().to_string()); - } - } - } - if let Ok(obj) = m.object() { - collect_chain(&obj, out); - } - } - _ => {} - } - } - - let mut parts = Vec::new(); - collect_chain(expr, &mut parts); - - // e.g. ["only", "describe", "test"] (collected from right-to-left) - // Check that "describe" is present and chain is rooted at "describe" or "test" - let has_describe = parts.iter().any(|p| p == "describe"); - let rooted_ok = parts.iter().any(|p| p == "describe" || p == "test"); - has_describe && rooted_ok -} - declare_lint_rule! { /// Enforce Playwright async APIs to be awaited or returned. /// @@ -138,7 +101,6 @@ pub enum MissingAwaitType { ExpectMatcher(String), ExpectPoll, TestStep, - Describe, } impl Rule for MissingPlaywrightAwait { @@ -150,16 +112,6 @@ impl Rule for MissingPlaywrightAwait { fn run(ctx: &RuleContext) -> Self::Signals { let call_expr = ctx.query(); - // Check for describe-like calls - if let Ok(callee) = call_expr.callee() { - if is_describe_like_callee(&callee) { - if !is_properly_handled(call_expr) { - return Some(MissingAwaitType::Describe); - } - return None; - } - } - // Check for test.step() calls if is_test_step_call(call_expr) { if !is_properly_handled(call_expr) { From f0f63489f3c96aa719be8a375698480b21493645 Mon Sep 17 00:00:00 2001 From: Josh Delsman <12201+voxxit@users.noreply.github.com> Date: Fri, 17 Oct 2025 04:23:50 -0500 Subject: [PATCH 08/24] refactor(biome_js_analyze): remove "describe" call checks from missingPlaywrightAwait rule Eliminate the handling of "describe" calls in the missingPlaywrightAwait lint rule to simplify its logic. This change focuses the rule on ensuring that Playwright async APIs are awaited or returned, enhancing clarity and maintainability. No modifications to test cases were required, preserving existing linting functionality. --- .../src/lint/nursery/missing_playwright_await.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/crates/biome_js_analyze/src/lint/nursery/missing_playwright_await.rs b/crates/biome_js_analyze/src/lint/nursery/missing_playwright_await.rs index 690e87539225..5f89287c55fc 100644 --- a/crates/biome_js_analyze/src/lint/nursery/missing_playwright_await.rs +++ b/crates/biome_js_analyze/src/lint/nursery/missing_playwright_await.rs @@ -158,14 +158,6 @@ impl Rule for MissingPlaywrightAwait { "Add ""await"" before the test.step call or return the promise." }, ), - MissingAwaitType::Describe => ( - markup! { - "describe"" call must be awaited or returned." - }, - markup! { - "Add ""await"" before the describe call or return the promise." - }, - ), }; Some( From a02098db10b6c7a1769b3c584b9b0ba183f289b2 Mon Sep 17 00:00:00 2001 From: Josh Delsman <12201+voxxit@users.noreply.github.com> Date: Fri, 17 Oct 2025 04:24:43 -0500 Subject: [PATCH 09/24] refactor(biome_js_analyze): remove invalid and valid describe test cases for missingPlaywrightAwait rule Delete obsolete test cases related to "describe" calls in the missingPlaywrightAwait rule. This cleanup aligns with recent refactors that focus on ensuring Playwright async APIs are awaited or returned, enhancing the clarity and maintainability of the test suite. No functional changes to the linting logic were made. --- .../invalid/describe-not-awaited.js | 24 --- .../invalid/describe-not-awaited.js.snap | 148 ------------------ .../valid/describe-awaited.js | 24 --- .../valid/describe-awaited.js.snap | 32 ---- 4 files changed, 228 deletions(-) delete mode 100644 crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/describe-not-awaited.js delete mode 100644 crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/describe-not-awaited.js.snap delete mode 100644 crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/describe-awaited.js delete mode 100644 crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/describe-awaited.js.snap diff --git a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/describe-not-awaited.js b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/describe-not-awaited.js deleted file mode 100644 index 56450c669815..000000000000 --- a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/describe-not-awaited.js +++ /dev/null @@ -1,24 +0,0 @@ -test('example', async ({ page }) => { - describe('suite', () => { - // test content - }); -}); - -test('example', async ({ page }) => { - describe.only('suite', () => { - // test content - }); -}); - -test('example', async ({ page }) => { - test.describe('suite', () => { - // test content - }); -}); - -test('example', async ({ page }) => { - test.describe.parallel('suite', () => { - // test content - }); -}); - diff --git a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/describe-not-awaited.js.snap b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/describe-not-awaited.js.snap deleted file mode 100644 index bcd3499c34ff..000000000000 --- a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/describe-not-awaited.js.snap +++ /dev/null @@ -1,148 +0,0 @@ ---- -source: crates/biome_js_analyze/tests/spec_tests.rs -expression: describe-not-awaited.js ---- -# Input -```js -test('example', async ({ page }) => { - describe('suite', () => { - // test content - }); -}); - -test('example', async ({ page }) => { - describe.only('suite', () => { - // test content - }); -}); - -test('example', async ({ page }) => { - test.describe('suite', () => { - // test content - }); -}); - -test('example', async ({ page }) => { - test.describe.parallel('suite', () => { - // test content - }); -}); - - -``` - -# Diagnostics -``` -describe-not-awaited.js:2:5 lint/nursery/missingPlaywrightAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━ - - i describe call must be awaited or returned. - - 1 │ test('example', async ({ page }) => { - > 2 │ describe('suite', () => { - │ ^^^^^^^^^^^^^^^^^^^^^^^^^ - > 3 │ // test content - > 4 │ }); - │ ^^ - 5 │ }); - 6 │ - - i Add await before the describe call or return the promise. - - i Unsafe fix: Add await - - 1 1 │ test('example', async ({ page }) => { - 2 │ - ····describe('suite',·()·=>·{ - 2 │ + ····await - 3 │ + ····describe('suite',·()·=>·{ - 3 4 │ // test content - 4 5 │ }); - - -``` - -``` -describe-not-awaited.js:8:5 lint/nursery/missingPlaywrightAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━ - - i describe call must be awaited or returned. - - 7 │ test('example', async ({ page }) => { - > 8 │ describe.only('suite', () => { - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - > 9 │ // test content - > 10 │ }); - │ ^^ - 11 │ }); - 12 │ - - i Add await before the describe call or return the promise. - - i Unsafe fix: Add await - - 6 6 │ - 7 7 │ test('example', async ({ page }) => { - 8 │ - ····describe.only('suite',·()·=>·{ - 8 │ + ····await - 9 │ + ····describe.only('suite',·()·=>·{ - 9 10 │ // test content - 10 11 │ }); - - -``` - -``` -describe-not-awaited.js:14:5 lint/nursery/missingPlaywrightAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━ - - i describe call must be awaited or returned. - - 13 │ test('example', async ({ page }) => { - > 14 │ test.describe('suite', () => { - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - > 15 │ // test content - > 16 │ }); - │ ^^ - 17 │ }); - 18 │ - - i Add await before the describe call or return the promise. - - i Unsafe fix: Add await - - 12 12 │ - 13 13 │ test('example', async ({ page }) => { - 14 │ - ····test.describe('suite',·()·=>·{ - 14 │ + ····await - 15 │ + ····test.describe('suite',·()·=>·{ - 15 16 │ // test content - 16 17 │ }); - - -``` - -``` -describe-not-awaited.js:20:5 lint/nursery/missingPlaywrightAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━ - - i describe call must be awaited or returned. - - 19 │ test('example', async ({ page }) => { - > 20 │ test.describe.parallel('suite', () => { - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - > 21 │ // test content - > 22 │ }); - │ ^^ - 23 │ }); - 24 │ - - i Add await before the describe call or return the promise. - - i Unsafe fix: Add await - - 18 18 │ - 19 19 │ test('example', async ({ page }) => { - 20 │ - ····test.describe.parallel('suite',·()·=>·{ - 20 │ + ····await - 21 │ + ····test.describe.parallel('suite',·()·=>·{ - 21 22 │ // test content - 22 23 │ }); - - -``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/describe-awaited.js b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/describe-awaited.js deleted file mode 100644 index 2032c4e25b29..000000000000 --- a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/describe-awaited.js +++ /dev/null @@ -1,24 +0,0 @@ -test('example', async ({ page }) => { - await describe('suite', () => { - // test content - }); -}); - -test('example', async ({ page }) => { - await describe.only('suite', () => { - // test content - }); -}); - -test('example', async ({ page }) => { - await test.describe('suite', () => { - // test content - }); -}); - -test('example', async ({ page }) => { - await test.describe.parallel('suite', () => { - // test content - }); -}); - diff --git a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/describe-awaited.js.snap b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/describe-awaited.js.snap deleted file mode 100644 index 2951171db47c..000000000000 --- a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/describe-awaited.js.snap +++ /dev/null @@ -1,32 +0,0 @@ ---- -source: crates/biome_js_analyze/tests/spec_tests.rs -expression: describe-awaited.js ---- -# Input -```js -test('example', async ({ page }) => { - await describe('suite', () => { - // test content - }); -}); - -test('example', async ({ page }) => { - await describe.only('suite', () => { - // test content - }); -}); - -test('example', async ({ page }) => { - await test.describe('suite', () => { - // test content - }); -}); - -test('example', async ({ page }) => { - await test.describe.parallel('suite', () => { - // test content - }); -}); - - -``` From 388330e55ceceec5df350cff46cedbe33260bddd Mon Sep 17 00:00:00 2001 From: Josh Delsman <12201+voxxit@users.noreply.github.com> Date: Fri, 17 Oct 2025 04:38:57 -0500 Subject: [PATCH 10/24] refactor(biome_js_analyze): enhance missingPlaywrightAwait rule to check async context Update the missingPlaywrightAwait lint rule to include a check for async contexts, ensuring that Playwright async APIs are only considered awaited when within an async function or module. This change improves the accuracy of the linting process. Additionally, remove obsolete test snapshots related to the noPlaywrightForceOption rule, streamlining the test suite. --- .../lint/nursery/missing_playwright_await.rs | 44 ++++++++++++++++++- .../invalid/module-level.js | 4 ++ .../invalid/module-level.js.snap | 35 +++++++++++++++ .../invalid/non-async-context.js | 5 +++ .../invalid/non-async-context.js.snap | 30 +++++++++++++ .../invalid/click.js.snap\\" | 28 ------------ 6 files changed, 116 insertions(+), 30 deletions(-) create mode 100644 crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/module-level.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/module-level.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/non-async-context.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/non-async-context.js.snap delete mode 100644 "crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/click.js.snap\\" diff --git a/crates/biome_js_analyze/src/lint/nursery/missing_playwright_await.rs b/crates/biome_js_analyze/src/lint/nursery/missing_playwright_await.rs index 5f89287c55fc..6d5303246a9c 100644 --- a/crates/biome_js_analyze/src/lint/nursery/missing_playwright_await.rs +++ b/crates/biome_js_analyze/src/lint/nursery/missing_playwright_await.rs @@ -5,11 +5,11 @@ use biome_console::markup; use biome_diagnostics::Applicability; use biome_js_factory::make; use biome_js_syntax::{ - AnyJsExpression, JsArrowFunctionExpression, JsCallExpression, T, + AnyJsExpression, JsArrowFunctionExpression, JsCallExpression, JsModule, T, }; use biome_rowan::{AstNode, BatchMutationExt}; -use crate::JsRuleAction; +use crate::{ast_utils::is_in_async_function, JsRuleAction}; declare_lint_rule! { /// Enforce Playwright async APIs to be awaited or returned. @@ -173,6 +173,11 @@ impl Rule for MissingPlaywrightAwait { fn action(ctx: &RuleContext, _: &Self::State) -> Option { let call_expr = ctx.query(); + // Check if we're in an async context + if !is_in_async_context(call_expr.syntax()) { + return None; + } + let mut mutation = ctx.root().begin(); // Create an await expression @@ -451,3 +456,38 @@ fn is_promise_all(call: &JsCallExpression) -> bool { false } +/// Checks if a node is within an async context (async function or module with TLA support). +/// +/// This checks for: +/// - Async functions (arrow, function declaration, method) +/// - Module context (for top-level await support) +fn is_in_async_context(node: &biome_js_syntax::JsSyntaxNode) -> bool { + // First check if we're in an async function + if is_in_async_function(node) { + return true; + } + + // Check if we're at module level (for top-level await) + for ancestor in node.ancestors() { + if JsModule::can_cast(ancestor.kind()) { + return true; + } + + // Stop at function boundaries (if we're in a non-async function, + // being in a module doesn't help) + if matches!( + ancestor.kind(), + biome_js_syntax::JsSyntaxKind::JS_FUNCTION_DECLARATION + | biome_js_syntax::JsSyntaxKind::JS_FUNCTION_EXPRESSION + | biome_js_syntax::JsSyntaxKind::JS_ARROW_FUNCTION_EXPRESSION + | biome_js_syntax::JsSyntaxKind::JS_METHOD_CLASS_MEMBER + | biome_js_syntax::JsSyntaxKind::JS_METHOD_OBJECT_MEMBER + ) { + // We're in a non-async function, stop searching + break; + } + } + + false +} + diff --git a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/module-level.js b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/module-level.js new file mode 100644 index 000000000000..cf8445f21c06 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/module-level.js @@ -0,0 +1,4 @@ +// Module-level code (top-level await is supported) +expect(page.locator('body')).toBeVisible(); + + diff --git a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/module-level.js.snap b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/module-level.js.snap new file mode 100644 index 000000000000..84cb234ec5c0 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/module-level.js.snap @@ -0,0 +1,35 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: module-level.js +--- +# Input +```js +// Module-level code (top-level await is supported) +expect(page.locator('body')).toBeVisible(); + + + +``` + +# Diagnostics +``` +module-level.js:2:1 lint/nursery/missingPlaywrightAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Async matcher toBeVisible must be awaited or returned. + + 1 │ // Module-level code (top-level await is supported) + > 2 │ expect(page.locator('body')).toBeVisible(); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 3 │ + + i Add await before the expect call or return the promise. + + i Unsafe fix: Add await + + 1 1 │ // Module-level code (top-level await is supported) + 2 │ + await//·Module-level·code·(top-level·await·is·supported) + 2 3 │ expect(page.locator('body')).toBeVisible(); + 3 4 │ + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/non-async-context.js b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/non-async-context.js new file mode 100644 index 000000000000..afc46d8d37d3 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/non-async-context.js @@ -0,0 +1,5 @@ +test('example', ({ page }) => { + expect(page.locator('body')).toBeVisible(); +}); + + diff --git a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/non-async-context.js.snap b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/non-async-context.js.snap new file mode 100644 index 000000000000..9f2a92bb58c6 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/non-async-context.js.snap @@ -0,0 +1,30 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: non-async-context.js +--- +# Input +```js +test('example', ({ page }) => { + expect(page.locator('body')).toBeVisible(); +}); + + + +``` + +# Diagnostics +``` +non-async-context.js:2:5 lint/nursery/missingPlaywrightAwait ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Async matcher toBeVisible must be awaited or returned. + + 1 │ test('example', ({ page }) => { + > 2 │ expect(page.locator('body')).toBeVisible(); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 3 │ }); + 4 │ + + i Add await before the expect call or return the promise. + + +``` diff --git "a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/click.js.snap\\" "b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/click.js.snap\\" deleted file mode 100644 index 9cf9ba0f11a5..000000000000 --- "a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/click.js.snap\\" +++ /dev/null @@ -1,28 +0,0 @@ ---- -source: crates/biome_js_analyze/tests/spec_tests.rs -assertion_line: 152 -expression: click.js ---- -# Input -```js -await page.locator('button').click({ force: true }); - - -``` - -# Diagnostics -``` -click.js:1:7 lint/nursery/noPlaywrightForceOption ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - i Unexpected use of { force: true } option. - - > 1 │ await page.locator('button').click({ force: true }); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 2 │ - - i The force option bypasses actionability checks and can lead to unreliable tests. - - i Fix the underlying issue instead of forcing the action. - - -``` From 99e2e5914aa6dda8fbab9c66adfd2e2f302acfbb Mon Sep 17 00:00:00 2001 From: Josh Delsman <12201+voxxit@users.noreply.github.com> Date: Fri, 17 Oct 2025 04:54:11 -0500 Subject: [PATCH 11/24] chore: add changeset for the Playwright additions --- .changeset/common-lizards-sniff.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/common-lizards-sniff.md diff --git a/.changeset/common-lizards-sniff.md b/.changeset/common-lizards-sniff.md new file mode 100644 index 000000000000..d7ae7fe07de0 --- /dev/null +++ b/.changeset/common-lizards-sniff.md @@ -0,0 +1,5 @@ +--- +"@biomejs/biome": patch +--- + +Added 13 new Playwright lint rules to the nursery (from eslint-plugin-playwright) From c05d8db7d14f6a981965a282a9e153df5a6d6fba Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Fri, 17 Oct 2025 10:26:06 +0000 Subject: [PATCH 12/24] [autofix.ci] apply automated fixes --- .../migrate/eslint_any_rule_to_biome.rs | 156 +++++++ .../src/analyzer/linter/rules.rs | 421 ++++++++++++++--- crates/biome_js_analyze/src/lint/nursery.rs | 14 +- .../lint/nursery/missing_playwright_await.rs | 76 ++- .../nursery/no_playwright_element_handle.rs | 37 +- .../src/lint/nursery/no_playwright_eval.rs | 39 +- .../nursery/no_playwright_focused_test.rs | 9 +- .../nursery/no_playwright_force_option.rs | 11 +- .../lint/nursery/no_playwright_networkidle.rs | 13 +- .../lint/nursery/no_playwright_page_pause.rs | 34 +- .../nursery/no_playwright_skipped_test.rs | 9 +- .../nursery/no_playwright_useless_await.rs | 21 +- .../no_playwright_valid_describe_callback.rs | 24 +- .../no_playwright_wait_for_navigation.rs | 37 +- .../no_playwright_wait_for_selector.rs | 37 +- .../nursery/no_playwright_wait_for_timeout.rs | 37 +- crates/biome_rule_options/src/lib.rs | 13 + .../src/missing_playwright_await.rs | 6 + .../src/no_playwright_element_handle.rs | 6 + .../src/no_playwright_eval.rs | 6 + .../src/no_playwright_focused_test.rs | 6 + .../src/no_playwright_force_option.rs | 6 + .../src/no_playwright_networkidle.rs | 6 + .../src/no_playwright_page_pause.rs | 6 + .../src/no_playwright_skipped_test.rs | 6 + .../src/no_playwright_useless_await.rs | 6 + .../no_playwright_valid_describe_callback.rs | 6 + .../src/no_playwright_wait_for_navigation.rs | 6 + .../src/no_playwright_wait_for_selector.rs | 6 + .../src/no_playwright_wait_for_timeout.rs | 6 + .../@biomejs/backend-jsonrpc/src/workspace.ts | 255 ++++++++++ .../@biomejs/biome/configuration_schema.json | 442 ++++++++++++++++++ 32 files changed, 1509 insertions(+), 254 deletions(-) create mode 100644 crates/biome_rule_options/src/missing_playwright_await.rs create mode 100644 crates/biome_rule_options/src/no_playwright_element_handle.rs create mode 100644 crates/biome_rule_options/src/no_playwright_eval.rs create mode 100644 crates/biome_rule_options/src/no_playwright_focused_test.rs create mode 100644 crates/biome_rule_options/src/no_playwright_force_option.rs create mode 100644 crates/biome_rule_options/src/no_playwright_networkidle.rs create mode 100644 crates/biome_rule_options/src/no_playwright_page_pause.rs create mode 100644 crates/biome_rule_options/src/no_playwright_skipped_test.rs create mode 100644 crates/biome_rule_options/src/no_playwright_useless_await.rs create mode 100644 crates/biome_rule_options/src/no_playwright_valid_describe_callback.rs create mode 100644 crates/biome_rule_options/src/no_playwright_wait_for_navigation.rs create mode 100644 crates/biome_rule_options/src/no_playwright_wait_for_selector.rs create mode 100644 crates/biome_rule_options/src/no_playwright_wait_for_timeout.rs diff --git a/crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs b/crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs index 8a2aa6239c0b..2332d71d402a 100644 --- a/crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs +++ b/crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs @@ -2093,6 +2093,162 @@ pub(crate) fn migrate_eslint_any_rule( .get_or_insert(Default::default()); rule.set_level(rule.level().max(rule_severity.into())); } + "playwright/missing-playwright-await" => { + if !options.include_nursery { + results.add(eslint_name, eslint_to_biome::RuleMigrationResult::Nursery); + return false; + } + let group = rules.nursery.get_or_insert_with(Default::default); + let rule = group + .unwrap_group_as_mut() + .missing_playwright_await + .get_or_insert(Default::default()); + rule.set_level(rule.level().max(rule_severity.into())); + } + "playwright/no-element-handle" => { + if !options.include_nursery { + results.add(eslint_name, eslint_to_biome::RuleMigrationResult::Nursery); + return false; + } + let group = rules.nursery.get_or_insert_with(Default::default); + let rule = group + .unwrap_group_as_mut() + .no_playwright_element_handle + .get_or_insert(Default::default()); + rule.set_level(rule.level().max(rule_severity.into())); + } + "playwright/no-eval" => { + if !options.include_nursery { + results.add(eslint_name, eslint_to_biome::RuleMigrationResult::Nursery); + return false; + } + let group = rules.nursery.get_or_insert_with(Default::default); + let rule = group + .unwrap_group_as_mut() + .no_playwright_eval + .get_or_insert(Default::default()); + rule.set_level(rule.level().max(rule_severity.into())); + } + "playwright/no-focused-test" => { + if !options.include_nursery { + results.add(eslint_name, eslint_to_biome::RuleMigrationResult::Nursery); + return false; + } + let group = rules.nursery.get_or_insert_with(Default::default); + let rule = group + .unwrap_group_as_mut() + .no_playwright_focused_test + .get_or_insert(Default::default()); + rule.set_level(rule.level().max(rule_severity.into())); + } + "playwright/no-force-option" => { + if !options.include_nursery { + results.add(eslint_name, eslint_to_biome::RuleMigrationResult::Nursery); + return false; + } + let group = rules.nursery.get_or_insert_with(Default::default); + let rule = group + .unwrap_group_as_mut() + .no_playwright_force_option + .get_or_insert(Default::default()); + rule.set_level(rule.level().max(rule_severity.into())); + } + "playwright/no-networkidle" => { + if !options.include_nursery { + results.add(eslint_name, eslint_to_biome::RuleMigrationResult::Nursery); + return false; + } + let group = rules.nursery.get_or_insert_with(Default::default); + let rule = group + .unwrap_group_as_mut() + .no_playwright_networkidle + .get_or_insert(Default::default()); + rule.set_level(rule.level().max(rule_severity.into())); + } + "playwright/no-page-pause" => { + if !options.include_nursery { + results.add(eslint_name, eslint_to_biome::RuleMigrationResult::Nursery); + return false; + } + let group = rules.nursery.get_or_insert_with(Default::default); + let rule = group + .unwrap_group_as_mut() + .no_playwright_page_pause + .get_or_insert(Default::default()); + rule.set_level(rule.level().max(rule_severity.into())); + } + "playwright/no-skipped-test" => { + if !options.include_nursery { + results.add(eslint_name, eslint_to_biome::RuleMigrationResult::Nursery); + return false; + } + let group = rules.nursery.get_or_insert_with(Default::default); + let rule = group + .unwrap_group_as_mut() + .no_playwright_skipped_test + .get_or_insert(Default::default()); + rule.set_level(rule.level().max(rule_severity.into())); + } + "playwright/no-useless-await" => { + if !options.include_nursery { + results.add(eslint_name, eslint_to_biome::RuleMigrationResult::Nursery); + return false; + } + let group = rules.nursery.get_or_insert_with(Default::default); + let rule = group + .unwrap_group_as_mut() + .no_playwright_useless_await + .get_or_insert(Default::default()); + rule.set_level(rule.level().max(rule_severity.into())); + } + "playwright/no-wait-for-navigation" => { + if !options.include_nursery { + results.add(eslint_name, eslint_to_biome::RuleMigrationResult::Nursery); + return false; + } + let group = rules.nursery.get_or_insert_with(Default::default); + let rule = group + .unwrap_group_as_mut() + .no_playwright_wait_for_navigation + .get_or_insert(Default::default()); + rule.set_level(rule.level().max(rule_severity.into())); + } + "playwright/no-wait-for-selector" => { + if !options.include_nursery { + results.add(eslint_name, eslint_to_biome::RuleMigrationResult::Nursery); + return false; + } + let group = rules.nursery.get_or_insert_with(Default::default); + let rule = group + .unwrap_group_as_mut() + .no_playwright_wait_for_selector + .get_or_insert(Default::default()); + rule.set_level(rule.level().max(rule_severity.into())); + } + "playwright/no-wait-for-timeout" => { + if !options.include_nursery { + results.add(eslint_name, eslint_to_biome::RuleMigrationResult::Nursery); + return false; + } + let group = rules.nursery.get_or_insert_with(Default::default); + let rule = group + .unwrap_group_as_mut() + .no_playwright_wait_for_timeout + .get_or_insert(Default::default()); + rule.set_level(rule.level().max(rule_severity.into())); + } + "playwright/valid-describe-callback" => { + if !options.include_nursery { + results.add(eslint_name, eslint_to_biome::RuleMigrationResult::Nursery); + return false; + } + let group = rules.nursery.get_or_insert_with(Default::default); + let rule = group + .unwrap_group_as_mut() + .no_playwright_valid_describe_callback + .get_or_insert(Default::default()); + rule.set_level(rule.level().max(rule_severity.into())); + } "prefer-arrow-callback" => { if !options.include_inspired { results.add(eslint_name, eslint_to_biome::RuleMigrationResult::Inspired); diff --git a/crates/biome_configuration/src/analyzer/linter/rules.rs b/crates/biome_configuration/src/analyzer/linter/rules.rs index 417ebb834b0e..7fbd0a410e2b 100644 --- a/crates/biome_configuration/src/analyzer/linter/rules.rs +++ b/crates/biome_configuration/src/analyzer/linter/rules.rs @@ -89,6 +89,7 @@ impl std::fmt::Display for RuleGroup { #[cfg_attr(feature = "schema", derive(JsonSchema))] #[serde(rename_all = "camelCase")] pub enum RuleName { + MissingPlaywrightAwait, NoAccessKey, NoAccumulatingSpread, NoAdjacentSpacesInRegex, @@ -227,6 +228,18 @@ pub enum RuleName { NoOctalEscape, NoParameterAssign, NoParameterProperties, + NoPlaywrightElementHandle, + NoPlaywrightEval, + NoPlaywrightFocusedTest, + NoPlaywrightForceOption, + NoPlaywrightNetworkidle, + NoPlaywrightPagePause, + NoPlaywrightSkippedTest, + NoPlaywrightUselessAwait, + NoPlaywrightValidDescribeCallback, + NoPlaywrightWaitForNavigation, + NoPlaywrightWaitForSelector, + NoPlaywrightWaitForTimeout, NoPositiveTabindex, NoPrecisionLoss, NoPrivateImports, @@ -455,6 +468,7 @@ pub enum RuleName { impl RuleName { pub const fn as_str(self) -> &'static str { match self { + Self::MissingPlaywrightAwait => "missingPlaywrightAwait", Self::NoAccessKey => "noAccessKey", Self::NoAccumulatingSpread => "noAccumulatingSpread", Self::NoAdjacentSpacesInRegex => "noAdjacentSpacesInRegex", @@ -597,6 +611,18 @@ impl RuleName { Self::NoOctalEscape => "noOctalEscape", Self::NoParameterAssign => "noParameterAssign", Self::NoParameterProperties => "noParameterProperties", + Self::NoPlaywrightElementHandle => "noPlaywrightElementHandle", + Self::NoPlaywrightEval => "noPlaywrightEval", + Self::NoPlaywrightFocusedTest => "noPlaywrightFocusedTest", + Self::NoPlaywrightForceOption => "noPlaywrightForceOption", + Self::NoPlaywrightNetworkidle => "noPlaywrightNetworkidle", + Self::NoPlaywrightPagePause => "noPlaywrightPagePause", + Self::NoPlaywrightSkippedTest => "noPlaywrightSkippedTest", + Self::NoPlaywrightUselessAwait => "noPlaywrightUselessAwait", + Self::NoPlaywrightValidDescribeCallback => "noPlaywrightValidDescribeCallback", + Self::NoPlaywrightWaitForNavigation => "noPlaywrightWaitForNavigation", + Self::NoPlaywrightWaitForSelector => "noPlaywrightWaitForSelector", + Self::NoPlaywrightWaitForTimeout => "noPlaywrightWaitForTimeout", Self::NoPositiveTabindex => "noPositiveTabindex", Self::NoPrecisionLoss => "noPrecisionLoss", Self::NoPrivateImports => "noPrivateImports", @@ -825,6 +851,7 @@ impl RuleName { } pub const fn group(self) -> RuleGroup { match self { + Self::MissingPlaywrightAwait => RuleGroup::Nursery, Self::NoAccessKey => RuleGroup::A11y, Self::NoAccumulatingSpread => RuleGroup::Performance, Self::NoAdjacentSpacesInRegex => RuleGroup::Complexity, @@ -963,6 +990,18 @@ impl RuleName { Self::NoOctalEscape => RuleGroup::Suspicious, Self::NoParameterAssign => RuleGroup::Style, Self::NoParameterProperties => RuleGroup::Style, + Self::NoPlaywrightElementHandle => RuleGroup::Nursery, + Self::NoPlaywrightEval => RuleGroup::Nursery, + Self::NoPlaywrightFocusedTest => RuleGroup::Nursery, + Self::NoPlaywrightForceOption => RuleGroup::Nursery, + Self::NoPlaywrightNetworkidle => RuleGroup::Nursery, + Self::NoPlaywrightPagePause => RuleGroup::Nursery, + Self::NoPlaywrightSkippedTest => RuleGroup::Nursery, + Self::NoPlaywrightUselessAwait => RuleGroup::Nursery, + Self::NoPlaywrightValidDescribeCallback => RuleGroup::Nursery, + Self::NoPlaywrightWaitForNavigation => RuleGroup::Nursery, + Self::NoPlaywrightWaitForSelector => RuleGroup::Nursery, + Self::NoPlaywrightWaitForTimeout => RuleGroup::Nursery, Self::NoPositiveTabindex => RuleGroup::A11y, Self::NoPrecisionLoss => RuleGroup::Correctness, Self::NoPrivateImports => RuleGroup::Correctness, @@ -1194,6 +1233,7 @@ impl std::str::FromStr for RuleName { type Err = &'static str; fn from_str(s: &str) -> Result { match s { + "missingPlaywrightAwait" => Ok(Self::MissingPlaywrightAwait), "noAccessKey" => Ok(Self::NoAccessKey), "noAccumulatingSpread" => Ok(Self::NoAccumulatingSpread), "noAdjacentSpacesInRegex" => Ok(Self::NoAdjacentSpacesInRegex), @@ -1338,6 +1378,18 @@ impl std::str::FromStr for RuleName { "noOctalEscape" => Ok(Self::NoOctalEscape), "noParameterAssign" => Ok(Self::NoParameterAssign), "noParameterProperties" => Ok(Self::NoParameterProperties), + "noPlaywrightElementHandle" => Ok(Self::NoPlaywrightElementHandle), + "noPlaywrightEval" => Ok(Self::NoPlaywrightEval), + "noPlaywrightFocusedTest" => Ok(Self::NoPlaywrightFocusedTest), + "noPlaywrightForceOption" => Ok(Self::NoPlaywrightForceOption), + "noPlaywrightNetworkidle" => Ok(Self::NoPlaywrightNetworkidle), + "noPlaywrightPagePause" => Ok(Self::NoPlaywrightPagePause), + "noPlaywrightSkippedTest" => Ok(Self::NoPlaywrightSkippedTest), + "noPlaywrightUselessAwait" => Ok(Self::NoPlaywrightUselessAwait), + "noPlaywrightValidDescribeCallback" => Ok(Self::NoPlaywrightValidDescribeCallback), + "noPlaywrightWaitForNavigation" => Ok(Self::NoPlaywrightWaitForNavigation), + "noPlaywrightWaitForSelector" => Ok(Self::NoPlaywrightWaitForSelector), + "noPlaywrightWaitForTimeout" => Ok(Self::NoPlaywrightWaitForTimeout), "noPositiveTabindex" => Ok(Self::NoPositiveTabindex), "noPrecisionLoss" => Ok(Self::NoPrecisionLoss), "noPrivateImports" => Ok(Self::NoPrivateImports), @@ -4646,10 +4698,11 @@ impl From for Correctness { #[cfg_attr(feature = "schema", derive(JsonSchema))] #[serde(rename_all = "camelCase", default, deny_unknown_fields)] #[doc = r" A list of rules that belong to this group"] -pub struct Nursery { # [doc = r" Enables the recommended rules for this group"] # [serde (skip_serializing_if = "Option::is_none")] pub recommended : Option < bool > , # [doc = "Restrict imports of deprecated exports."] # [serde (skip_serializing_if = "Option::is_none")] pub no_deprecated_imports : Option < RuleConfiguration < biome_rule_options :: no_deprecated_imports :: NoDeprecatedImportsOptions >> , # [doc = "Prevent the listing of duplicate dependencies. The rule supports the following dependency groups: \"bundledDependencies\", \"bundleDependencies\", \"dependencies\", \"devDependencies\", \"overrides\", \"optionalDependencies\", and \"peerDependencies\"."] # [serde (skip_serializing_if = "Option::is_none")] pub no_duplicate_dependencies : Option < RuleConfiguration < biome_rule_options :: no_duplicate_dependencies :: NoDuplicateDependenciesOptions >> , # [doc = "Disallow empty sources."] # [serde (skip_serializing_if = "Option::is_none")] pub no_empty_source : Option < RuleConfiguration < biome_rule_options :: no_empty_source :: NoEmptySourceOptions >> , # [doc = "Require Promise-like statements to be handled appropriately."] # [serde (skip_serializing_if = "Option::is_none")] pub no_floating_promises : Option < RuleFixConfiguration < biome_rule_options :: no_floating_promises :: NoFloatingPromisesOptions >> , # [doc = "Prevent import cycles."] # [serde (skip_serializing_if = "Option::is_none")] pub no_import_cycles : Option < RuleConfiguration < biome_rule_options :: no_import_cycles :: NoImportCyclesOptions >> , # [doc = "Disallow string literals inside JSX elements."] # [serde (skip_serializing_if = "Option::is_none")] pub no_jsx_literals : Option < RuleConfiguration < biome_rule_options :: no_jsx_literals :: NoJsxLiteralsOptions >> , # [doc = "Disallow Promises to be used in places where they are almost certainly a mistake."] # [serde (skip_serializing_if = "Option::is_none")] pub no_misused_promises : Option < RuleFixConfiguration < biome_rule_options :: no_misused_promises :: NoMisusedPromisesOptions >> , # [doc = "Prevent client components from being async functions."] # [serde (skip_serializing_if = "Option::is_none")] pub no_next_async_client_component : Option < RuleConfiguration < biome_rule_options :: no_next_async_client_component :: NoNextAsyncClientComponentOptions >> , # [doc = "Disallow non-null assertions after optional chaining expressions."] # [serde (skip_serializing_if = "Option::is_none")] pub no_non_null_asserted_optional_chain : Option < RuleConfiguration < biome_rule_options :: no_non_null_asserted_optional_chain :: NoNonNullAssertedOptionalChainOptions >> , # [doc = "Disallow useVisibleTask$() functions in Qwik components."] # [serde (skip_serializing_if = "Option::is_none")] pub no_qwik_use_visible_task : Option < RuleConfiguration < biome_rule_options :: no_qwik_use_visible_task :: NoQwikUseVisibleTaskOptions >> , # [doc = "Replaces usages of forwardRef with passing ref as a prop."] # [serde (skip_serializing_if = "Option::is_none")] pub no_react_forward_ref : Option < RuleFixConfiguration < biome_rule_options :: no_react_forward_ref :: NoReactForwardRefOptions >> , # [doc = "Disallow usage of sensitive data such as API keys and tokens."] # [serde (skip_serializing_if = "Option::is_none")] pub no_secrets : Option < RuleConfiguration < biome_rule_options :: no_secrets :: NoSecretsOptions >> , # [doc = "Disallow variable declarations from shadowing variables declared in the outer scope."] # [serde (skip_serializing_if = "Option::is_none")] pub no_shadow : Option < RuleConfiguration < biome_rule_options :: no_shadow :: NoShadowOptions >> , # [doc = "Disallow unnecessary type-based conditions that can be statically determined as redundant."] # [serde (skip_serializing_if = "Option::is_none")] pub no_unnecessary_conditions : Option < RuleConfiguration < biome_rule_options :: no_unnecessary_conditions :: NoUnnecessaryConditionsOptions >> , # [doc = "Warn when importing non-existing exports."] # [serde (skip_serializing_if = "Option::is_none")] pub no_unresolved_imports : Option < RuleConfiguration < biome_rule_options :: no_unresolved_imports :: NoUnresolvedImportsOptions >> , # [doc = "Disallow expression statements that are neither a function call nor an assignment."] # [serde (skip_serializing_if = "Option::is_none")] pub no_unused_expressions : Option < RuleConfiguration < biome_rule_options :: no_unused_expressions :: NoUnusedExpressionsOptions >> , # [doc = "Disallow unused catch bindings."] # [serde (skip_serializing_if = "Option::is_none")] pub no_useless_catch_binding : Option < RuleFixConfiguration < biome_rule_options :: no_useless_catch_binding :: NoUselessCatchBindingOptions >> , # [doc = "Disallow the use of useless undefined."] # [serde (skip_serializing_if = "Option::is_none")] pub no_useless_undefined : Option < RuleFixConfiguration < biome_rule_options :: no_useless_undefined :: NoUselessUndefinedOptions >> , # [doc = "Enforce that Vue component data options are declared as functions."] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_data_object_declaration : Option < RuleFixConfiguration < biome_rule_options :: no_vue_data_object_declaration :: NoVueDataObjectDeclarationOptions >> , # [doc = "Disallow duplicate keys in Vue component data, methods, computed properties, and other options."] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_duplicate_keys : Option < RuleConfiguration < biome_rule_options :: no_vue_duplicate_keys :: NoVueDuplicateKeysOptions >> , # [doc = "Disallow reserved keys in Vue component data and computed properties."] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_reserved_keys : Option < RuleConfiguration < biome_rule_options :: no_vue_reserved_keys :: NoVueReservedKeysOptions >> , # [doc = "Disallow reserved names to be used as props."] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_reserved_props : Option < RuleConfiguration < biome_rule_options :: no_vue_reserved_props :: NoVueReservedPropsOptions >> , # [doc = "Enforces href attribute for \\ elements."] # [serde (skip_serializing_if = "Option::is_none")] pub use_anchor_href : Option < RuleConfiguration < biome_rule_options :: use_anchor_href :: UseAnchorHrefOptions >> , # [doc = "Enforce consistent arrow function bodies."] # [serde (skip_serializing_if = "Option::is_none")] pub use_consistent_arrow_return : Option < RuleFixConfiguration < biome_rule_options :: use_consistent_arrow_return :: UseConsistentArrowReturnOptions >> , # [doc = "Enforce type definitions to consistently use either interface or type."] # [serde (skip_serializing_if = "Option::is_none")] pub use_consistent_type_definitions : Option < RuleFixConfiguration < biome_rule_options :: use_consistent_type_definitions :: UseConsistentTypeDefinitionsOptions >> , # [doc = "Require the @deprecated directive to specify a deletion date."] # [serde (skip_serializing_if = "Option::is_none")] pub use_deprecated_date : Option < RuleConfiguration < biome_rule_options :: use_deprecated_date :: UseDeprecatedDateOptions >> , # [doc = "Require switch-case statements to be exhaustive."] # [serde (skip_serializing_if = "Option::is_none")] pub use_exhaustive_switch_cases : Option < RuleFixConfiguration < biome_rule_options :: use_exhaustive_switch_cases :: UseExhaustiveSwitchCasesOptions >> , # [doc = "Enforce types in functions, methods, variables, and parameters."] # [serde (skip_serializing_if = "Option::is_none")] pub use_explicit_type : Option < RuleConfiguration < biome_rule_options :: use_explicit_type :: UseExplicitTypeOptions >> , # [doc = "Enforces that \\ elements have both width and height attributes."] # [serde (skip_serializing_if = "Option::is_none")] pub use_image_size : Option < RuleConfiguration < biome_rule_options :: use_image_size :: UseImageSizeOptions >> , # [doc = "Enforce a maximum number of parameters in function definitions."] # [serde (skip_serializing_if = "Option::is_none")] pub use_max_params : Option < RuleConfiguration < biome_rule_options :: use_max_params :: UseMaxParamsOptions >> , # [doc = "Prefer using the class prop as a classlist over the classnames helper."] # [serde (skip_serializing_if = "Option::is_none")] pub use_qwik_classlist : Option < RuleConfiguration < biome_rule_options :: use_qwik_classlist :: UseQwikClasslistOptions >> , # [doc = "Disallow use* hooks outside of component$ or other use* hooks in Qwik applications."] # [serde (skip_serializing_if = "Option::is_none")] pub use_qwik_method_usage : Option < RuleConfiguration < biome_rule_options :: use_qwik_method_usage :: UseQwikMethodUsageOptions >> , # [doc = "Disallow unserializable expressions in Qwik dollar ($) scopes."] # [serde (skip_serializing_if = "Option::is_none")] pub use_qwik_valid_lexical_scope : Option < RuleConfiguration < biome_rule_options :: use_qwik_valid_lexical_scope :: UseQwikValidLexicalScopeOptions >> , # [doc = "Enforce that components are defined as functions and never as classes."] # [serde (skip_serializing_if = "Option::is_none")] pub use_react_function_components : Option < RuleConfiguration < biome_rule_options :: use_react_function_components :: UseReactFunctionComponentsOptions >> , # [doc = "Enforce the sorting of CSS utility classes."] # [serde (skip_serializing_if = "Option::is_none")] pub use_sorted_classes : Option < RuleFixConfiguration < biome_rule_options :: use_sorted_classes :: UseSortedClassesOptions >> , # [doc = "Enforce multi-word component names in Vue components."] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_multi_word_component_names : Option < RuleConfiguration < biome_rule_options :: use_vue_multi_word_component_names :: UseVueMultiWordComponentNamesOptions >> } +pub struct Nursery { # [doc = r" Enables the recommended rules for this group"] # [serde (skip_serializing_if = "Option::is_none")] pub recommended : Option < bool > , # [doc = "Enforce Playwright async APIs to be awaited or returned."] # [serde (skip_serializing_if = "Option::is_none")] pub missing_playwright_await : Option < RuleFixConfiguration < biome_rule_options :: missing_playwright_await :: MissingPlaywrightAwaitOptions >> , # [doc = "Restrict imports of deprecated exports."] # [serde (skip_serializing_if = "Option::is_none")] pub no_deprecated_imports : Option < RuleConfiguration < biome_rule_options :: no_deprecated_imports :: NoDeprecatedImportsOptions >> , # [doc = "Prevent the listing of duplicate dependencies. The rule supports the following dependency groups: \"bundledDependencies\", \"bundleDependencies\", \"dependencies\", \"devDependencies\", \"overrides\", \"optionalDependencies\", and \"peerDependencies\"."] # [serde (skip_serializing_if = "Option::is_none")] pub no_duplicate_dependencies : Option < RuleConfiguration < biome_rule_options :: no_duplicate_dependencies :: NoDuplicateDependenciesOptions >> , # [doc = "Disallow empty sources."] # [serde (skip_serializing_if = "Option::is_none")] pub no_empty_source : Option < RuleConfiguration < biome_rule_options :: no_empty_source :: NoEmptySourceOptions >> , # [doc = "Require Promise-like statements to be handled appropriately."] # [serde (skip_serializing_if = "Option::is_none")] pub no_floating_promises : Option < RuleFixConfiguration < biome_rule_options :: no_floating_promises :: NoFloatingPromisesOptions >> , # [doc = "Prevent import cycles."] # [serde (skip_serializing_if = "Option::is_none")] pub no_import_cycles : Option < RuleConfiguration < biome_rule_options :: no_import_cycles :: NoImportCyclesOptions >> , # [doc = "Disallow string literals inside JSX elements."] # [serde (skip_serializing_if = "Option::is_none")] pub no_jsx_literals : Option < RuleConfiguration < biome_rule_options :: no_jsx_literals :: NoJsxLiteralsOptions >> , # [doc = "Disallow Promises to be used in places where they are almost certainly a mistake."] # [serde (skip_serializing_if = "Option::is_none")] pub no_misused_promises : Option < RuleFixConfiguration < biome_rule_options :: no_misused_promises :: NoMisusedPromisesOptions >> , # [doc = "Prevent client components from being async functions."] # [serde (skip_serializing_if = "Option::is_none")] pub no_next_async_client_component : Option < RuleConfiguration < biome_rule_options :: no_next_async_client_component :: NoNextAsyncClientComponentOptions >> , # [doc = "Disallow non-null assertions after optional chaining expressions."] # [serde (skip_serializing_if = "Option::is_none")] pub no_non_null_asserted_optional_chain : Option < RuleConfiguration < biome_rule_options :: no_non_null_asserted_optional_chain :: NoNonNullAssertedOptionalChainOptions >> , # [doc = "Disallow usage of element handles (page.$() and page.$$())."] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_element_handle : Option < RuleConfiguration < biome_rule_options :: no_playwright_element_handle :: NoPlaywrightElementHandleOptions >> , # [doc = "Disallow usage of page.$eval() and page.$$eval()."] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_eval : Option < RuleConfiguration < biome_rule_options :: no_playwright_eval :: NoPlaywrightEvalOptions >> , # [doc = "Disallow usage of .only annotation in Playwright tests."] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_focused_test : Option < RuleConfiguration < biome_rule_options :: no_playwright_focused_test :: NoPlaywrightFocusedTestOptions >> , # [doc = "Disallow usage of the { force: true } option."] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_force_option : Option < RuleConfiguration < biome_rule_options :: no_playwright_force_option :: NoPlaywrightForceOptionOptions >> , # [doc = "Disallow usage of the networkidle option."] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_networkidle : Option < RuleConfiguration < biome_rule_options :: no_playwright_networkidle :: NoPlaywrightNetworkidleOptions >> , # [doc = "Disallow using page.pause()."] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_page_pause : Option < RuleConfiguration < biome_rule_options :: no_playwright_page_pause :: NoPlaywrightPagePauseOptions >> , # [doc = "Disallow usage of .skip annotation in Playwright tests."] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_skipped_test : Option < RuleConfiguration < biome_rule_options :: no_playwright_skipped_test :: NoPlaywrightSkippedTestOptions >> , # [doc = "Disallow unnecessary await for Playwright methods that don't return promises."] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_useless_await : Option < RuleFixConfiguration < biome_rule_options :: no_playwright_useless_await :: NoPlaywrightUselessAwaitOptions >> , # [doc = "Enforce valid describe() callback."] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_valid_describe_callback : Option < RuleConfiguration < biome_rule_options :: no_playwright_valid_describe_callback :: NoPlaywrightValidDescribeCallbackOptions >> , # [doc = "Disallow using page.waitForNavigation()."] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_wait_for_navigation : Option < RuleConfiguration < biome_rule_options :: no_playwright_wait_for_navigation :: NoPlaywrightWaitForNavigationOptions >> , # [doc = "Disallow using page.waitForSelector()."] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_wait_for_selector : Option < RuleConfiguration < biome_rule_options :: no_playwright_wait_for_selector :: NoPlaywrightWaitForSelectorOptions >> , # [doc = "Disallow using page.waitForTimeout()."] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_wait_for_timeout : Option < RuleConfiguration < biome_rule_options :: no_playwright_wait_for_timeout :: NoPlaywrightWaitForTimeoutOptions >> , # [doc = "Disallow useVisibleTask$() functions in Qwik components."] # [serde (skip_serializing_if = "Option::is_none")] pub no_qwik_use_visible_task : Option < RuleConfiguration < biome_rule_options :: no_qwik_use_visible_task :: NoQwikUseVisibleTaskOptions >> , # [doc = "Replaces usages of forwardRef with passing ref as a prop."] # [serde (skip_serializing_if = "Option::is_none")] pub no_react_forward_ref : Option < RuleFixConfiguration < biome_rule_options :: no_react_forward_ref :: NoReactForwardRefOptions >> , # [doc = "Disallow usage of sensitive data such as API keys and tokens."] # [serde (skip_serializing_if = "Option::is_none")] pub no_secrets : Option < RuleConfiguration < biome_rule_options :: no_secrets :: NoSecretsOptions >> , # [doc = "Disallow variable declarations from shadowing variables declared in the outer scope."] # [serde (skip_serializing_if = "Option::is_none")] pub no_shadow : Option < RuleConfiguration < biome_rule_options :: no_shadow :: NoShadowOptions >> , # [doc = "Disallow unnecessary type-based conditions that can be statically determined as redundant."] # [serde (skip_serializing_if = "Option::is_none")] pub no_unnecessary_conditions : Option < RuleConfiguration < biome_rule_options :: no_unnecessary_conditions :: NoUnnecessaryConditionsOptions >> , # [doc = "Warn when importing non-existing exports."] # [serde (skip_serializing_if = "Option::is_none")] pub no_unresolved_imports : Option < RuleConfiguration < biome_rule_options :: no_unresolved_imports :: NoUnresolvedImportsOptions >> , # [doc = "Disallow expression statements that are neither a function call nor an assignment."] # [serde (skip_serializing_if = "Option::is_none")] pub no_unused_expressions : Option < RuleConfiguration < biome_rule_options :: no_unused_expressions :: NoUnusedExpressionsOptions >> , # [doc = "Disallow unused catch bindings."] # [serde (skip_serializing_if = "Option::is_none")] pub no_useless_catch_binding : Option < RuleFixConfiguration < biome_rule_options :: no_useless_catch_binding :: NoUselessCatchBindingOptions >> , # [doc = "Disallow the use of useless undefined."] # [serde (skip_serializing_if = "Option::is_none")] pub no_useless_undefined : Option < RuleFixConfiguration < biome_rule_options :: no_useless_undefined :: NoUselessUndefinedOptions >> , # [doc = "Enforce that Vue component data options are declared as functions."] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_data_object_declaration : Option < RuleFixConfiguration < biome_rule_options :: no_vue_data_object_declaration :: NoVueDataObjectDeclarationOptions >> , # [doc = "Disallow duplicate keys in Vue component data, methods, computed properties, and other options."] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_duplicate_keys : Option < RuleConfiguration < biome_rule_options :: no_vue_duplicate_keys :: NoVueDuplicateKeysOptions >> , # [doc = "Disallow reserved keys in Vue component data and computed properties."] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_reserved_keys : Option < RuleConfiguration < biome_rule_options :: no_vue_reserved_keys :: NoVueReservedKeysOptions >> , # [doc = "Disallow reserved names to be used as props."] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_reserved_props : Option < RuleConfiguration < biome_rule_options :: no_vue_reserved_props :: NoVueReservedPropsOptions >> , # [doc = "Enforces href attribute for \\ elements."] # [serde (skip_serializing_if = "Option::is_none")] pub use_anchor_href : Option < RuleConfiguration < biome_rule_options :: use_anchor_href :: UseAnchorHrefOptions >> , # [doc = "Enforce consistent arrow function bodies."] # [serde (skip_serializing_if = "Option::is_none")] pub use_consistent_arrow_return : Option < RuleFixConfiguration < biome_rule_options :: use_consistent_arrow_return :: UseConsistentArrowReturnOptions >> , # [doc = "Enforce type definitions to consistently use either interface or type."] # [serde (skip_serializing_if = "Option::is_none")] pub use_consistent_type_definitions : Option < RuleFixConfiguration < biome_rule_options :: use_consistent_type_definitions :: UseConsistentTypeDefinitionsOptions >> , # [doc = "Require the @deprecated directive to specify a deletion date."] # [serde (skip_serializing_if = "Option::is_none")] pub use_deprecated_date : Option < RuleConfiguration < biome_rule_options :: use_deprecated_date :: UseDeprecatedDateOptions >> , # [doc = "Require switch-case statements to be exhaustive."] # [serde (skip_serializing_if = "Option::is_none")] pub use_exhaustive_switch_cases : Option < RuleFixConfiguration < biome_rule_options :: use_exhaustive_switch_cases :: UseExhaustiveSwitchCasesOptions >> , # [doc = "Enforce types in functions, methods, variables, and parameters."] # [serde (skip_serializing_if = "Option::is_none")] pub use_explicit_type : Option < RuleConfiguration < biome_rule_options :: use_explicit_type :: UseExplicitTypeOptions >> , # [doc = "Enforces that \\ elements have both width and height attributes."] # [serde (skip_serializing_if = "Option::is_none")] pub use_image_size : Option < RuleConfiguration < biome_rule_options :: use_image_size :: UseImageSizeOptions >> , # [doc = "Enforce a maximum number of parameters in function definitions."] # [serde (skip_serializing_if = "Option::is_none")] pub use_max_params : Option < RuleConfiguration < biome_rule_options :: use_max_params :: UseMaxParamsOptions >> , # [doc = "Prefer using the class prop as a classlist over the classnames helper."] # [serde (skip_serializing_if = "Option::is_none")] pub use_qwik_classlist : Option < RuleConfiguration < biome_rule_options :: use_qwik_classlist :: UseQwikClasslistOptions >> , # [doc = "Disallow use* hooks outside of component$ or other use* hooks in Qwik applications."] # [serde (skip_serializing_if = "Option::is_none")] pub use_qwik_method_usage : Option < RuleConfiguration < biome_rule_options :: use_qwik_method_usage :: UseQwikMethodUsageOptions >> , # [doc = "Disallow unserializable expressions in Qwik dollar ($) scopes."] # [serde (skip_serializing_if = "Option::is_none")] pub use_qwik_valid_lexical_scope : Option < RuleConfiguration < biome_rule_options :: use_qwik_valid_lexical_scope :: UseQwikValidLexicalScopeOptions >> , # [doc = "Enforce that components are defined as functions and never as classes."] # [serde (skip_serializing_if = "Option::is_none")] pub use_react_function_components : Option < RuleConfiguration < biome_rule_options :: use_react_function_components :: UseReactFunctionComponentsOptions >> , # [doc = "Enforce the sorting of CSS utility classes."] # [serde (skip_serializing_if = "Option::is_none")] pub use_sorted_classes : Option < RuleFixConfiguration < biome_rule_options :: use_sorted_classes :: UseSortedClassesOptions >> , # [doc = "Enforce multi-word component names in Vue components."] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_multi_word_component_names : Option < RuleConfiguration < biome_rule_options :: use_vue_multi_word_component_names :: UseVueMultiWordComponentNamesOptions >> } impl Nursery { const GROUP_NAME: &'static str = "nursery"; pub(crate) const GROUP_RULES: &'static [&'static str] = &[ + "missingPlaywrightAwait", "noDeprecatedImports", "noDuplicateDependencies", "noEmptySource", @@ -4659,6 +4712,18 @@ impl Nursery { "noMisusedPromises", "noNextAsyncClientComponent", "noNonNullAssertedOptionalChain", + "noPlaywrightElementHandle", + "noPlaywrightEval", + "noPlaywrightFocusedTest", + "noPlaywrightForceOption", + "noPlaywrightNetworkidle", + "noPlaywrightPagePause", + "noPlaywrightSkippedTest", + "noPlaywrightUselessAwait", + "noPlaywrightValidDescribeCallback", + "noPlaywrightWaitForNavigation", + "noPlaywrightWaitForSelector", + "noPlaywrightWaitForTimeout", "noQwikUseVisibleTask", "noReactForwardRef", "noSecrets", @@ -4688,7 +4753,7 @@ impl Nursery { "useVueMultiWordComponentNames", ]; const RECOMMENDED_RULES_AS_FILTERS: &'static [RuleFilter<'static>] = - &[RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[8])]; + &[RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[9])]; const ALL_RULES_AS_FILTERS: &'static [RuleFilter<'static>] = &[ RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[0]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[1]), @@ -4726,6 +4791,19 @@ impl Nursery { RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[33]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[34]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[35]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[36]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[37]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[38]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[39]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[40]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[41]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[42]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[43]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[44]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[45]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[46]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[47]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[48]), ]; } impl RuleGroupExt for Nursery { @@ -4737,370 +4815,500 @@ impl RuleGroupExt for Nursery { } fn get_enabled_rules(&self) -> FxHashSet> { let mut index_set = FxHashSet::default(); - if let Some(rule) = self.no_deprecated_imports.as_ref() + if let Some(rule) = self.missing_playwright_await.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[0])); } - if let Some(rule) = self.no_duplicate_dependencies.as_ref() + if let Some(rule) = self.no_deprecated_imports.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[1])); } - if let Some(rule) = self.no_empty_source.as_ref() + if let Some(rule) = self.no_duplicate_dependencies.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[2])); } - if let Some(rule) = self.no_floating_promises.as_ref() + if let Some(rule) = self.no_empty_source.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[3])); } - if let Some(rule) = self.no_import_cycles.as_ref() + if let Some(rule) = self.no_floating_promises.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[4])); } - if let Some(rule) = self.no_jsx_literals.as_ref() + if let Some(rule) = self.no_import_cycles.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[5])); } - if let Some(rule) = self.no_misused_promises.as_ref() + if let Some(rule) = self.no_jsx_literals.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[6])); } - if let Some(rule) = self.no_next_async_client_component.as_ref() + if let Some(rule) = self.no_misused_promises.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[7])); } - if let Some(rule) = self.no_non_null_asserted_optional_chain.as_ref() + if let Some(rule) = self.no_next_async_client_component.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[8])); } - if let Some(rule) = self.no_qwik_use_visible_task.as_ref() + if let Some(rule) = self.no_non_null_asserted_optional_chain.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[9])); } - if let Some(rule) = self.no_react_forward_ref.as_ref() + if let Some(rule) = self.no_playwright_element_handle.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[10])); } - if let Some(rule) = self.no_secrets.as_ref() + if let Some(rule) = self.no_playwright_eval.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[11])); } - if let Some(rule) = self.no_shadow.as_ref() + if let Some(rule) = self.no_playwright_focused_test.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[12])); } - if let Some(rule) = self.no_unnecessary_conditions.as_ref() + if let Some(rule) = self.no_playwright_force_option.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13])); } - if let Some(rule) = self.no_unresolved_imports.as_ref() + if let Some(rule) = self.no_playwright_networkidle.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[14])); } - if let Some(rule) = self.no_unused_expressions.as_ref() + if let Some(rule) = self.no_playwright_page_pause.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[15])); } - if let Some(rule) = self.no_useless_catch_binding.as_ref() + if let Some(rule) = self.no_playwright_skipped_test.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[16])); } - if let Some(rule) = self.no_useless_undefined.as_ref() + if let Some(rule) = self.no_playwright_useless_await.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[17])); } - if let Some(rule) = self.no_vue_data_object_declaration.as_ref() + if let Some(rule) = self.no_playwright_valid_describe_callback.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[18])); } - if let Some(rule) = self.no_vue_duplicate_keys.as_ref() + if let Some(rule) = self.no_playwright_wait_for_navigation.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[19])); } - if let Some(rule) = self.no_vue_reserved_keys.as_ref() + if let Some(rule) = self.no_playwright_wait_for_selector.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20])); } - if let Some(rule) = self.no_vue_reserved_props.as_ref() + if let Some(rule) = self.no_playwright_wait_for_timeout.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21])); } - if let Some(rule) = self.use_anchor_href.as_ref() + if let Some(rule) = self.no_qwik_use_visible_task.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22])); } - if let Some(rule) = self.use_consistent_arrow_return.as_ref() + if let Some(rule) = self.no_react_forward_ref.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23])); } - if let Some(rule) = self.use_consistent_type_definitions.as_ref() + if let Some(rule) = self.no_secrets.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24])); } - if let Some(rule) = self.use_deprecated_date.as_ref() + if let Some(rule) = self.no_shadow.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[25])); } - if let Some(rule) = self.use_exhaustive_switch_cases.as_ref() + if let Some(rule) = self.no_unnecessary_conditions.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[26])); } - if let Some(rule) = self.use_explicit_type.as_ref() + if let Some(rule) = self.no_unresolved_imports.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[27])); } - if let Some(rule) = self.use_image_size.as_ref() + if let Some(rule) = self.no_unused_expressions.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[28])); } - if let Some(rule) = self.use_max_params.as_ref() + if let Some(rule) = self.no_useless_catch_binding.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[29])); } - if let Some(rule) = self.use_qwik_classlist.as_ref() + if let Some(rule) = self.no_useless_undefined.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[30])); } - if let Some(rule) = self.use_qwik_method_usage.as_ref() + if let Some(rule) = self.no_vue_data_object_declaration.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[31])); } - if let Some(rule) = self.use_qwik_valid_lexical_scope.as_ref() + if let Some(rule) = self.no_vue_duplicate_keys.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[32])); } - if let Some(rule) = self.use_react_function_components.as_ref() + if let Some(rule) = self.no_vue_reserved_keys.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[33])); } - if let Some(rule) = self.use_sorted_classes.as_ref() + if let Some(rule) = self.no_vue_reserved_props.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[34])); } - if let Some(rule) = self.use_vue_multi_word_component_names.as_ref() + if let Some(rule) = self.use_anchor_href.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[35])); } + if let Some(rule) = self.use_consistent_arrow_return.as_ref() + && rule.is_enabled() + { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[36])); + } + if let Some(rule) = self.use_consistent_type_definitions.as_ref() + && rule.is_enabled() + { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[37])); + } + if let Some(rule) = self.use_deprecated_date.as_ref() + && rule.is_enabled() + { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[38])); + } + if let Some(rule) = self.use_exhaustive_switch_cases.as_ref() + && rule.is_enabled() + { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[39])); + } + if let Some(rule) = self.use_explicit_type.as_ref() + && rule.is_enabled() + { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[40])); + } + if let Some(rule) = self.use_image_size.as_ref() + && rule.is_enabled() + { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[41])); + } + if let Some(rule) = self.use_max_params.as_ref() + && rule.is_enabled() + { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[42])); + } + if let Some(rule) = self.use_qwik_classlist.as_ref() + && rule.is_enabled() + { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[43])); + } + if let Some(rule) = self.use_qwik_method_usage.as_ref() + && rule.is_enabled() + { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[44])); + } + if let Some(rule) = self.use_qwik_valid_lexical_scope.as_ref() + && rule.is_enabled() + { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[45])); + } + if let Some(rule) = self.use_react_function_components.as_ref() + && rule.is_enabled() + { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[46])); + } + if let Some(rule) = self.use_sorted_classes.as_ref() + && rule.is_enabled() + { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[47])); + } + if let Some(rule) = self.use_vue_multi_word_component_names.as_ref() + && rule.is_enabled() + { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[48])); + } index_set } fn get_disabled_rules(&self) -> FxHashSet> { let mut index_set = FxHashSet::default(); - if let Some(rule) = self.no_deprecated_imports.as_ref() + if let Some(rule) = self.missing_playwright_await.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[0])); } - if let Some(rule) = self.no_duplicate_dependencies.as_ref() + if let Some(rule) = self.no_deprecated_imports.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[1])); } - if let Some(rule) = self.no_empty_source.as_ref() + if let Some(rule) = self.no_duplicate_dependencies.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[2])); } - if let Some(rule) = self.no_floating_promises.as_ref() + if let Some(rule) = self.no_empty_source.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[3])); } - if let Some(rule) = self.no_import_cycles.as_ref() + if let Some(rule) = self.no_floating_promises.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[4])); } - if let Some(rule) = self.no_jsx_literals.as_ref() + if let Some(rule) = self.no_import_cycles.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[5])); } - if let Some(rule) = self.no_misused_promises.as_ref() + if let Some(rule) = self.no_jsx_literals.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[6])); } - if let Some(rule) = self.no_next_async_client_component.as_ref() + if let Some(rule) = self.no_misused_promises.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[7])); } - if let Some(rule) = self.no_non_null_asserted_optional_chain.as_ref() + if let Some(rule) = self.no_next_async_client_component.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[8])); } - if let Some(rule) = self.no_qwik_use_visible_task.as_ref() + if let Some(rule) = self.no_non_null_asserted_optional_chain.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[9])); } - if let Some(rule) = self.no_react_forward_ref.as_ref() + if let Some(rule) = self.no_playwright_element_handle.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[10])); } - if let Some(rule) = self.no_secrets.as_ref() + if let Some(rule) = self.no_playwright_eval.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[11])); } - if let Some(rule) = self.no_shadow.as_ref() + if let Some(rule) = self.no_playwright_focused_test.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[12])); } - if let Some(rule) = self.no_unnecessary_conditions.as_ref() + if let Some(rule) = self.no_playwright_force_option.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13])); } - if let Some(rule) = self.no_unresolved_imports.as_ref() + if let Some(rule) = self.no_playwright_networkidle.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[14])); } - if let Some(rule) = self.no_unused_expressions.as_ref() + if let Some(rule) = self.no_playwright_page_pause.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[15])); } - if let Some(rule) = self.no_useless_catch_binding.as_ref() + if let Some(rule) = self.no_playwright_skipped_test.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[16])); } - if let Some(rule) = self.no_useless_undefined.as_ref() + if let Some(rule) = self.no_playwright_useless_await.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[17])); } - if let Some(rule) = self.no_vue_data_object_declaration.as_ref() + if let Some(rule) = self.no_playwright_valid_describe_callback.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[18])); } - if let Some(rule) = self.no_vue_duplicate_keys.as_ref() + if let Some(rule) = self.no_playwright_wait_for_navigation.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[19])); } - if let Some(rule) = self.no_vue_reserved_keys.as_ref() + if let Some(rule) = self.no_playwright_wait_for_selector.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20])); } - if let Some(rule) = self.no_vue_reserved_props.as_ref() + if let Some(rule) = self.no_playwright_wait_for_timeout.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21])); } - if let Some(rule) = self.use_anchor_href.as_ref() + if let Some(rule) = self.no_qwik_use_visible_task.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22])); } - if let Some(rule) = self.use_consistent_arrow_return.as_ref() + if let Some(rule) = self.no_react_forward_ref.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23])); } - if let Some(rule) = self.use_consistent_type_definitions.as_ref() + if let Some(rule) = self.no_secrets.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24])); } - if let Some(rule) = self.use_deprecated_date.as_ref() + if let Some(rule) = self.no_shadow.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[25])); } - if let Some(rule) = self.use_exhaustive_switch_cases.as_ref() + if let Some(rule) = self.no_unnecessary_conditions.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[26])); } - if let Some(rule) = self.use_explicit_type.as_ref() + if let Some(rule) = self.no_unresolved_imports.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[27])); } - if let Some(rule) = self.use_image_size.as_ref() + if let Some(rule) = self.no_unused_expressions.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[28])); } - if let Some(rule) = self.use_max_params.as_ref() + if let Some(rule) = self.no_useless_catch_binding.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[29])); } - if let Some(rule) = self.use_qwik_classlist.as_ref() + if let Some(rule) = self.no_useless_undefined.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[30])); } - if let Some(rule) = self.use_qwik_method_usage.as_ref() + if let Some(rule) = self.no_vue_data_object_declaration.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[31])); } - if let Some(rule) = self.use_qwik_valid_lexical_scope.as_ref() + if let Some(rule) = self.no_vue_duplicate_keys.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[32])); } - if let Some(rule) = self.use_react_function_components.as_ref() + if let Some(rule) = self.no_vue_reserved_keys.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[33])); } - if let Some(rule) = self.use_sorted_classes.as_ref() + if let Some(rule) = self.no_vue_reserved_props.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[34])); } - if let Some(rule) = self.use_vue_multi_word_component_names.as_ref() + if let Some(rule) = self.use_anchor_href.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[35])); } + if let Some(rule) = self.use_consistent_arrow_return.as_ref() + && rule.is_disabled() + { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[36])); + } + if let Some(rule) = self.use_consistent_type_definitions.as_ref() + && rule.is_disabled() + { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[37])); + } + if let Some(rule) = self.use_deprecated_date.as_ref() + && rule.is_disabled() + { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[38])); + } + if let Some(rule) = self.use_exhaustive_switch_cases.as_ref() + && rule.is_disabled() + { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[39])); + } + if let Some(rule) = self.use_explicit_type.as_ref() + && rule.is_disabled() + { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[40])); + } + if let Some(rule) = self.use_image_size.as_ref() + && rule.is_disabled() + { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[41])); + } + if let Some(rule) = self.use_max_params.as_ref() + && rule.is_disabled() + { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[42])); + } + if let Some(rule) = self.use_qwik_classlist.as_ref() + && rule.is_disabled() + { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[43])); + } + if let Some(rule) = self.use_qwik_method_usage.as_ref() + && rule.is_disabled() + { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[44])); + } + if let Some(rule) = self.use_qwik_valid_lexical_scope.as_ref() + && rule.is_disabled() + { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[45])); + } + if let Some(rule) = self.use_react_function_components.as_ref() + && rule.is_disabled() + { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[46])); + } + if let Some(rule) = self.use_sorted_classes.as_ref() + && rule.is_disabled() + { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[47])); + } + if let Some(rule) = self.use_vue_multi_word_component_names.as_ref() + && rule.is_disabled() + { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[48])); + } index_set } #[doc = r" Checks if, given a rule name, matches one of the rules contained in this category"] @@ -5131,6 +5339,10 @@ impl RuleGroupExt for Nursery { rule_name: &str, ) -> Option<(RulePlainConfiguration, Option)> { match rule_name { + "missingPlaywrightAwait" => self + .missing_playwright_await + .as_ref() + .map(|conf| (conf.level(), conf.get_options())), "noDeprecatedImports" => self .no_deprecated_imports .as_ref() @@ -5167,6 +5379,54 @@ impl RuleGroupExt for Nursery { .no_non_null_asserted_optional_chain .as_ref() .map(|conf| (conf.level(), conf.get_options())), + "noPlaywrightElementHandle" => self + .no_playwright_element_handle + .as_ref() + .map(|conf| (conf.level(), conf.get_options())), + "noPlaywrightEval" => self + .no_playwright_eval + .as_ref() + .map(|conf| (conf.level(), conf.get_options())), + "noPlaywrightFocusedTest" => self + .no_playwright_focused_test + .as_ref() + .map(|conf| (conf.level(), conf.get_options())), + "noPlaywrightForceOption" => self + .no_playwright_force_option + .as_ref() + .map(|conf| (conf.level(), conf.get_options())), + "noPlaywrightNetworkidle" => self + .no_playwright_networkidle + .as_ref() + .map(|conf| (conf.level(), conf.get_options())), + "noPlaywrightPagePause" => self + .no_playwright_page_pause + .as_ref() + .map(|conf| (conf.level(), conf.get_options())), + "noPlaywrightSkippedTest" => self + .no_playwright_skipped_test + .as_ref() + .map(|conf| (conf.level(), conf.get_options())), + "noPlaywrightUselessAwait" => self + .no_playwright_useless_await + .as_ref() + .map(|conf| (conf.level(), conf.get_options())), + "noPlaywrightValidDescribeCallback" => self + .no_playwright_valid_describe_callback + .as_ref() + .map(|conf| (conf.level(), conf.get_options())), + "noPlaywrightWaitForNavigation" => self + .no_playwright_wait_for_navigation + .as_ref() + .map(|conf| (conf.level(), conf.get_options())), + "noPlaywrightWaitForSelector" => self + .no_playwright_wait_for_selector + .as_ref() + .map(|conf| (conf.level(), conf.get_options())), + "noPlaywrightWaitForTimeout" => self + .no_playwright_wait_for_timeout + .as_ref() + .map(|conf| (conf.level(), conf.get_options())), "noQwikUseVisibleTask" => self .no_qwik_use_visible_task .as_ref() @@ -5283,6 +5543,7 @@ impl From for Nursery { fn from(value: GroupPlainConfiguration) -> Self { Self { recommended: None, + missing_playwright_await: Some(value.into()), no_deprecated_imports: Some(value.into()), no_duplicate_dependencies: Some(value.into()), no_empty_source: Some(value.into()), @@ -5292,6 +5553,18 @@ impl From for Nursery { no_misused_promises: Some(value.into()), no_next_async_client_component: Some(value.into()), no_non_null_asserted_optional_chain: Some(value.into()), + no_playwright_element_handle: Some(value.into()), + no_playwright_eval: Some(value.into()), + no_playwright_focused_test: Some(value.into()), + no_playwright_force_option: Some(value.into()), + no_playwright_networkidle: Some(value.into()), + no_playwright_page_pause: Some(value.into()), + no_playwright_skipped_test: Some(value.into()), + no_playwright_useless_await: Some(value.into()), + no_playwright_valid_describe_callback: Some(value.into()), + no_playwright_wait_for_navigation: Some(value.into()), + no_playwright_wait_for_selector: Some(value.into()), + no_playwright_wait_for_timeout: Some(value.into()), no_qwik_use_visible_task: Some(value.into()), no_react_forward_ref: Some(value.into()), no_secrets: Some(value.into()), diff --git a/crates/biome_js_analyze/src/lint/nursery.rs b/crates/biome_js_analyze/src/lint/nursery.rs index 808424f64bad..56ab34e1e59a 100644 --- a/crates/biome_js_analyze/src/lint/nursery.rs +++ b/crates/biome_js_analyze/src/lint/nursery.rs @@ -6,24 +6,24 @@ use biome_analyze::declare_lint_group; pub mod missing_playwright_await; pub mod no_deprecated_imports; pub mod no_empty_source; +pub mod no_floating_promises; +pub mod no_import_cycles; +pub mod no_jsx_literals; +pub mod no_misused_promises; +pub mod no_next_async_client_component; +pub mod no_non_null_asserted_optional_chain; pub mod no_playwright_element_handle; pub mod no_playwright_eval; pub mod no_playwright_focused_test; pub mod no_playwright_force_option; -pub mod no_playwright_useless_await; pub mod no_playwright_networkidle; pub mod no_playwright_page_pause; pub mod no_playwright_skipped_test; +pub mod no_playwright_useless_await; pub mod no_playwright_valid_describe_callback; pub mod no_playwright_wait_for_navigation; pub mod no_playwright_wait_for_selector; pub mod no_playwright_wait_for_timeout; -pub mod no_floating_promises; -pub mod no_import_cycles; -pub mod no_jsx_literals; -pub mod no_misused_promises; -pub mod no_next_async_client_component; -pub mod no_non_null_asserted_optional_chain; pub mod no_qwik_use_visible_task; pub mod no_react_forward_ref; pub mod no_secrets; diff --git a/crates/biome_js_analyze/src/lint/nursery/missing_playwright_await.rs b/crates/biome_js_analyze/src/lint/nursery/missing_playwright_await.rs index 6d5303246a9c..c97d0909027a 100644 --- a/crates/biome_js_analyze/src/lint/nursery/missing_playwright_await.rs +++ b/crates/biome_js_analyze/src/lint/nursery/missing_playwright_await.rs @@ -1,15 +1,13 @@ use biome_analyze::{ - context::RuleContext, declare_lint_rule, FixKind, Ast, Rule, RuleDiagnostic, RuleSource, + Ast, FixKind, Rule, RuleDiagnostic, RuleSource, context::RuleContext, declare_lint_rule, }; use biome_console::markup; use biome_diagnostics::Applicability; use biome_js_factory::make; -use biome_js_syntax::{ - AnyJsExpression, JsArrowFunctionExpression, JsCallExpression, JsModule, T, -}; +use biome_js_syntax::{AnyJsExpression, JsArrowFunctionExpression, JsCallExpression, JsModule, T}; use biome_rowan::{AstNode, BatchMutationExt}; -use crate::{ast_utils::is_in_async_function, JsRuleAction}; +use crate::{JsRuleAction, ast_utils::is_in_async_function}; declare_lint_rule! { /// Enforce Playwright async APIs to be awaited or returned. @@ -111,7 +109,7 @@ impl Rule for MissingPlaywrightAwait { fn run(ctx: &RuleContext) -> Self::Signals { let call_expr = ctx.query(); - + // Check for test.step() calls if is_test_step_call(call_expr) { if !is_properly_handled(call_expr) { @@ -132,7 +130,7 @@ impl Rule for MissingPlaywrightAwait { fn diagnostic(ctx: &RuleContext, state: &Self::State) -> Option { let node = ctx.query(); - + let (message, note) = match state { MissingAwaitType::ExpectMatcher(matcher) => ( markup! { @@ -160,37 +158,28 @@ impl Rule for MissingPlaywrightAwait { ), }; - Some( - RuleDiagnostic::new( - rule_category!(), - node.range(), - message, - ) - .note(note), - ) + Some(RuleDiagnostic::new(rule_category!(), node.range(), message).note(note)) } fn action(ctx: &RuleContext, _: &Self::State) -> Option { let call_expr = ctx.query(); - + // Check if we're in an async context if !is_in_async_context(call_expr.syntax()) { return None; } - + let mut mutation = ctx.root().begin(); - + // Create an await expression - let await_expr = make::js_await_expression( - make::token(T![await]), - call_expr.clone().into(), - ); - + let await_expr = + make::js_await_expression(make::token(T![await]), call_expr.clone().into()); + mutation.replace_element( call_expr.clone().into_syntax().into(), await_expr.into_syntax().into(), ); - + Some(JsRuleAction::new( ctx.metadata().action_category(ctx.category(), ctx.group()), Applicability::MaybeIncorrect, @@ -202,7 +191,7 @@ impl Rule for MissingPlaywrightAwait { fn is_test_step_call(call_expr: &JsCallExpression) -> bool { let callee = call_expr.callee().ok(); - + // Check for test.step pattern if let Some(AnyJsExpression::JsStaticMemberExpression(member)) = callee { if let Ok(member_name) = member.member() { @@ -224,16 +213,16 @@ fn is_test_step_call(call_expr: &JsCallExpression) -> bool { } } } - + false } fn get_async_expect_matcher(call_expr: &JsCallExpression) -> Option { let callee = call_expr.callee().ok()?; - + // Must be a member expression (matcher call) let member_expr = callee.as_js_static_member_expression()?; - + // Get the matcher name let member = member_expr.member().ok()?; let name = member.as_js_name()?; @@ -247,12 +236,12 @@ fn get_async_expect_matcher(call_expr: &JsCallExpression) -> Option bool { /// Checks if a call expression is directly awaited or returned (without checking Promise.all) fn is_call_awaited_or_returned(call_expr: &JsCallExpression) -> bool { let parent = call_expr.syntax().parent(); - + // Check if it's awaited if let Some(parent) = &parent { if parent.kind() == biome_js_syntax::JsSyntaxKind::JS_AWAIT_EXPRESSION { @@ -357,7 +346,9 @@ fn is_call_awaited_or_returned(call_expr: &JsCallExpression) -> bool { if let Some(body_expr) = body.as_any_js_expression() { // Only return true if the call expression is exactly the arrow body // (not just nested somewhere inside it) - if call_expr.syntax().text_trimmed_range() == body_expr.syntax().text_trimmed_range() { + if call_expr.syntax().text_trimmed_range() + == body_expr.syntax().text_trimmed_range() + { return true; } } @@ -393,7 +384,7 @@ fn is_properly_handled(call_expr: &JsCallExpression) -> bool { fn find_enclosing_promise_all(call_expr: &JsCallExpression) -> Option { let mut current = call_expr.syntax().parent(); - + while let Some(node) = current { // Check if we're in an array expression if node.kind() == biome_js_syntax::JsSyntaxKind::JS_ARRAY_EXPRESSION { @@ -401,7 +392,9 @@ fn find_enclosing_promise_all(call_expr: &JsCallExpression) -> Option Option Option bool { if is_in_async_function(node) { return true; } - + // Check if we're at module level (for top-level await) for ancestor in node.ancestors() { if JsModule::can_cast(ancestor.kind()) { return true; } - - // Stop at function boundaries (if we're in a non-async function, + + // Stop at function boundaries (if we're in a non-async function, // being in a module doesn't help) if matches!( ancestor.kind(), @@ -487,7 +480,6 @@ fn is_in_async_context(node: &biome_js_syntax::JsSyntaxNode) -> bool { break; } } - + false } - diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_element_handle.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_element_handle.rs index 34bcb9b76b37..70771c170de4 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_playwright_element_handle.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_playwright_element_handle.rs @@ -1,5 +1,5 @@ use biome_analyze::{ - context::RuleContext, declare_lint_rule, Ast, Rule, RuleDiagnostic, RuleSource, + Ast, Rule, RuleDiagnostic, RuleSource, context::RuleContext, declare_lint_rule, }; use biome_console::markup; use biome_js_syntax::{JsCallExpression, JsStaticMemberExpression}; @@ -64,11 +64,11 @@ impl Rule for NoPlaywrightElementHandle { let callee = call_expr.callee().ok()?; let member_expr = JsStaticMemberExpression::cast_ref(callee.syntax())?; - + let member_name = member_expr.member().ok()?; let member_text = member_name.as_js_name()?.value_token().ok()?; let member_str = member_text.text_trimmed(); - + // Check if the method is $ or $$ if member_str != "$" && member_str != "$$" { return None; @@ -76,20 +76,30 @@ impl Rule for NoPlaywrightElementHandle { let object = member_expr.object().ok()?; let object_text = match object { - biome_js_syntax::AnyJsExpression::JsIdentifierExpression(id) => { - id.name().ok()?.value_token().ok()?.text_trimmed().to_string() - } - biome_js_syntax::AnyJsExpression::JsStaticMemberExpression(member) => { - member.member().ok()?.as_js_name()?.value_token().ok()?.text_trimmed().to_string() - } + biome_js_syntax::AnyJsExpression::JsIdentifierExpression(id) => id + .name() + .ok()? + .value_token() + .ok()? + .text_trimmed() + .to_string(), + biome_js_syntax::AnyJsExpression::JsStaticMemberExpression(member) => member + .member() + .ok()? + .as_js_name()? + .value_token() + .ok()? + .text_trimmed() + .to_string(), _ => return None, }; // Check if it's "page" or "frame" or ends with "Page" or "Frame" - if object_text == "page" - || object_text == "frame" - || object_text.ends_with("Page") - || object_text.ends_with("Frame") { + if object_text == "page" + || object_text == "frame" + || object_text.ends_with("Page") + || object_text.ends_with("Frame") + { Some(member_str.to_string()) } else { None @@ -118,4 +128,3 @@ impl Rule for NoPlaywrightElementHandle { ) } } - diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_eval.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_eval.rs index de9ff31db8c3..1fd0b01b9daa 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_playwright_eval.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_playwright_eval.rs @@ -1,5 +1,5 @@ use biome_analyze::{ - context::RuleContext, declare_lint_rule, Ast, Rule, RuleDiagnostic, RuleSource, + Ast, Rule, RuleDiagnostic, RuleSource, context::RuleContext, declare_lint_rule, }; use biome_console::markup; use biome_js_syntax::{JsCallExpression, JsStaticMemberExpression}; @@ -53,11 +53,11 @@ impl Rule for NoPlaywrightEval { let callee = call_expr.callee().ok()?; let member_expr = JsStaticMemberExpression::cast_ref(callee.syntax())?; - + let member_name = member_expr.member().ok()?; let member_text = member_name.as_js_name()?.value_token().ok()?; let member_str = member_text.text_trimmed(); - + // Check if the method is $eval or $$eval if member_str != "$eval" && member_str != "$$eval" { return None; @@ -65,19 +65,29 @@ impl Rule for NoPlaywrightEval { let object = member_expr.object().ok()?; let object_text = match object { - biome_js_syntax::AnyJsExpression::JsIdentifierExpression(id) => { - id.name().ok()?.value_token().ok()?.text_trimmed().to_string() - } - biome_js_syntax::AnyJsExpression::JsStaticMemberExpression(member) => { - member.member().ok()?.as_js_name()?.value_token().ok()?.text_trimmed().to_string() - } + biome_js_syntax::AnyJsExpression::JsIdentifierExpression(id) => id + .name() + .ok()? + .value_token() + .ok()? + .text_trimmed() + .to_string(), + biome_js_syntax::AnyJsExpression::JsStaticMemberExpression(member) => member + .member() + .ok()? + .as_js_name()? + .value_token() + .ok()? + .text_trimmed() + .to_string(), _ => return None, }; - if object_text == "page" - || object_text == "frame" - || object_text.ends_with("Page") - || object_text.ends_with("Frame") { + if object_text == "page" + || object_text == "frame" + || object_text.ends_with("Page") + || object_text.ends_with("Frame") + { Some(member_str.to_string()) } else { None @@ -87,7 +97,7 @@ impl Rule for NoPlaywrightEval { fn diagnostic(ctx: &RuleContext, state: &Self::State) -> Option { let node = ctx.query(); let is_eval = state == "$eval"; - + Some( RuleDiagnostic::new( rule_category!(), @@ -105,4 +115,3 @@ impl Rule for NoPlaywrightEval { ) } } - diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_focused_test.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_focused_test.rs index b3164d68e8a6..56ba51ffc7fb 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_playwright_focused_test.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_playwright_focused_test.rs @@ -1,5 +1,5 @@ use biome_analyze::{ - context::RuleContext, declare_lint_rule, Ast, Rule, RuleDiagnostic, RuleSource, + Ast, Rule, RuleDiagnostic, RuleSource, context::RuleContext, declare_lint_rule, }; use biome_console::markup; use biome_js_syntax::{JsCallExpression, JsStaticMemberExpression}; @@ -59,18 +59,18 @@ impl Rule for NoPlaywrightFocusedTest { // Check if this is a member expression like test.only() or describe.only() let member_expr = JsStaticMemberExpression::cast_ref(callee.syntax())?; - + // Check if the member being accessed is "only" let member_name = member_expr.member().ok()?; let member_text = member_name.as_js_name()?.value_token().ok()?; - + if member_text.text_trimmed() != "only" { return None; } // Check if the object is test/describe or a chain like test.describe let object = member_expr.object().ok()?; - + fn is_test_or_describe_object(expr: &biome_js_syntax::AnyJsExpression) -> bool { match expr { biome_js_syntax::AnyJsExpression::JsIdentifierExpression(id) => { @@ -128,4 +128,3 @@ impl Rule for NoPlaywrightFocusedTest { ) } } - diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_force_option.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_force_option.rs index c70648ae09a2..637456e107f8 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_playwright_force_option.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_playwright_force_option.rs @@ -1,5 +1,5 @@ use biome_analyze::{ - context::RuleContext, declare_lint_rule, Ast, Rule, RuleDiagnostic, RuleSource, + Ast, Rule, RuleDiagnostic, RuleSource, context::RuleContext, declare_lint_rule, }; use biome_console::markup; use biome_js_syntax::{AnyJsExpression, JsCallExpression, JsObjectExpression}; @@ -87,7 +87,7 @@ impl Rule for NoPlaywrightForceOption { // Check the arguments for { force: true } let args = call_expr.arguments().ok()?; - + for arg in args.args().into_iter().flatten() { if let Some(expr) = arg.as_any_js_expression() { if let AnyJsExpression::JsObjectExpression(obj_expr) = expr { @@ -132,7 +132,9 @@ fn has_force_true(obj_expr: &JsObjectExpression) -> bool { // Check if value is true if let Ok(value) = prop.value() { if let Some(literal) = value.as_any_js_literal_expression() { - if let Some(bool_lit) = literal.as_js_boolean_literal_expression() { + if let Some(bool_lit) = + literal.as_js_boolean_literal_expression() + { if let Ok(value_token) = bool_lit.value_token() { if value_token.text_trimmed() == "true" { return true; @@ -147,7 +149,6 @@ fn has_force_true(obj_expr: &JsObjectExpression) -> bool { } } } - + false } - diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_networkidle.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_networkidle.rs index b072395eb639..255f5a5c7538 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_playwright_networkidle.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_playwright_networkidle.rs @@ -1,5 +1,5 @@ use biome_analyze::{ - context::RuleContext, declare_lint_rule, Ast, Rule, RuleDiagnostic, RuleSource, + Ast, Rule, RuleDiagnostic, RuleSource, context::RuleContext, declare_lint_rule, }; use biome_console::markup; use biome_js_syntax::{ @@ -72,7 +72,7 @@ impl Rule for NoPlaywrightNetworkidle { let [Some(first_arg)] = args.get_arguments_by_index([0]) else { return None; }; - + if let Some(expr) = first_arg.as_any_js_expression() { if let Some(literal) = expr.as_any_js_literal_expression() { if let Some(string_lit) = literal.as_js_string_literal_expression() { @@ -89,7 +89,7 @@ impl Rule for NoPlaywrightNetworkidle { // For navigation methods, check if options object has waitUntil: 'networkidle' if is_navigation_method { let args = call_expr.arguments().ok()?; - + // Navigation methods typically have options as the second argument for arg in args.args().into_iter().flatten() { if let Some(expr) = arg.as_any_js_expression() { @@ -136,7 +136,9 @@ fn has_networkidle_option(obj_expr: &JsObjectExpression) -> bool { // Check if value is 'networkidle' if let Ok(value) = prop.value() { if let Some(literal_expr) = value.as_any_js_literal_expression() { - if let Some(string_lit) = literal_expr.as_js_string_literal_expression() { + if let Some(string_lit) = + literal_expr.as_js_string_literal_expression() + { if let Ok(inner) = string_lit.inner_string_text() { if inner.text() == "networkidle" { return true; @@ -151,7 +153,6 @@ fn has_networkidle_option(obj_expr: &JsObjectExpression) -> bool { } } } - + false } - diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_page_pause.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_page_pause.rs index bfa9af446c69..4d4661cab2a1 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_playwright_page_pause.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_playwright_page_pause.rs @@ -1,5 +1,5 @@ use biome_analyze::{ - context::RuleContext, declare_lint_rule, Ast, Rule, RuleDiagnostic, RuleSource, + Ast, Rule, RuleDiagnostic, RuleSource, context::RuleContext, declare_lint_rule, }; use biome_console::markup; use biome_js_syntax::{JsCallExpression, JsStaticMemberExpression}; @@ -57,11 +57,11 @@ impl Rule for NoPlaywrightPagePause { // Check if this is a member expression (e.g., page.pause()) let member_expr = JsStaticMemberExpression::cast_ref(callee.syntax())?; - + // Check if the member being accessed is "pause" let member_name = member_expr.member().ok()?; let member_text = member_name.as_js_name()?.value_token().ok()?; - + if member_text.text_trimmed() != "pause" { return None; } @@ -69,21 +69,33 @@ impl Rule for NoPlaywrightPagePause { // Check if the object is "page" or "frame" let object = member_expr.object().ok()?; let object_text = match object { - biome_js_syntax::AnyJsExpression::JsIdentifierExpression(id) => { - id.name().ok()?.value_token().ok()?.text_trimmed().to_string() - } + biome_js_syntax::AnyJsExpression::JsIdentifierExpression(id) => id + .name() + .ok()? + .value_token() + .ok()? + .text_trimmed() + .to_string(), biome_js_syntax::AnyJsExpression::JsStaticMemberExpression(member) => { // Handle cases like "context.page.pause()" - member.member().ok()?.as_js_name()?.value_token().ok()?.text_trimmed().to_string() + member + .member() + .ok()? + .as_js_name()? + .value_token() + .ok()? + .text_trimmed() + .to_string() } _ => return None, }; // Check if it's "page" or "frame" or ends with "Page" or "Frame" (for variable names) - if object_text == "page" - || object_text == "frame" - || object_text.ends_with("Page") - || object_text.ends_with("Frame") { + if object_text == "page" + || object_text == "frame" + || object_text.ends_with("Page") + || object_text.ends_with("Frame") + { Some(()) } else { None diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_skipped_test.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_skipped_test.rs index ff43f1cdb4c0..19ab1cd1cd17 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_playwright_skipped_test.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_playwright_skipped_test.rs @@ -1,5 +1,5 @@ use biome_analyze::{ - context::RuleContext, declare_lint_rule, Ast, Rule, RuleDiagnostic, RuleSource, + Ast, Rule, RuleDiagnostic, RuleSource, context::RuleContext, declare_lint_rule, }; use biome_console::markup; use biome_js_syntax::{JsCallExpression, JsStaticMemberExpression}; @@ -58,18 +58,18 @@ impl Rule for NoPlaywrightSkippedTest { // Check if this is a member expression like test.skip() or describe.skip() let member_expr = JsStaticMemberExpression::cast_ref(callee.syntax())?; - + // Check if the member being accessed is "skip" let member_name = member_expr.member().ok()?; let member_text = member_name.as_js_name()?.value_token().ok()?; - + if member_text.text_trimmed() != "skip" { return None; } // Check if the object is test/describe or a chain like test.describe let object = member_expr.object().ok()?; - + fn is_test_or_describe_object(expr: &biome_js_syntax::AnyJsExpression) -> bool { match expr { biome_js_syntax::AnyJsExpression::JsIdentifierExpression(id) => { @@ -127,4 +127,3 @@ impl Rule for NoPlaywrightSkippedTest { ) } } - diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_useless_await.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_useless_await.rs index f893ac57d1b4..29d5be4ecfec 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_playwright_useless_await.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_playwright_useless_await.rs @@ -1,5 +1,5 @@ use biome_analyze::{ - context::RuleContext, declare_lint_rule, FixKind, Ast, Rule, RuleDiagnostic, RuleSource, + Ast, FixKind, Rule, RuleDiagnostic, RuleSource, context::RuleContext, declare_lint_rule, }; use biome_console::markup; use biome_diagnostics::Applicability; @@ -134,7 +134,7 @@ impl Rule for NoPlaywrightUselessAwait { fn run(ctx: &RuleContext) -> Self::Signals { let await_expr = ctx.query(); let argument = await_expr.argument().ok()?; - + // Check if the awaited expression is a call expression let call_expr = argument.as_js_call_expression()?; let callee = call_expr.callee().ok()?; @@ -190,14 +190,14 @@ impl Rule for NoPlaywrightUselessAwait { fn action(ctx: &RuleContext, _: &Self::State) -> Option { let await_expr = ctx.query(); let argument = await_expr.argument().ok()?; - + let mut mutation = ctx.root().begin(); // Replace the entire await expression with just its argument mutation.replace_element( await_expr.clone().into_syntax().into(), argument.into_syntax().into(), ); - + Some(JsRuleAction::new( ctx.metadata().action_category(ctx.category(), ctx.group()), Applicability::Always, @@ -213,7 +213,7 @@ fn is_page_or_frame(expr: &AnyJsExpression) -> bool { if let Ok(name) = id.name() { if let Ok(token) = name.value_token() { let text = token.text_trimmed(); - return text == "page" + return text == "page" || text == "frame" || text.ends_with("Page") || text.ends_with("Frame"); @@ -226,7 +226,7 @@ fn is_page_or_frame(expr: &AnyJsExpression) -> bool { if let Some(name) = member_name.as_js_name() { if let Ok(token) = name.value_token() { let text = token.text_trimmed(); - return text == "page" + return text == "page" || text == "frame" || text.ends_with("Page") || text.ends_with("Frame"); @@ -241,7 +241,7 @@ fn is_page_or_frame(expr: &AnyJsExpression) -> bool { fn is_sync_expect_call(call_expr: &JsCallExpression) -> bool { let callee = call_expr.callee().ok(); - + // Check if this is an expect().matcher() pattern // The call should be a member expression where the object is expect() let member_expr = match callee { @@ -272,7 +272,7 @@ fn is_sync_expect_call(call_expr: &JsCallExpression) -> bool { let object = member_expr.object().ok(); if let Some(AnyJsExpression::JsCallExpression(expect_call)) = object { let expect_callee = expect_call.callee().ok(); - + // Check if it's expect (not expect.poll or expect with resolves/rejects) match expect_callee { Some(AnyJsExpression::JsIdentifierExpression(id)) => { @@ -310,7 +310,7 @@ fn has_async_modifier(expect_call: &JsCallExpression, final_call: &JsCallExpress // Walk the chain from expect_call to final_call looking for "poll", "resolves", "rejects" let mut current = final_call.syntax().clone(); let expect_syntax = expect_call.syntax(); - + while current != *expect_syntax { if let Some(member) = JsStaticMemberExpression::cast_ref(¤t) { if let Ok(member_name) = member.member() { @@ -338,7 +338,6 @@ fn has_async_modifier(expect_call: &JsCallExpression, final_call: &JsCallExpress break; } } - + false } - diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_valid_describe_callback.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_valid_describe_callback.rs index f5cfb76d4797..8eee2b6ae91c 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_playwright_valid_describe_callback.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_playwright_valid_describe_callback.rs @@ -1,10 +1,8 @@ use biome_analyze::{ - context::RuleContext, declare_lint_rule, Ast, Rule, RuleDiagnostic, RuleSource, + Ast, Rule, RuleDiagnostic, RuleSource, context::RuleContext, declare_lint_rule, }; use biome_console::markup; -use biome_js_syntax::{ - AnyJsExpression, JsCallExpression, -}; +use biome_js_syntax::{AnyJsExpression, JsCallExpression}; use biome_rowan::{AstNode, AstSeparatedList}; declare_lint_rule! { @@ -78,7 +76,7 @@ impl Rule for NoPlaywrightValidDescribeCallback { AnyJsExpression::JsStaticMemberExpression(member) => { let member_name = member.member().ok()?; let member_text = member_name.as_js_name()?.value_token().ok()?; - + if member_text.text_trimmed() == "describe" { // Check if object is "test" let object = member.object().ok()?; @@ -114,7 +112,7 @@ impl Rule for NoPlaywrightValidDescribeCallback { if arrow.async_token().is_some() { return Some(InvalidReason::Async); } - + // Check if has parameters if let Ok(params) = arrow.parameters() { let has_params = match params { @@ -133,7 +131,7 @@ impl Rule for NoPlaywrightValidDescribeCallback { if func.async_token().is_some() { return Some(InvalidReason::Async); } - + // Check if has parameters if let Ok(params) = func.parameters() { if params.items().len() > 0 { @@ -149,7 +147,7 @@ impl Rule for NoPlaywrightValidDescribeCallback { fn diagnostic(ctx: &RuleContext, state: &Self::State) -> Option { let node = ctx.query(); - + let (message, note) = match state { InvalidReason::Async => ( markup! { "Describe callback should not be ""async""." }, @@ -161,14 +159,6 @@ impl Rule for NoPlaywrightValidDescribeCallback { ), }; - Some( - RuleDiagnostic::new( - rule_category!(), - node.range(), - message, - ) - .note(note), - ) + Some(RuleDiagnostic::new(rule_category!(), node.range(), message).note(note)) } } - diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_navigation.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_navigation.rs index cac5888690d6..8c4a19210ec2 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_navigation.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_navigation.rs @@ -1,5 +1,5 @@ use biome_analyze::{ - context::RuleContext, declare_lint_rule, Ast, Rule, RuleDiagnostic, RuleSource, + Ast, Rule, RuleDiagnostic, RuleSource, context::RuleContext, declare_lint_rule, }; use biome_console::markup; use biome_js_syntax::{JsCallExpression, JsStaticMemberExpression}; @@ -58,29 +58,39 @@ impl Rule for NoPlaywrightWaitForNavigation { let callee = call_expr.callee().ok()?; let member_expr = JsStaticMemberExpression::cast_ref(callee.syntax())?; - + let member_name = member_expr.member().ok()?; let member_text = member_name.as_js_name()?.value_token().ok()?; - + if member_text.text_trimmed() != "waitForNavigation" { return None; } let object = member_expr.object().ok()?; let object_text = match object { - biome_js_syntax::AnyJsExpression::JsIdentifierExpression(id) => { - id.name().ok()?.value_token().ok()?.text_trimmed().to_string() - } - biome_js_syntax::AnyJsExpression::JsStaticMemberExpression(member) => { - member.member().ok()?.as_js_name()?.value_token().ok()?.text_trimmed().to_string() - } + biome_js_syntax::AnyJsExpression::JsIdentifierExpression(id) => id + .name() + .ok()? + .value_token() + .ok()? + .text_trimmed() + .to_string(), + biome_js_syntax::AnyJsExpression::JsStaticMemberExpression(member) => member + .member() + .ok()? + .as_js_name()? + .value_token() + .ok()? + .text_trimmed() + .to_string(), _ => return None, }; - if object_text == "page" - || object_text == "frame" - || object_text.ends_with("Page") - || object_text.ends_with("Frame") { + if object_text == "page" + || object_text == "frame" + || object_text.ends_with("Page") + || object_text.ends_with("Frame") + { Some(()) } else { None @@ -106,4 +116,3 @@ impl Rule for NoPlaywrightWaitForNavigation { ) } } - diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_selector.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_selector.rs index 6ce046abbc3e..fda8c606d6e4 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_selector.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_selector.rs @@ -1,5 +1,5 @@ use biome_analyze::{ - context::RuleContext, declare_lint_rule, Ast, Rule, RuleDiagnostic, RuleSource, + Ast, Rule, RuleDiagnostic, RuleSource, context::RuleContext, declare_lint_rule, }; use biome_console::markup; use biome_js_syntax::{JsCallExpression, JsStaticMemberExpression}; @@ -59,29 +59,39 @@ impl Rule for NoPlaywrightWaitForSelector { let callee = call_expr.callee().ok()?; let member_expr = JsStaticMemberExpression::cast_ref(callee.syntax())?; - + let member_name = member_expr.member().ok()?; let member_text = member_name.as_js_name()?.value_token().ok()?; - + if member_text.text_trimmed() != "waitForSelector" { return None; } let object = member_expr.object().ok()?; let object_text = match object { - biome_js_syntax::AnyJsExpression::JsIdentifierExpression(id) => { - id.name().ok()?.value_token().ok()?.text_trimmed().to_string() - } - biome_js_syntax::AnyJsExpression::JsStaticMemberExpression(member) => { - member.member().ok()?.as_js_name()?.value_token().ok()?.text_trimmed().to_string() - } + biome_js_syntax::AnyJsExpression::JsIdentifierExpression(id) => id + .name() + .ok()? + .value_token() + .ok()? + .text_trimmed() + .to_string(), + biome_js_syntax::AnyJsExpression::JsStaticMemberExpression(member) => member + .member() + .ok()? + .as_js_name()? + .value_token() + .ok()? + .text_trimmed() + .to_string(), _ => return None, }; - if object_text == "page" - || object_text == "frame" - || object_text.ends_with("Page") - || object_text.ends_with("Frame") { + if object_text == "page" + || object_text == "frame" + || object_text.ends_with("Page") + || object_text.ends_with("Frame") + { Some(()) } else { None @@ -107,4 +117,3 @@ impl Rule for NoPlaywrightWaitForSelector { ) } } - diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_timeout.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_timeout.rs index aca2d99cbaca..4febeef312ff 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_timeout.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_timeout.rs @@ -1,5 +1,5 @@ use biome_analyze::{ - context::RuleContext, declare_lint_rule, Ast, Rule, RuleDiagnostic, RuleSource, + Ast, Rule, RuleDiagnostic, RuleSource, context::RuleContext, declare_lint_rule, }; use biome_console::markup; use biome_js_syntax::{JsCallExpression, JsStaticMemberExpression}; @@ -58,29 +58,39 @@ impl Rule for NoPlaywrightWaitForTimeout { let callee = call_expr.callee().ok()?; let member_expr = JsStaticMemberExpression::cast_ref(callee.syntax())?; - + let member_name = member_expr.member().ok()?; let member_text = member_name.as_js_name()?.value_token().ok()?; - + if member_text.text_trimmed() != "waitForTimeout" { return None; } let object = member_expr.object().ok()?; let object_text = match object { - biome_js_syntax::AnyJsExpression::JsIdentifierExpression(id) => { - id.name().ok()?.value_token().ok()?.text_trimmed().to_string() - } - biome_js_syntax::AnyJsExpression::JsStaticMemberExpression(member) => { - member.member().ok()?.as_js_name()?.value_token().ok()?.text_trimmed().to_string() - } + biome_js_syntax::AnyJsExpression::JsIdentifierExpression(id) => id + .name() + .ok()? + .value_token() + .ok()? + .text_trimmed() + .to_string(), + biome_js_syntax::AnyJsExpression::JsStaticMemberExpression(member) => member + .member() + .ok()? + .as_js_name()? + .value_token() + .ok()? + .text_trimmed() + .to_string(), _ => return None, }; - if object_text == "page" - || object_text == "frame" - || object_text.ends_with("Page") - || object_text.ends_with("Frame") { + if object_text == "page" + || object_text == "frame" + || object_text.ends_with("Page") + || object_text.ends_with("Frame") + { Some(()) } else { None @@ -106,4 +116,3 @@ impl Rule for NoPlaywrightWaitForTimeout { ) } } - diff --git a/crates/biome_rule_options/src/lib.rs b/crates/biome_rule_options/src/lib.rs index 1d92577ec12f..8adaa31cce3c 100644 --- a/crates/biome_rule_options/src/lib.rs +++ b/crates/biome_rule_options/src/lib.rs @@ -2,6 +2,7 @@ mod shared; pub use shared::*; +pub mod missing_playwright_await; pub mod no_access_key; pub mod no_accumulating_spread; pub mod no_adjacent_spaces_in_regex; @@ -140,6 +141,18 @@ pub mod no_nonoctal_decimal_escape; pub mod no_octal_escape; pub mod no_parameter_assign; pub mod no_parameter_properties; +pub mod no_playwright_element_handle; +pub mod no_playwright_eval; +pub mod no_playwright_focused_test; +pub mod no_playwright_force_option; +pub mod no_playwright_networkidle; +pub mod no_playwright_page_pause; +pub mod no_playwright_skipped_test; +pub mod no_playwright_useless_await; +pub mod no_playwright_valid_describe_callback; +pub mod no_playwright_wait_for_navigation; +pub mod no_playwright_wait_for_selector; +pub mod no_playwright_wait_for_timeout; pub mod no_positive_tabindex; pub mod no_precision_loss; pub mod no_private_imports; diff --git a/crates/biome_rule_options/src/missing_playwright_await.rs b/crates/biome_rule_options/src/missing_playwright_await.rs new file mode 100644 index 000000000000..72d89583ce33 --- /dev/null +++ b/crates/biome_rule_options/src/missing_playwright_await.rs @@ -0,0 +1,6 @@ +use biome_deserialize_macros::Deserializable; +use serde::{Deserialize, Serialize}; +#[derive(Default, Clone, Debug, Deserialize, Deserializable, Eq, PartialEq, Serialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(rename_all = "camelCase", deny_unknown_fields, default)] +pub struct MissingPlaywrightAwaitOptions {} diff --git a/crates/biome_rule_options/src/no_playwright_element_handle.rs b/crates/biome_rule_options/src/no_playwright_element_handle.rs new file mode 100644 index 000000000000..33da626b62da --- /dev/null +++ b/crates/biome_rule_options/src/no_playwright_element_handle.rs @@ -0,0 +1,6 @@ +use biome_deserialize_macros::Deserializable; +use serde::{Deserialize, Serialize}; +#[derive(Default, Clone, Debug, Deserialize, Deserializable, Eq, PartialEq, Serialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(rename_all = "camelCase", deny_unknown_fields, default)] +pub struct NoPlaywrightElementHandleOptions {} diff --git a/crates/biome_rule_options/src/no_playwright_eval.rs b/crates/biome_rule_options/src/no_playwright_eval.rs new file mode 100644 index 000000000000..81a8d64394eb --- /dev/null +++ b/crates/biome_rule_options/src/no_playwright_eval.rs @@ -0,0 +1,6 @@ +use biome_deserialize_macros::Deserializable; +use serde::{Deserialize, Serialize}; +#[derive(Default, Clone, Debug, Deserialize, Deserializable, Eq, PartialEq, Serialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(rename_all = "camelCase", deny_unknown_fields, default)] +pub struct NoPlaywrightEvalOptions {} diff --git a/crates/biome_rule_options/src/no_playwright_focused_test.rs b/crates/biome_rule_options/src/no_playwright_focused_test.rs new file mode 100644 index 000000000000..df36136e4c3a --- /dev/null +++ b/crates/biome_rule_options/src/no_playwright_focused_test.rs @@ -0,0 +1,6 @@ +use biome_deserialize_macros::Deserializable; +use serde::{Deserialize, Serialize}; +#[derive(Default, Clone, Debug, Deserialize, Deserializable, Eq, PartialEq, Serialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(rename_all = "camelCase", deny_unknown_fields, default)] +pub struct NoPlaywrightFocusedTestOptions {} diff --git a/crates/biome_rule_options/src/no_playwright_force_option.rs b/crates/biome_rule_options/src/no_playwright_force_option.rs new file mode 100644 index 000000000000..a606e3211d0e --- /dev/null +++ b/crates/biome_rule_options/src/no_playwright_force_option.rs @@ -0,0 +1,6 @@ +use biome_deserialize_macros::Deserializable; +use serde::{Deserialize, Serialize}; +#[derive(Default, Clone, Debug, Deserialize, Deserializable, Eq, PartialEq, Serialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(rename_all = "camelCase", deny_unknown_fields, default)] +pub struct NoPlaywrightForceOptionOptions {} diff --git a/crates/biome_rule_options/src/no_playwright_networkidle.rs b/crates/biome_rule_options/src/no_playwright_networkidle.rs new file mode 100644 index 000000000000..afd8fd859818 --- /dev/null +++ b/crates/biome_rule_options/src/no_playwright_networkidle.rs @@ -0,0 +1,6 @@ +use biome_deserialize_macros::Deserializable; +use serde::{Deserialize, Serialize}; +#[derive(Default, Clone, Debug, Deserialize, Deserializable, Eq, PartialEq, Serialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(rename_all = "camelCase", deny_unknown_fields, default)] +pub struct NoPlaywrightNetworkidleOptions {} diff --git a/crates/biome_rule_options/src/no_playwright_page_pause.rs b/crates/biome_rule_options/src/no_playwright_page_pause.rs new file mode 100644 index 000000000000..91f548332b95 --- /dev/null +++ b/crates/biome_rule_options/src/no_playwright_page_pause.rs @@ -0,0 +1,6 @@ +use biome_deserialize_macros::Deserializable; +use serde::{Deserialize, Serialize}; +#[derive(Default, Clone, Debug, Deserialize, Deserializable, Eq, PartialEq, Serialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(rename_all = "camelCase", deny_unknown_fields, default)] +pub struct NoPlaywrightPagePauseOptions {} diff --git a/crates/biome_rule_options/src/no_playwright_skipped_test.rs b/crates/biome_rule_options/src/no_playwright_skipped_test.rs new file mode 100644 index 000000000000..f8723b500de2 --- /dev/null +++ b/crates/biome_rule_options/src/no_playwright_skipped_test.rs @@ -0,0 +1,6 @@ +use biome_deserialize_macros::Deserializable; +use serde::{Deserialize, Serialize}; +#[derive(Default, Clone, Debug, Deserialize, Deserializable, Eq, PartialEq, Serialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(rename_all = "camelCase", deny_unknown_fields, default)] +pub struct NoPlaywrightSkippedTestOptions {} diff --git a/crates/biome_rule_options/src/no_playwright_useless_await.rs b/crates/biome_rule_options/src/no_playwright_useless_await.rs new file mode 100644 index 000000000000..dd99da3c0232 --- /dev/null +++ b/crates/biome_rule_options/src/no_playwright_useless_await.rs @@ -0,0 +1,6 @@ +use biome_deserialize_macros::Deserializable; +use serde::{Deserialize, Serialize}; +#[derive(Default, Clone, Debug, Deserialize, Deserializable, Eq, PartialEq, Serialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(rename_all = "camelCase", deny_unknown_fields, default)] +pub struct NoPlaywrightUselessAwaitOptions {} diff --git a/crates/biome_rule_options/src/no_playwright_valid_describe_callback.rs b/crates/biome_rule_options/src/no_playwright_valid_describe_callback.rs new file mode 100644 index 000000000000..6f90800fbea3 --- /dev/null +++ b/crates/biome_rule_options/src/no_playwright_valid_describe_callback.rs @@ -0,0 +1,6 @@ +use biome_deserialize_macros::Deserializable; +use serde::{Deserialize, Serialize}; +#[derive(Default, Clone, Debug, Deserialize, Deserializable, Eq, PartialEq, Serialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(rename_all = "camelCase", deny_unknown_fields, default)] +pub struct NoPlaywrightValidDescribeCallbackOptions {} diff --git a/crates/biome_rule_options/src/no_playwright_wait_for_navigation.rs b/crates/biome_rule_options/src/no_playwright_wait_for_navigation.rs new file mode 100644 index 000000000000..c27b8fcc035d --- /dev/null +++ b/crates/biome_rule_options/src/no_playwright_wait_for_navigation.rs @@ -0,0 +1,6 @@ +use biome_deserialize_macros::Deserializable; +use serde::{Deserialize, Serialize}; +#[derive(Default, Clone, Debug, Deserialize, Deserializable, Eq, PartialEq, Serialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(rename_all = "camelCase", deny_unknown_fields, default)] +pub struct NoPlaywrightWaitForNavigationOptions {} diff --git a/crates/biome_rule_options/src/no_playwright_wait_for_selector.rs b/crates/biome_rule_options/src/no_playwright_wait_for_selector.rs new file mode 100644 index 000000000000..d3c1cb45e461 --- /dev/null +++ b/crates/biome_rule_options/src/no_playwright_wait_for_selector.rs @@ -0,0 +1,6 @@ +use biome_deserialize_macros::Deserializable; +use serde::{Deserialize, Serialize}; +#[derive(Default, Clone, Debug, Deserialize, Deserializable, Eq, PartialEq, Serialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(rename_all = "camelCase", deny_unknown_fields, default)] +pub struct NoPlaywrightWaitForSelectorOptions {} diff --git a/crates/biome_rule_options/src/no_playwright_wait_for_timeout.rs b/crates/biome_rule_options/src/no_playwright_wait_for_timeout.rs new file mode 100644 index 000000000000..b2b426a4ceb6 --- /dev/null +++ b/crates/biome_rule_options/src/no_playwright_wait_for_timeout.rs @@ -0,0 +1,6 @@ +use biome_deserialize_macros::Deserializable; +use serde::{Deserialize, Serialize}; +#[derive(Default, Clone, Debug, Deserialize, Deserializable, Eq, PartialEq, Serialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(rename_all = "camelCase", deny_unknown_fields, default)] +pub struct NoPlaywrightWaitForTimeoutOptions {} diff --git a/packages/@biomejs/backend-jsonrpc/src/workspace.ts b/packages/@biomejs/backend-jsonrpc/src/workspace.ts index eeda91504047..482142630869 100644 --- a/packages/@biomejs/backend-jsonrpc/src/workspace.ts +++ b/packages/@biomejs/backend-jsonrpc/src/workspace.ts @@ -1625,6 +1625,10 @@ export interface Correctness { * A list of rules that belong to this group */ export interface Nursery { + /** + * Enforce Playwright async APIs to be awaited or returned. + */ + missingPlaywrightAwait?: RuleFixConfiguration_for_MissingPlaywrightAwaitOptions; /** * Restrict imports of deprecated exports. */ @@ -1661,6 +1665,54 @@ export interface Nursery { * Disallow non-null assertions after optional chaining expressions. */ noNonNullAssertedOptionalChain?: RuleConfiguration_for_NoNonNullAssertedOptionalChainOptions; + /** + * Disallow usage of element handles (page.$() and page.$$()). + */ + noPlaywrightElementHandle?: RuleConfiguration_for_NoPlaywrightElementHandleOptions; + /** + * Disallow usage of page.$eval() and page.$$eval(). + */ + noPlaywrightEval?: RuleConfiguration_for_NoPlaywrightEvalOptions; + /** + * Disallow usage of .only annotation in Playwright tests. + */ + noPlaywrightFocusedTest?: RuleConfiguration_for_NoPlaywrightFocusedTestOptions; + /** + * Disallow usage of the { force: true } option. + */ + noPlaywrightForceOption?: RuleConfiguration_for_NoPlaywrightForceOptionOptions; + /** + * Disallow usage of the networkidle option. + */ + noPlaywrightNetworkidle?: RuleConfiguration_for_NoPlaywrightNetworkidleOptions; + /** + * Disallow using page.pause(). + */ + noPlaywrightPagePause?: RuleConfiguration_for_NoPlaywrightPagePauseOptions; + /** + * Disallow usage of .skip annotation in Playwright tests. + */ + noPlaywrightSkippedTest?: RuleConfiguration_for_NoPlaywrightSkippedTestOptions; + /** + * Disallow unnecessary await for Playwright methods that don't return promises. + */ + noPlaywrightUselessAwait?: RuleFixConfiguration_for_NoPlaywrightUselessAwaitOptions; + /** + * Enforce valid describe() callback. + */ + noPlaywrightValidDescribeCallback?: RuleConfiguration_for_NoPlaywrightValidDescribeCallbackOptions; + /** + * Disallow using page.waitForNavigation(). + */ + noPlaywrightWaitForNavigation?: RuleConfiguration_for_NoPlaywrightWaitForNavigationOptions; + /** + * Disallow using page.waitForSelector(). + */ + noPlaywrightWaitForSelector?: RuleConfiguration_for_NoPlaywrightWaitForSelectorOptions; + /** + * Disallow using page.waitForTimeout(). + */ + noPlaywrightWaitForTimeout?: RuleConfiguration_for_NoPlaywrightWaitForTimeoutOptions; /** * Disallow useVisibleTask$() functions in Qwik components. */ @@ -3010,6 +3062,9 @@ export type RuleFixConfiguration_for_UseValidTypeofOptions = export type RuleConfiguration_for_UseYieldOptions = | RulePlainConfiguration | RuleWithOptions_for_UseYieldOptions; +export type RuleFixConfiguration_for_MissingPlaywrightAwaitOptions = + | RulePlainConfiguration + | RuleWithFixOptions_for_MissingPlaywrightAwaitOptions; export type RuleConfiguration_for_NoDeprecatedImportsOptions = | RulePlainConfiguration | RuleWithOptions_for_NoDeprecatedImportsOptions; @@ -3037,6 +3092,42 @@ export type RuleConfiguration_for_NoNextAsyncClientComponentOptions = export type RuleConfiguration_for_NoNonNullAssertedOptionalChainOptions = | RulePlainConfiguration | RuleWithOptions_for_NoNonNullAssertedOptionalChainOptions; +export type RuleConfiguration_for_NoPlaywrightElementHandleOptions = + | RulePlainConfiguration + | RuleWithOptions_for_NoPlaywrightElementHandleOptions; +export type RuleConfiguration_for_NoPlaywrightEvalOptions = + | RulePlainConfiguration + | RuleWithOptions_for_NoPlaywrightEvalOptions; +export type RuleConfiguration_for_NoPlaywrightFocusedTestOptions = + | RulePlainConfiguration + | RuleWithOptions_for_NoPlaywrightFocusedTestOptions; +export type RuleConfiguration_for_NoPlaywrightForceOptionOptions = + | RulePlainConfiguration + | RuleWithOptions_for_NoPlaywrightForceOptionOptions; +export type RuleConfiguration_for_NoPlaywrightNetworkidleOptions = + | RulePlainConfiguration + | RuleWithOptions_for_NoPlaywrightNetworkidleOptions; +export type RuleConfiguration_for_NoPlaywrightPagePauseOptions = + | RulePlainConfiguration + | RuleWithOptions_for_NoPlaywrightPagePauseOptions; +export type RuleConfiguration_for_NoPlaywrightSkippedTestOptions = + | RulePlainConfiguration + | RuleWithOptions_for_NoPlaywrightSkippedTestOptions; +export type RuleFixConfiguration_for_NoPlaywrightUselessAwaitOptions = + | RulePlainConfiguration + | RuleWithFixOptions_for_NoPlaywrightUselessAwaitOptions; +export type RuleConfiguration_for_NoPlaywrightValidDescribeCallbackOptions = + | RulePlainConfiguration + | RuleWithOptions_for_NoPlaywrightValidDescribeCallbackOptions; +export type RuleConfiguration_for_NoPlaywrightWaitForNavigationOptions = + | RulePlainConfiguration + | RuleWithOptions_for_NoPlaywrightWaitForNavigationOptions; +export type RuleConfiguration_for_NoPlaywrightWaitForSelectorOptions = + | RulePlainConfiguration + | RuleWithOptions_for_NoPlaywrightWaitForSelectorOptions; +export type RuleConfiguration_for_NoPlaywrightWaitForTimeoutOptions = + | RulePlainConfiguration + | RuleWithOptions_for_NoPlaywrightWaitForTimeoutOptions; export type RuleConfiguration_for_NoQwikUseVisibleTaskOptions = | RulePlainConfiguration | RuleWithOptions_for_NoQwikUseVisibleTaskOptions; @@ -5403,6 +5494,20 @@ export interface RuleWithOptions_for_UseYieldOptions { */ options: UseYieldOptions; } +export interface RuleWithFixOptions_for_MissingPlaywrightAwaitOptions { + /** + * The kind of the code actions emitted by the rule + */ + fix?: FixKind; + /** + * The severity of the emitted diagnostics by the rule + */ + level: RulePlainConfiguration; + /** + * Rule's options + */ + options: MissingPlaywrightAwaitOptions; +} export interface RuleWithOptions_for_NoDeprecatedImportsOptions { /** * The severity of the emitted diagnostics by the rule @@ -5501,6 +5606,130 @@ export interface RuleWithOptions_for_NoNonNullAssertedOptionalChainOptions { */ options: NoNonNullAssertedOptionalChainOptions; } +export interface RuleWithOptions_for_NoPlaywrightElementHandleOptions { + /** + * The severity of the emitted diagnostics by the rule + */ + level: RulePlainConfiguration; + /** + * Rule's options + */ + options: NoPlaywrightElementHandleOptions; +} +export interface RuleWithOptions_for_NoPlaywrightEvalOptions { + /** + * The severity of the emitted diagnostics by the rule + */ + level: RulePlainConfiguration; + /** + * Rule's options + */ + options: NoPlaywrightEvalOptions; +} +export interface RuleWithOptions_for_NoPlaywrightFocusedTestOptions { + /** + * The severity of the emitted diagnostics by the rule + */ + level: RulePlainConfiguration; + /** + * Rule's options + */ + options: NoPlaywrightFocusedTestOptions; +} +export interface RuleWithOptions_for_NoPlaywrightForceOptionOptions { + /** + * The severity of the emitted diagnostics by the rule + */ + level: RulePlainConfiguration; + /** + * Rule's options + */ + options: NoPlaywrightForceOptionOptions; +} +export interface RuleWithOptions_for_NoPlaywrightNetworkidleOptions { + /** + * The severity of the emitted diagnostics by the rule + */ + level: RulePlainConfiguration; + /** + * Rule's options + */ + options: NoPlaywrightNetworkidleOptions; +} +export interface RuleWithOptions_for_NoPlaywrightPagePauseOptions { + /** + * The severity of the emitted diagnostics by the rule + */ + level: RulePlainConfiguration; + /** + * Rule's options + */ + options: NoPlaywrightPagePauseOptions; +} +export interface RuleWithOptions_for_NoPlaywrightSkippedTestOptions { + /** + * The severity of the emitted diagnostics by the rule + */ + level: RulePlainConfiguration; + /** + * Rule's options + */ + options: NoPlaywrightSkippedTestOptions; +} +export interface RuleWithFixOptions_for_NoPlaywrightUselessAwaitOptions { + /** + * The kind of the code actions emitted by the rule + */ + fix?: FixKind; + /** + * The severity of the emitted diagnostics by the rule + */ + level: RulePlainConfiguration; + /** + * Rule's options + */ + options: NoPlaywrightUselessAwaitOptions; +} +export interface RuleWithOptions_for_NoPlaywrightValidDescribeCallbackOptions { + /** + * The severity of the emitted diagnostics by the rule + */ + level: RulePlainConfiguration; + /** + * Rule's options + */ + options: NoPlaywrightValidDescribeCallbackOptions; +} +export interface RuleWithOptions_for_NoPlaywrightWaitForNavigationOptions { + /** + * The severity of the emitted diagnostics by the rule + */ + level: RulePlainConfiguration; + /** + * Rule's options + */ + options: NoPlaywrightWaitForNavigationOptions; +} +export interface RuleWithOptions_for_NoPlaywrightWaitForSelectorOptions { + /** + * The severity of the emitted diagnostics by the rule + */ + level: RulePlainConfiguration; + /** + * Rule's options + */ + options: NoPlaywrightWaitForSelectorOptions; +} +export interface RuleWithOptions_for_NoPlaywrightWaitForTimeoutOptions { + /** + * The severity of the emitted diagnostics by the rule + */ + level: RulePlainConfiguration; + /** + * Rule's options + */ + options: NoPlaywrightWaitForTimeoutOptions; +} export interface RuleWithOptions_for_NoQwikUseVisibleTaskOptions { /** * The severity of the emitted diagnostics by the rule @@ -8198,6 +8427,7 @@ export interface UseUniqueElementIdsOptions { export interface UseValidForDirectionOptions {} export interface UseValidTypeofOptions {} export interface UseYieldOptions {} +export interface MissingPlaywrightAwaitOptions {} export interface NoDeprecatedImportsOptions {} export interface NoDuplicateDependenciesOptions {} export interface NoEmptySourceOptions { @@ -8230,6 +8460,18 @@ export interface NoJsxLiteralsOptions { export interface NoMisusedPromisesOptions {} export interface NoNextAsyncClientComponentOptions {} export interface NoNonNullAssertedOptionalChainOptions {} +export interface NoPlaywrightElementHandleOptions {} +export interface NoPlaywrightEvalOptions {} +export interface NoPlaywrightFocusedTestOptions {} +export interface NoPlaywrightForceOptionOptions {} +export interface NoPlaywrightNetworkidleOptions {} +export interface NoPlaywrightPagePauseOptions {} +export interface NoPlaywrightSkippedTestOptions {} +export interface NoPlaywrightUselessAwaitOptions {} +export interface NoPlaywrightValidDescribeCallbackOptions {} +export interface NoPlaywrightWaitForNavigationOptions {} +export interface NoPlaywrightWaitForSelectorOptions {} +export interface NoPlaywrightWaitForTimeoutOptions {} export interface NoQwikUseVisibleTaskOptions {} export interface NoReactForwardRefOptions {} export interface NoSecretsOptions { @@ -8993,7 +9235,20 @@ export type Category = | "lint/nursery/noMissingGenericFamilyKeyword" | "lint/nursery/noMisusedPromises" | "lint/nursery/noNextAsyncClientComponent" + | "lint/nursery/missingPlaywrightAwait" | "lint/nursery/noNonNullAssertedOptionalChain" + | "lint/nursery/noPlaywrightElementHandle" + | "lint/nursery/noPlaywrightEval" + | "lint/nursery/noPlaywrightFocusedTest" + | "lint/nursery/noPlaywrightForceOption" + | "lint/nursery/noPlaywrightNetworkidle" + | "lint/nursery/noPlaywrightUselessAwait" + | "lint/nursery/noPlaywrightPagePause" + | "lint/nursery/noPlaywrightSkippedTest" + | "lint/nursery/noPlaywrightValidDescribeCallback" + | "lint/nursery/noPlaywrightWaitForNavigation" + | "lint/nursery/noPlaywrightWaitForSelector" + | "lint/nursery/noPlaywrightWaitForTimeout" | "lint/nursery/noQwikUseVisibleTask" | "lint/nursery/noReactForwardRef" | "lint/nursery/noSecrets" diff --git a/packages/@biomejs/biome/configuration_schema.json b/packages/@biomejs/biome/configuration_schema.json index 3e4332ca5429..9d71ad537b1e 100644 --- a/packages/@biomejs/biome/configuration_schema.json +++ b/packages/@biomejs/biome/configuration_schema.json @@ -2432,6 +2432,16 @@ "additionalProperties": false }, "MaxSize": { "type": "integer", "format": "uint64", "minimum": 1.0 }, + "MissingPlaywrightAwaitConfiguration": { + "anyOf": [ + { "$ref": "#/definitions/RulePlainConfiguration" }, + { "$ref": "#/definitions/RuleWithMissingPlaywrightAwaitOptions" } + ] + }, + "MissingPlaywrightAwaitOptions": { + "type": "object", + "additionalProperties": false + }, "Modifiers": { "type": "array", "items": { "$ref": "#/definitions/RestrictedModifier" }, @@ -3917,6 +3927,128 @@ "type": "object", "additionalProperties": false }, + "NoPlaywrightElementHandleConfiguration": { + "anyOf": [ + { "$ref": "#/definitions/RulePlainConfiguration" }, + { "$ref": "#/definitions/RuleWithNoPlaywrightElementHandleOptions" } + ] + }, + "NoPlaywrightElementHandleOptions": { + "type": "object", + "additionalProperties": false + }, + "NoPlaywrightEvalConfiguration": { + "anyOf": [ + { "$ref": "#/definitions/RulePlainConfiguration" }, + { "$ref": "#/definitions/RuleWithNoPlaywrightEvalOptions" } + ] + }, + "NoPlaywrightEvalOptions": { + "type": "object", + "additionalProperties": false + }, + "NoPlaywrightFocusedTestConfiguration": { + "anyOf": [ + { "$ref": "#/definitions/RulePlainConfiguration" }, + { "$ref": "#/definitions/RuleWithNoPlaywrightFocusedTestOptions" } + ] + }, + "NoPlaywrightFocusedTestOptions": { + "type": "object", + "additionalProperties": false + }, + "NoPlaywrightForceOptionConfiguration": { + "anyOf": [ + { "$ref": "#/definitions/RulePlainConfiguration" }, + { "$ref": "#/definitions/RuleWithNoPlaywrightForceOptionOptions" } + ] + }, + "NoPlaywrightForceOptionOptions": { + "type": "object", + "additionalProperties": false + }, + "NoPlaywrightNetworkidleConfiguration": { + "anyOf": [ + { "$ref": "#/definitions/RulePlainConfiguration" }, + { "$ref": "#/definitions/RuleWithNoPlaywrightNetworkidleOptions" } + ] + }, + "NoPlaywrightNetworkidleOptions": { + "type": "object", + "additionalProperties": false + }, + "NoPlaywrightPagePauseConfiguration": { + "anyOf": [ + { "$ref": "#/definitions/RulePlainConfiguration" }, + { "$ref": "#/definitions/RuleWithNoPlaywrightPagePauseOptions" } + ] + }, + "NoPlaywrightPagePauseOptions": { + "type": "object", + "additionalProperties": false + }, + "NoPlaywrightSkippedTestConfiguration": { + "anyOf": [ + { "$ref": "#/definitions/RulePlainConfiguration" }, + { "$ref": "#/definitions/RuleWithNoPlaywrightSkippedTestOptions" } + ] + }, + "NoPlaywrightSkippedTestOptions": { + "type": "object", + "additionalProperties": false + }, + "NoPlaywrightUselessAwaitConfiguration": { + "anyOf": [ + { "$ref": "#/definitions/RulePlainConfiguration" }, + { "$ref": "#/definitions/RuleWithNoPlaywrightUselessAwaitOptions" } + ] + }, + "NoPlaywrightUselessAwaitOptions": { + "type": "object", + "additionalProperties": false + }, + "NoPlaywrightValidDescribeCallbackConfiguration": { + "anyOf": [ + { "$ref": "#/definitions/RulePlainConfiguration" }, + { + "$ref": "#/definitions/RuleWithNoPlaywrightValidDescribeCallbackOptions" + } + ] + }, + "NoPlaywrightValidDescribeCallbackOptions": { + "type": "object", + "additionalProperties": false + }, + "NoPlaywrightWaitForNavigationConfiguration": { + "anyOf": [ + { "$ref": "#/definitions/RulePlainConfiguration" }, + { "$ref": "#/definitions/RuleWithNoPlaywrightWaitForNavigationOptions" } + ] + }, + "NoPlaywrightWaitForNavigationOptions": { + "type": "object", + "additionalProperties": false + }, + "NoPlaywrightWaitForSelectorConfiguration": { + "anyOf": [ + { "$ref": "#/definitions/RulePlainConfiguration" }, + { "$ref": "#/definitions/RuleWithNoPlaywrightWaitForSelectorOptions" } + ] + }, + "NoPlaywrightWaitForSelectorOptions": { + "type": "object", + "additionalProperties": false + }, + "NoPlaywrightWaitForTimeoutConfiguration": { + "anyOf": [ + { "$ref": "#/definitions/RulePlainConfiguration" }, + { "$ref": "#/definitions/RuleWithNoPlaywrightWaitForTimeoutOptions" } + ] + }, + "NoPlaywrightWaitForTimeoutOptions": { + "type": "object", + "additionalProperties": false + }, "NoPositiveTabindexConfiguration": { "anyOf": [ { "$ref": "#/definitions/RulePlainConfiguration" }, @@ -5006,6 +5138,13 @@ "description": "A list of rules that belong to this group", "type": "object", "properties": { + "missingPlaywrightAwait": { + "description": "Enforce Playwright async APIs to be awaited or returned.", + "anyOf": [ + { "$ref": "#/definitions/MissingPlaywrightAwaitConfiguration" }, + { "type": "null" } + ] + }, "noDeprecatedImports": { "description": "Restrict imports of deprecated exports.", "anyOf": [ @@ -5071,6 +5210,96 @@ { "type": "null" } ] }, + "noPlaywrightElementHandle": { + "description": "Disallow usage of element handles (page.$() and page.$$()).", + "anyOf": [ + { "$ref": "#/definitions/NoPlaywrightElementHandleConfiguration" }, + { "type": "null" } + ] + }, + "noPlaywrightEval": { + "description": "Disallow usage of page.$eval() and page.$$eval().", + "anyOf": [ + { "$ref": "#/definitions/NoPlaywrightEvalConfiguration" }, + { "type": "null" } + ] + }, + "noPlaywrightFocusedTest": { + "description": "Disallow usage of .only annotation in Playwright tests.", + "anyOf": [ + { "$ref": "#/definitions/NoPlaywrightFocusedTestConfiguration" }, + { "type": "null" } + ] + }, + "noPlaywrightForceOption": { + "description": "Disallow usage of the { force: true } option.", + "anyOf": [ + { "$ref": "#/definitions/NoPlaywrightForceOptionConfiguration" }, + { "type": "null" } + ] + }, + "noPlaywrightNetworkidle": { + "description": "Disallow usage of the networkidle option.", + "anyOf": [ + { "$ref": "#/definitions/NoPlaywrightNetworkidleConfiguration" }, + { "type": "null" } + ] + }, + "noPlaywrightPagePause": { + "description": "Disallow using page.pause().", + "anyOf": [ + { "$ref": "#/definitions/NoPlaywrightPagePauseConfiguration" }, + { "type": "null" } + ] + }, + "noPlaywrightSkippedTest": { + "description": "Disallow usage of .skip annotation in Playwright tests.", + "anyOf": [ + { "$ref": "#/definitions/NoPlaywrightSkippedTestConfiguration" }, + { "type": "null" } + ] + }, + "noPlaywrightUselessAwait": { + "description": "Disallow unnecessary await for Playwright methods that don't return promises.", + "anyOf": [ + { "$ref": "#/definitions/NoPlaywrightUselessAwaitConfiguration" }, + { "type": "null" } + ] + }, + "noPlaywrightValidDescribeCallback": { + "description": "Enforce valid describe() callback.", + "anyOf": [ + { + "$ref": "#/definitions/NoPlaywrightValidDescribeCallbackConfiguration" + }, + { "type": "null" } + ] + }, + "noPlaywrightWaitForNavigation": { + "description": "Disallow using page.waitForNavigation().", + "anyOf": [ + { + "$ref": "#/definitions/NoPlaywrightWaitForNavigationConfiguration" + }, + { "type": "null" } + ] + }, + "noPlaywrightWaitForSelector": { + "description": "Disallow using page.waitForSelector().", + "anyOf": [ + { + "$ref": "#/definitions/NoPlaywrightWaitForSelectorConfiguration" + }, + { "type": "null" } + ] + }, + "noPlaywrightWaitForTimeout": { + "description": "Disallow using page.waitForTimeout().", + "anyOf": [ + { "$ref": "#/definitions/NoPlaywrightWaitForTimeoutConfiguration" }, + { "type": "null" } + ] + }, "noQwikUseVisibleTask": { "description": "Disallow useVisibleTask$() functions in Qwik components.", "anyOf": [ @@ -5870,6 +6099,25 @@ } ] }, + "RuleWithMissingPlaywrightAwaitOptions": { + "type": "object", + "required": ["level"], + "properties": { + "fix": { + "description": "The kind of the code actions emitted by the rule", + "anyOf": [{ "$ref": "#/definitions/FixKind" }, { "type": "null" }] + }, + "level": { + "description": "The severity of the emitted diagnostics by the rule", + "allOf": [{ "$ref": "#/definitions/RulePlainConfiguration" }] + }, + "options": { + "description": "Rule's options", + "allOf": [{ "$ref": "#/definitions/MissingPlaywrightAwaitOptions" }] + } + }, + "additionalProperties": false + }, "RuleWithNoAccessKeyOptions": { "type": "object", "required": ["level"], @@ -8186,6 +8434,200 @@ }, "additionalProperties": false }, + "RuleWithNoPlaywrightElementHandleOptions": { + "type": "object", + "required": ["level"], + "properties": { + "level": { + "description": "The severity of the emitted diagnostics by the rule", + "allOf": [{ "$ref": "#/definitions/RulePlainConfiguration" }] + }, + "options": { + "description": "Rule's options", + "allOf": [ + { "$ref": "#/definitions/NoPlaywrightElementHandleOptions" } + ] + } + }, + "additionalProperties": false + }, + "RuleWithNoPlaywrightEvalOptions": { + "type": "object", + "required": ["level"], + "properties": { + "level": { + "description": "The severity of the emitted diagnostics by the rule", + "allOf": [{ "$ref": "#/definitions/RulePlainConfiguration" }] + }, + "options": { + "description": "Rule's options", + "allOf": [{ "$ref": "#/definitions/NoPlaywrightEvalOptions" }] + } + }, + "additionalProperties": false + }, + "RuleWithNoPlaywrightFocusedTestOptions": { + "type": "object", + "required": ["level"], + "properties": { + "level": { + "description": "The severity of the emitted diagnostics by the rule", + "allOf": [{ "$ref": "#/definitions/RulePlainConfiguration" }] + }, + "options": { + "description": "Rule's options", + "allOf": [{ "$ref": "#/definitions/NoPlaywrightFocusedTestOptions" }] + } + }, + "additionalProperties": false + }, + "RuleWithNoPlaywrightForceOptionOptions": { + "type": "object", + "required": ["level"], + "properties": { + "level": { + "description": "The severity of the emitted diagnostics by the rule", + "allOf": [{ "$ref": "#/definitions/RulePlainConfiguration" }] + }, + "options": { + "description": "Rule's options", + "allOf": [{ "$ref": "#/definitions/NoPlaywrightForceOptionOptions" }] + } + }, + "additionalProperties": false + }, + "RuleWithNoPlaywrightNetworkidleOptions": { + "type": "object", + "required": ["level"], + "properties": { + "level": { + "description": "The severity of the emitted diagnostics by the rule", + "allOf": [{ "$ref": "#/definitions/RulePlainConfiguration" }] + }, + "options": { + "description": "Rule's options", + "allOf": [{ "$ref": "#/definitions/NoPlaywrightNetworkidleOptions" }] + } + }, + "additionalProperties": false + }, + "RuleWithNoPlaywrightPagePauseOptions": { + "type": "object", + "required": ["level"], + "properties": { + "level": { + "description": "The severity of the emitted diagnostics by the rule", + "allOf": [{ "$ref": "#/definitions/RulePlainConfiguration" }] + }, + "options": { + "description": "Rule's options", + "allOf": [{ "$ref": "#/definitions/NoPlaywrightPagePauseOptions" }] + } + }, + "additionalProperties": false + }, + "RuleWithNoPlaywrightSkippedTestOptions": { + "type": "object", + "required": ["level"], + "properties": { + "level": { + "description": "The severity of the emitted diagnostics by the rule", + "allOf": [{ "$ref": "#/definitions/RulePlainConfiguration" }] + }, + "options": { + "description": "Rule's options", + "allOf": [{ "$ref": "#/definitions/NoPlaywrightSkippedTestOptions" }] + } + }, + "additionalProperties": false + }, + "RuleWithNoPlaywrightUselessAwaitOptions": { + "type": "object", + "required": ["level"], + "properties": { + "fix": { + "description": "The kind of the code actions emitted by the rule", + "anyOf": [{ "$ref": "#/definitions/FixKind" }, { "type": "null" }] + }, + "level": { + "description": "The severity of the emitted diagnostics by the rule", + "allOf": [{ "$ref": "#/definitions/RulePlainConfiguration" }] + }, + "options": { + "description": "Rule's options", + "allOf": [{ "$ref": "#/definitions/NoPlaywrightUselessAwaitOptions" }] + } + }, + "additionalProperties": false + }, + "RuleWithNoPlaywrightValidDescribeCallbackOptions": { + "type": "object", + "required": ["level"], + "properties": { + "level": { + "description": "The severity of the emitted diagnostics by the rule", + "allOf": [{ "$ref": "#/definitions/RulePlainConfiguration" }] + }, + "options": { + "description": "Rule's options", + "allOf": [ + { "$ref": "#/definitions/NoPlaywrightValidDescribeCallbackOptions" } + ] + } + }, + "additionalProperties": false + }, + "RuleWithNoPlaywrightWaitForNavigationOptions": { + "type": "object", + "required": ["level"], + "properties": { + "level": { + "description": "The severity of the emitted diagnostics by the rule", + "allOf": [{ "$ref": "#/definitions/RulePlainConfiguration" }] + }, + "options": { + "description": "Rule's options", + "allOf": [ + { "$ref": "#/definitions/NoPlaywrightWaitForNavigationOptions" } + ] + } + }, + "additionalProperties": false + }, + "RuleWithNoPlaywrightWaitForSelectorOptions": { + "type": "object", + "required": ["level"], + "properties": { + "level": { + "description": "The severity of the emitted diagnostics by the rule", + "allOf": [{ "$ref": "#/definitions/RulePlainConfiguration" }] + }, + "options": { + "description": "Rule's options", + "allOf": [ + { "$ref": "#/definitions/NoPlaywrightWaitForSelectorOptions" } + ] + } + }, + "additionalProperties": false + }, + "RuleWithNoPlaywrightWaitForTimeoutOptions": { + "type": "object", + "required": ["level"], + "properties": { + "level": { + "description": "The severity of the emitted diagnostics by the rule", + "allOf": [{ "$ref": "#/definitions/RulePlainConfiguration" }] + }, + "options": { + "description": "Rule's options", + "allOf": [ + { "$ref": "#/definitions/NoPlaywrightWaitForTimeoutOptions" } + ] + } + }, + "additionalProperties": false + }, "RuleWithNoPositiveTabindexOptions": { "type": "object", "required": ["level"], From cef341d1c8f013869826acb3e53d18a95906dfc4 Mon Sep 17 00:00:00 2001 From: Josh Delsman <12201+voxxit@users.noreply.github.com> Date: Sat, 18 Oct 2025 23:02:50 -0500 Subject: [PATCH 13/24] feat(biome_js_analyze): add Playwright lint rules and remove focused test rule This commit introduces a new `Playwright` domain to the linting rules, allowing for better categorization of Playwright-specific rules. The `NoPlaywrightFocusedTest` rule has been removed, as its functionality is covered under the `suspicious` rules. Additionally, the Playwright rules have been updated to include the `Playwright` domain. Ran `just l` and fixed any remaining issues. --- crates/biome_analyze/src/rule.rs | 5 + .../migrate/eslint_any_rule_to_biome.rs | 8 +- .../src/analyzer/linter/rules.rs | 35 +-- crates/biome_js_analyze/src/lint/nursery.rs | 3 +- .../lint/nursery/missing_playwright_await.rs | 251 +++++++++--------- .../nursery/no_playwright_element_handle.rs | 3 +- .../src/lint/nursery/no_playwright_eval.rs | 3 +- .../nursery/no_playwright_focused_test.rs | 130 --------- .../nursery/no_playwright_force_option.rs | 49 ++-- .../lint/nursery/no_playwright_networkidle.rs | 64 ++--- .../lint/nursery/no_playwright_page_pause.rs | 3 +- .../nursery/no_playwright_skipped_test.rs | 32 +-- .../nursery/no_playwright_useless_await.rs | 92 ++++--- .../no_playwright_valid_describe_callback.rs | 21 +- .../no_playwright_wait_for_navigation.rs | 3 +- .../no_playwright_wait_for_selector.rs | 3 +- .../nursery/no_playwright_wait_for_timeout.rs | 3 +- .../src/lint/suspicious/no_focused_tests.rs | 1 + crates/biome_rule_options/src/lib.rs | 1 - .../src/no_playwright_focused_test.rs | 6 - 20 files changed, 272 insertions(+), 444 deletions(-) delete mode 100644 crates/biome_js_analyze/src/lint/nursery/no_playwright_focused_test.rs delete mode 100644 crates/biome_rule_options/src/no_playwright_focused_test.rs diff --git a/crates/biome_analyze/src/rule.rs b/crates/biome_analyze/src/rule.rs index cda426d5d35d..88e1efc1d0f6 100644 --- a/crates/biome_analyze/src/rule.rs +++ b/crates/biome_analyze/src/rule.rs @@ -453,6 +453,8 @@ pub enum RuleDomain { Solid, /// Next.js framework rules Next, + /// Playwright testing library rules + Playwright, /// Qwik framework rules Qwik, /// Vue.js framework rules @@ -471,6 +473,7 @@ impl Display for RuleDomain { Self::Test => fmt.write_str("test"), Self::Solid => fmt.write_str("solid"), Self::Next => fmt.write_str("next"), + Self::Playwright => fmt.write_str("playwright"), Self::Qwik => fmt.write_str("qwik"), Self::Vue => fmt.write_str("vue"), Self::Project => fmt.write_str("project"), @@ -507,6 +510,7 @@ impl RuleDomain { ], Self::Solid => &[&("solid", ">=1.0.0")], Self::Next => &[&("next", ">=14.0.0")], + Self::Playwright => &[&("@playwright/test", ">=1.0.0")], Self::Qwik => &[ &("@builder.io/qwik", ">=1.0.0"), &("@qwik.dev/core", ">=2.0.0"), @@ -535,6 +539,7 @@ impl RuleDomain { ], Self::Solid => &[], Self::Next => &[], + Self::Playwright => &["test", "expect"], Self::Qwik => &[], Self::Vue => &[], Self::Project => &[], diff --git a/crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs b/crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs index 2332d71d402a..a446a299e276 100644 --- a/crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs +++ b/crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs @@ -2130,14 +2130,10 @@ pub(crate) fn migrate_eslint_any_rule( rule.set_level(rule.level().max(rule_severity.into())); } "playwright/no-focused-test" => { - if !options.include_nursery { - results.add(eslint_name, eslint_to_biome::RuleMigrationResult::Nursery); - return false; - } - let group = rules.nursery.get_or_insert_with(Default::default); + let group = rules.suspicious.get_or_insert_with(Default::default); let rule = group .unwrap_group_as_mut() - .no_playwright_focused_test + .no_focused_tests .get_or_insert(Default::default()); rule.set_level(rule.level().max(rule_severity.into())); } diff --git a/crates/biome_configuration/src/analyzer/linter/rules.rs b/crates/biome_configuration/src/analyzer/linter/rules.rs index 7fbd0a410e2b..1034a3f52b28 100644 --- a/crates/biome_configuration/src/analyzer/linter/rules.rs +++ b/crates/biome_configuration/src/analyzer/linter/rules.rs @@ -230,7 +230,6 @@ pub enum RuleName { NoParameterProperties, NoPlaywrightElementHandle, NoPlaywrightEval, - NoPlaywrightFocusedTest, NoPlaywrightForceOption, NoPlaywrightNetworkidle, NoPlaywrightPagePause, @@ -613,7 +612,6 @@ impl RuleName { Self::NoParameterProperties => "noParameterProperties", Self::NoPlaywrightElementHandle => "noPlaywrightElementHandle", Self::NoPlaywrightEval => "noPlaywrightEval", - Self::NoPlaywrightFocusedTest => "noPlaywrightFocusedTest", Self::NoPlaywrightForceOption => "noPlaywrightForceOption", Self::NoPlaywrightNetworkidle => "noPlaywrightNetworkidle", Self::NoPlaywrightPagePause => "noPlaywrightPagePause", @@ -992,7 +990,6 @@ impl RuleName { Self::NoParameterProperties => RuleGroup::Style, Self::NoPlaywrightElementHandle => RuleGroup::Nursery, Self::NoPlaywrightEval => RuleGroup::Nursery, - Self::NoPlaywrightFocusedTest => RuleGroup::Nursery, Self::NoPlaywrightForceOption => RuleGroup::Nursery, Self::NoPlaywrightNetworkidle => RuleGroup::Nursery, Self::NoPlaywrightPagePause => RuleGroup::Nursery, @@ -1380,7 +1377,6 @@ impl std::str::FromStr for RuleName { "noParameterProperties" => Ok(Self::NoParameterProperties), "noPlaywrightElementHandle" => Ok(Self::NoPlaywrightElementHandle), "noPlaywrightEval" => Ok(Self::NoPlaywrightEval), - "noPlaywrightFocusedTest" => Ok(Self::NoPlaywrightFocusedTest), "noPlaywrightForceOption" => Ok(Self::NoPlaywrightForceOption), "noPlaywrightNetworkidle" => Ok(Self::NoPlaywrightNetworkidle), "noPlaywrightPagePause" => Ok(Self::NoPlaywrightPagePause), @@ -4698,7 +4694,7 @@ impl From for Correctness { #[cfg_attr(feature = "schema", derive(JsonSchema))] #[serde(rename_all = "camelCase", default, deny_unknown_fields)] #[doc = r" A list of rules that belong to this group"] -pub struct Nursery { # [doc = r" Enables the recommended rules for this group"] # [serde (skip_serializing_if = "Option::is_none")] pub recommended : Option < bool > , # [doc = "Enforce Playwright async APIs to be awaited or returned."] # [serde (skip_serializing_if = "Option::is_none")] pub missing_playwright_await : Option < RuleFixConfiguration < biome_rule_options :: missing_playwright_await :: MissingPlaywrightAwaitOptions >> , # [doc = "Restrict imports of deprecated exports."] # [serde (skip_serializing_if = "Option::is_none")] pub no_deprecated_imports : Option < RuleConfiguration < biome_rule_options :: no_deprecated_imports :: NoDeprecatedImportsOptions >> , # [doc = "Prevent the listing of duplicate dependencies. The rule supports the following dependency groups: \"bundledDependencies\", \"bundleDependencies\", \"dependencies\", \"devDependencies\", \"overrides\", \"optionalDependencies\", and \"peerDependencies\"."] # [serde (skip_serializing_if = "Option::is_none")] pub no_duplicate_dependencies : Option < RuleConfiguration < biome_rule_options :: no_duplicate_dependencies :: NoDuplicateDependenciesOptions >> , # [doc = "Disallow empty sources."] # [serde (skip_serializing_if = "Option::is_none")] pub no_empty_source : Option < RuleConfiguration < biome_rule_options :: no_empty_source :: NoEmptySourceOptions >> , # [doc = "Require Promise-like statements to be handled appropriately."] # [serde (skip_serializing_if = "Option::is_none")] pub no_floating_promises : Option < RuleFixConfiguration < biome_rule_options :: no_floating_promises :: NoFloatingPromisesOptions >> , # [doc = "Prevent import cycles."] # [serde (skip_serializing_if = "Option::is_none")] pub no_import_cycles : Option < RuleConfiguration < biome_rule_options :: no_import_cycles :: NoImportCyclesOptions >> , # [doc = "Disallow string literals inside JSX elements."] # [serde (skip_serializing_if = "Option::is_none")] pub no_jsx_literals : Option < RuleConfiguration < biome_rule_options :: no_jsx_literals :: NoJsxLiteralsOptions >> , # [doc = "Disallow Promises to be used in places where they are almost certainly a mistake."] # [serde (skip_serializing_if = "Option::is_none")] pub no_misused_promises : Option < RuleFixConfiguration < biome_rule_options :: no_misused_promises :: NoMisusedPromisesOptions >> , # [doc = "Prevent client components from being async functions."] # [serde (skip_serializing_if = "Option::is_none")] pub no_next_async_client_component : Option < RuleConfiguration < biome_rule_options :: no_next_async_client_component :: NoNextAsyncClientComponentOptions >> , # [doc = "Disallow non-null assertions after optional chaining expressions."] # [serde (skip_serializing_if = "Option::is_none")] pub no_non_null_asserted_optional_chain : Option < RuleConfiguration < biome_rule_options :: no_non_null_asserted_optional_chain :: NoNonNullAssertedOptionalChainOptions >> , # [doc = "Disallow usage of element handles (page.$() and page.$$())."] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_element_handle : Option < RuleConfiguration < biome_rule_options :: no_playwright_element_handle :: NoPlaywrightElementHandleOptions >> , # [doc = "Disallow usage of page.$eval() and page.$$eval()."] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_eval : Option < RuleConfiguration < biome_rule_options :: no_playwright_eval :: NoPlaywrightEvalOptions >> , # [doc = "Disallow usage of .only annotation in Playwright tests."] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_focused_test : Option < RuleConfiguration < biome_rule_options :: no_playwright_focused_test :: NoPlaywrightFocusedTestOptions >> , # [doc = "Disallow usage of the { force: true } option."] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_force_option : Option < RuleConfiguration < biome_rule_options :: no_playwright_force_option :: NoPlaywrightForceOptionOptions >> , # [doc = "Disallow usage of the networkidle option."] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_networkidle : Option < RuleConfiguration < biome_rule_options :: no_playwright_networkidle :: NoPlaywrightNetworkidleOptions >> , # [doc = "Disallow using page.pause()."] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_page_pause : Option < RuleConfiguration < biome_rule_options :: no_playwright_page_pause :: NoPlaywrightPagePauseOptions >> , # [doc = "Disallow usage of .skip annotation in Playwright tests."] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_skipped_test : Option < RuleConfiguration < biome_rule_options :: no_playwright_skipped_test :: NoPlaywrightSkippedTestOptions >> , # [doc = "Disallow unnecessary await for Playwright methods that don't return promises."] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_useless_await : Option < RuleFixConfiguration < biome_rule_options :: no_playwright_useless_await :: NoPlaywrightUselessAwaitOptions >> , # [doc = "Enforce valid describe() callback."] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_valid_describe_callback : Option < RuleConfiguration < biome_rule_options :: no_playwright_valid_describe_callback :: NoPlaywrightValidDescribeCallbackOptions >> , # [doc = "Disallow using page.waitForNavigation()."] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_wait_for_navigation : Option < RuleConfiguration < biome_rule_options :: no_playwright_wait_for_navigation :: NoPlaywrightWaitForNavigationOptions >> , # [doc = "Disallow using page.waitForSelector()."] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_wait_for_selector : Option < RuleConfiguration < biome_rule_options :: no_playwright_wait_for_selector :: NoPlaywrightWaitForSelectorOptions >> , # [doc = "Disallow using page.waitForTimeout()."] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_wait_for_timeout : Option < RuleConfiguration < biome_rule_options :: no_playwright_wait_for_timeout :: NoPlaywrightWaitForTimeoutOptions >> , # [doc = "Disallow useVisibleTask$() functions in Qwik components."] # [serde (skip_serializing_if = "Option::is_none")] pub no_qwik_use_visible_task : Option < RuleConfiguration < biome_rule_options :: no_qwik_use_visible_task :: NoQwikUseVisibleTaskOptions >> , # [doc = "Replaces usages of forwardRef with passing ref as a prop."] # [serde (skip_serializing_if = "Option::is_none")] pub no_react_forward_ref : Option < RuleFixConfiguration < biome_rule_options :: no_react_forward_ref :: NoReactForwardRefOptions >> , # [doc = "Disallow usage of sensitive data such as API keys and tokens."] # [serde (skip_serializing_if = "Option::is_none")] pub no_secrets : Option < RuleConfiguration < biome_rule_options :: no_secrets :: NoSecretsOptions >> , # [doc = "Disallow variable declarations from shadowing variables declared in the outer scope."] # [serde (skip_serializing_if = "Option::is_none")] pub no_shadow : Option < RuleConfiguration < biome_rule_options :: no_shadow :: NoShadowOptions >> , # [doc = "Disallow unnecessary type-based conditions that can be statically determined as redundant."] # [serde (skip_serializing_if = "Option::is_none")] pub no_unnecessary_conditions : Option < RuleConfiguration < biome_rule_options :: no_unnecessary_conditions :: NoUnnecessaryConditionsOptions >> , # [doc = "Warn when importing non-existing exports."] # [serde (skip_serializing_if = "Option::is_none")] pub no_unresolved_imports : Option < RuleConfiguration < biome_rule_options :: no_unresolved_imports :: NoUnresolvedImportsOptions >> , # [doc = "Disallow expression statements that are neither a function call nor an assignment."] # [serde (skip_serializing_if = "Option::is_none")] pub no_unused_expressions : Option < RuleConfiguration < biome_rule_options :: no_unused_expressions :: NoUnusedExpressionsOptions >> , # [doc = "Disallow unused catch bindings."] # [serde (skip_serializing_if = "Option::is_none")] pub no_useless_catch_binding : Option < RuleFixConfiguration < biome_rule_options :: no_useless_catch_binding :: NoUselessCatchBindingOptions >> , # [doc = "Disallow the use of useless undefined."] # [serde (skip_serializing_if = "Option::is_none")] pub no_useless_undefined : Option < RuleFixConfiguration < biome_rule_options :: no_useless_undefined :: NoUselessUndefinedOptions >> , # [doc = "Enforce that Vue component data options are declared as functions."] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_data_object_declaration : Option < RuleFixConfiguration < biome_rule_options :: no_vue_data_object_declaration :: NoVueDataObjectDeclarationOptions >> , # [doc = "Disallow duplicate keys in Vue component data, methods, computed properties, and other options."] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_duplicate_keys : Option < RuleConfiguration < biome_rule_options :: no_vue_duplicate_keys :: NoVueDuplicateKeysOptions >> , # [doc = "Disallow reserved keys in Vue component data and computed properties."] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_reserved_keys : Option < RuleConfiguration < biome_rule_options :: no_vue_reserved_keys :: NoVueReservedKeysOptions >> , # [doc = "Disallow reserved names to be used as props."] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_reserved_props : Option < RuleConfiguration < biome_rule_options :: no_vue_reserved_props :: NoVueReservedPropsOptions >> , # [doc = "Enforces href attribute for \\ elements."] # [serde (skip_serializing_if = "Option::is_none")] pub use_anchor_href : Option < RuleConfiguration < biome_rule_options :: use_anchor_href :: UseAnchorHrefOptions >> , # [doc = "Enforce consistent arrow function bodies."] # [serde (skip_serializing_if = "Option::is_none")] pub use_consistent_arrow_return : Option < RuleFixConfiguration < biome_rule_options :: use_consistent_arrow_return :: UseConsistentArrowReturnOptions >> , # [doc = "Enforce type definitions to consistently use either interface or type."] # [serde (skip_serializing_if = "Option::is_none")] pub use_consistent_type_definitions : Option < RuleFixConfiguration < biome_rule_options :: use_consistent_type_definitions :: UseConsistentTypeDefinitionsOptions >> , # [doc = "Require the @deprecated directive to specify a deletion date."] # [serde (skip_serializing_if = "Option::is_none")] pub use_deprecated_date : Option < RuleConfiguration < biome_rule_options :: use_deprecated_date :: UseDeprecatedDateOptions >> , # [doc = "Require switch-case statements to be exhaustive."] # [serde (skip_serializing_if = "Option::is_none")] pub use_exhaustive_switch_cases : Option < RuleFixConfiguration < biome_rule_options :: use_exhaustive_switch_cases :: UseExhaustiveSwitchCasesOptions >> , # [doc = "Enforce types in functions, methods, variables, and parameters."] # [serde (skip_serializing_if = "Option::is_none")] pub use_explicit_type : Option < RuleConfiguration < biome_rule_options :: use_explicit_type :: UseExplicitTypeOptions >> , # [doc = "Enforces that \\ elements have both width and height attributes."] # [serde (skip_serializing_if = "Option::is_none")] pub use_image_size : Option < RuleConfiguration < biome_rule_options :: use_image_size :: UseImageSizeOptions >> , # [doc = "Enforce a maximum number of parameters in function definitions."] # [serde (skip_serializing_if = "Option::is_none")] pub use_max_params : Option < RuleConfiguration < biome_rule_options :: use_max_params :: UseMaxParamsOptions >> , # [doc = "Prefer using the class prop as a classlist over the classnames helper."] # [serde (skip_serializing_if = "Option::is_none")] pub use_qwik_classlist : Option < RuleConfiguration < biome_rule_options :: use_qwik_classlist :: UseQwikClasslistOptions >> , # [doc = "Disallow use* hooks outside of component$ or other use* hooks in Qwik applications."] # [serde (skip_serializing_if = "Option::is_none")] pub use_qwik_method_usage : Option < RuleConfiguration < biome_rule_options :: use_qwik_method_usage :: UseQwikMethodUsageOptions >> , # [doc = "Disallow unserializable expressions in Qwik dollar ($) scopes."] # [serde (skip_serializing_if = "Option::is_none")] pub use_qwik_valid_lexical_scope : Option < RuleConfiguration < biome_rule_options :: use_qwik_valid_lexical_scope :: UseQwikValidLexicalScopeOptions >> , # [doc = "Enforce that components are defined as functions and never as classes."] # [serde (skip_serializing_if = "Option::is_none")] pub use_react_function_components : Option < RuleConfiguration < biome_rule_options :: use_react_function_components :: UseReactFunctionComponentsOptions >> , # [doc = "Enforce the sorting of CSS utility classes."] # [serde (skip_serializing_if = "Option::is_none")] pub use_sorted_classes : Option < RuleFixConfiguration < biome_rule_options :: use_sorted_classes :: UseSortedClassesOptions >> , # [doc = "Enforce multi-word component names in Vue components."] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_multi_word_component_names : Option < RuleConfiguration < biome_rule_options :: use_vue_multi_word_component_names :: UseVueMultiWordComponentNamesOptions >> } +pub struct Nursery { # [doc = r" Enables the recommended rules for this group"] # [serde (skip_serializing_if = "Option::is_none")] pub recommended : Option < bool > , # [doc = "Enforce Playwright async APIs to be awaited or returned."] # [serde (skip_serializing_if = "Option::is_none")] pub missing_playwright_await : Option < RuleFixConfiguration < biome_rule_options :: missing_playwright_await :: MissingPlaywrightAwaitOptions >> , # [doc = "Restrict imports of deprecated exports."] # [serde (skip_serializing_if = "Option::is_none")] pub no_deprecated_imports : Option < RuleConfiguration < biome_rule_options :: no_deprecated_imports :: NoDeprecatedImportsOptions >> , # [doc = "Prevent the listing of duplicate dependencies. The rule supports the following dependency groups: \"bundledDependencies\", \"bundleDependencies\", \"dependencies\", \"devDependencies\", \"overrides\", \"optionalDependencies\", and \"peerDependencies\"."] # [serde (skip_serializing_if = "Option::is_none")] pub no_duplicate_dependencies : Option < RuleConfiguration < biome_rule_options :: no_duplicate_dependencies :: NoDuplicateDependenciesOptions >> , # [doc = "Disallow empty sources."] # [serde (skip_serializing_if = "Option::is_none")] pub no_empty_source : Option < RuleConfiguration < biome_rule_options :: no_empty_source :: NoEmptySourceOptions >> , # [doc = "Require Promise-like statements to be handled appropriately."] # [serde (skip_serializing_if = "Option::is_none")] pub no_floating_promises : Option < RuleFixConfiguration < biome_rule_options :: no_floating_promises :: NoFloatingPromisesOptions >> , # [doc = "Prevent import cycles."] # [serde (skip_serializing_if = "Option::is_none")] pub no_import_cycles : Option < RuleConfiguration < biome_rule_options :: no_import_cycles :: NoImportCyclesOptions >> , # [doc = "Disallow string literals inside JSX elements."] # [serde (skip_serializing_if = "Option::is_none")] pub no_jsx_literals : Option < RuleConfiguration < biome_rule_options :: no_jsx_literals :: NoJsxLiteralsOptions >> , # [doc = "Disallow Promises to be used in places where they are almost certainly a mistake."] # [serde (skip_serializing_if = "Option::is_none")] pub no_misused_promises : Option < RuleFixConfiguration < biome_rule_options :: no_misused_promises :: NoMisusedPromisesOptions >> , # [doc = "Prevent client components from being async functions."] # [serde (skip_serializing_if = "Option::is_none")] pub no_next_async_client_component : Option < RuleConfiguration < biome_rule_options :: no_next_async_client_component :: NoNextAsyncClientComponentOptions >> , # [doc = "Disallow non-null assertions after optional chaining expressions."] # [serde (skip_serializing_if = "Option::is_none")] pub no_non_null_asserted_optional_chain : Option < RuleConfiguration < biome_rule_options :: no_non_null_asserted_optional_chain :: NoNonNullAssertedOptionalChainOptions >> , # [doc = "Disallow usage of element handles (page.$() and page.$$())."] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_element_handle : Option < RuleConfiguration < biome_rule_options :: no_playwright_element_handle :: NoPlaywrightElementHandleOptions >> , # [doc = "Disallow usage of page.$eval() and page.$$eval()."] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_eval : Option < RuleConfiguration < biome_rule_options :: no_playwright_eval :: NoPlaywrightEvalOptions >> , # [doc = "Disallow usage of the { force: true } option."] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_force_option : Option < RuleConfiguration < biome_rule_options :: no_playwright_force_option :: NoPlaywrightForceOptionOptions >> , # [doc = "Disallow usage of the networkidle option."] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_networkidle : Option < RuleConfiguration < biome_rule_options :: no_playwright_networkidle :: NoPlaywrightNetworkidleOptions >> , # [doc = "Disallow using page.pause()."] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_page_pause : Option < RuleConfiguration < biome_rule_options :: no_playwright_page_pause :: NoPlaywrightPagePauseOptions >> , # [doc = "Disallow usage of .skip annotation in Playwright tests."] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_skipped_test : Option < RuleConfiguration < biome_rule_options :: no_playwright_skipped_test :: NoPlaywrightSkippedTestOptions >> , # [doc = "Disallow unnecessary await for Playwright methods that don't return promises."] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_useless_await : Option < RuleFixConfiguration < biome_rule_options :: no_playwright_useless_await :: NoPlaywrightUselessAwaitOptions >> , # [doc = "Enforce valid describe() callback."] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_valid_describe_callback : Option < RuleConfiguration < biome_rule_options :: no_playwright_valid_describe_callback :: NoPlaywrightValidDescribeCallbackOptions >> , # [doc = "Disallow using page.waitForNavigation()."] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_wait_for_navigation : Option < RuleConfiguration < biome_rule_options :: no_playwright_wait_for_navigation :: NoPlaywrightWaitForNavigationOptions >> , # [doc = "Disallow using page.waitForSelector()."] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_wait_for_selector : Option < RuleConfiguration < biome_rule_options :: no_playwright_wait_for_selector :: NoPlaywrightWaitForSelectorOptions >> , # [doc = "Disallow using page.waitForTimeout()."] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_wait_for_timeout : Option < RuleConfiguration < biome_rule_options :: no_playwright_wait_for_timeout :: NoPlaywrightWaitForTimeoutOptions >> , # [doc = "Disallow useVisibleTask$() functions in Qwik components."] # [serde (skip_serializing_if = "Option::is_none")] pub no_qwik_use_visible_task : Option < RuleConfiguration < biome_rule_options :: no_qwik_use_visible_task :: NoQwikUseVisibleTaskOptions >> , # [doc = "Replaces usages of forwardRef with passing ref as a prop."] # [serde (skip_serializing_if = "Option::is_none")] pub no_react_forward_ref : Option < RuleFixConfiguration < biome_rule_options :: no_react_forward_ref :: NoReactForwardRefOptions >> , # [doc = "Disallow usage of sensitive data such as API keys and tokens."] # [serde (skip_serializing_if = "Option::is_none")] pub no_secrets : Option < RuleConfiguration < biome_rule_options :: no_secrets :: NoSecretsOptions >> , # [doc = "Disallow variable declarations from shadowing variables declared in the outer scope."] # [serde (skip_serializing_if = "Option::is_none")] pub no_shadow : Option < RuleConfiguration < biome_rule_options :: no_shadow :: NoShadowOptions >> , # [doc = "Disallow unnecessary type-based conditions that can be statically determined as redundant."] # [serde (skip_serializing_if = "Option::is_none")] pub no_unnecessary_conditions : Option < RuleConfiguration < biome_rule_options :: no_unnecessary_conditions :: NoUnnecessaryConditionsOptions >> , # [doc = "Warn when importing non-existing exports."] # [serde (skip_serializing_if = "Option::is_none")] pub no_unresolved_imports : Option < RuleConfiguration < biome_rule_options :: no_unresolved_imports :: NoUnresolvedImportsOptions >> , # [doc = "Disallow expression statements that are neither a function call nor an assignment."] # [serde (skip_serializing_if = "Option::is_none")] pub no_unused_expressions : Option < RuleConfiguration < biome_rule_options :: no_unused_expressions :: NoUnusedExpressionsOptions >> , # [doc = "Disallow unused catch bindings."] # [serde (skip_serializing_if = "Option::is_none")] pub no_useless_catch_binding : Option < RuleFixConfiguration < biome_rule_options :: no_useless_catch_binding :: NoUselessCatchBindingOptions >> , # [doc = "Disallow the use of useless undefined."] # [serde (skip_serializing_if = "Option::is_none")] pub no_useless_undefined : Option < RuleFixConfiguration < biome_rule_options :: no_useless_undefined :: NoUselessUndefinedOptions >> , # [doc = "Enforce that Vue component data options are declared as functions."] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_data_object_declaration : Option < RuleFixConfiguration < biome_rule_options :: no_vue_data_object_declaration :: NoVueDataObjectDeclarationOptions >> , # [doc = "Disallow duplicate keys in Vue component data, methods, computed properties, and other options."] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_duplicate_keys : Option < RuleConfiguration < biome_rule_options :: no_vue_duplicate_keys :: NoVueDuplicateKeysOptions >> , # [doc = "Disallow reserved keys in Vue component data and computed properties."] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_reserved_keys : Option < RuleConfiguration < biome_rule_options :: no_vue_reserved_keys :: NoVueReservedKeysOptions >> , # [doc = "Disallow reserved names to be used as props."] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_reserved_props : Option < RuleConfiguration < biome_rule_options :: no_vue_reserved_props :: NoVueReservedPropsOptions >> , # [doc = "Enforces href attribute for \\ elements."] # [serde (skip_serializing_if = "Option::is_none")] pub use_anchor_href : Option < RuleConfiguration < biome_rule_options :: use_anchor_href :: UseAnchorHrefOptions >> , # [doc = "Enforce consistent arrow function bodies."] # [serde (skip_serializing_if = "Option::is_none")] pub use_consistent_arrow_return : Option < RuleFixConfiguration < biome_rule_options :: use_consistent_arrow_return :: UseConsistentArrowReturnOptions >> , # [doc = "Enforce type definitions to consistently use either interface or type."] # [serde (skip_serializing_if = "Option::is_none")] pub use_consistent_type_definitions : Option < RuleFixConfiguration < biome_rule_options :: use_consistent_type_definitions :: UseConsistentTypeDefinitionsOptions >> , # [doc = "Require the @deprecated directive to specify a deletion date."] # [serde (skip_serializing_if = "Option::is_none")] pub use_deprecated_date : Option < RuleConfiguration < biome_rule_options :: use_deprecated_date :: UseDeprecatedDateOptions >> , # [doc = "Require switch-case statements to be exhaustive."] # [serde (skip_serializing_if = "Option::is_none")] pub use_exhaustive_switch_cases : Option < RuleFixConfiguration < biome_rule_options :: use_exhaustive_switch_cases :: UseExhaustiveSwitchCasesOptions >> , # [doc = "Enforce types in functions, methods, variables, and parameters."] # [serde (skip_serializing_if = "Option::is_none")] pub use_explicit_type : Option < RuleConfiguration < biome_rule_options :: use_explicit_type :: UseExplicitTypeOptions >> , # [doc = "Enforces that \\ elements have both width and height attributes."] # [serde (skip_serializing_if = "Option::is_none")] pub use_image_size : Option < RuleConfiguration < biome_rule_options :: use_image_size :: UseImageSizeOptions >> , # [doc = "Enforce a maximum number of parameters in function definitions."] # [serde (skip_serializing_if = "Option::is_none")] pub use_max_params : Option < RuleConfiguration < biome_rule_options :: use_max_params :: UseMaxParamsOptions >> , # [doc = "Prefer using the class prop as a classlist over the classnames helper."] # [serde (skip_serializing_if = "Option::is_none")] pub use_qwik_classlist : Option < RuleConfiguration < biome_rule_options :: use_qwik_classlist :: UseQwikClasslistOptions >> , # [doc = "Disallow use* hooks outside of component$ or other use* hooks in Qwik applications."] # [serde (skip_serializing_if = "Option::is_none")] pub use_qwik_method_usage : Option < RuleConfiguration < biome_rule_options :: use_qwik_method_usage :: UseQwikMethodUsageOptions >> , # [doc = "Disallow unserializable expressions in Qwik dollar ($) scopes."] # [serde (skip_serializing_if = "Option::is_none")] pub use_qwik_valid_lexical_scope : Option < RuleConfiguration < biome_rule_options :: use_qwik_valid_lexical_scope :: UseQwikValidLexicalScopeOptions >> , # [doc = "Enforce that components are defined as functions and never as classes."] # [serde (skip_serializing_if = "Option::is_none")] pub use_react_function_components : Option < RuleConfiguration < biome_rule_options :: use_react_function_components :: UseReactFunctionComponentsOptions >> , # [doc = "Enforce the sorting of CSS utility classes."] # [serde (skip_serializing_if = "Option::is_none")] pub use_sorted_classes : Option < RuleFixConfiguration < biome_rule_options :: use_sorted_classes :: UseSortedClassesOptions >> , # [doc = "Enforce multi-word component names in Vue components."] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_multi_word_component_names : Option < RuleConfiguration < biome_rule_options :: use_vue_multi_word_component_names :: UseVueMultiWordComponentNamesOptions >> } impl Nursery { const GROUP_NAME: &'static str = "nursery"; pub(crate) const GROUP_RULES: &'static [&'static str] = &[ @@ -4714,7 +4710,6 @@ impl Nursery { "noNonNullAssertedOptionalChain", "noPlaywrightElementHandle", "noPlaywrightEval", - "noPlaywrightFocusedTest", "noPlaywrightForceOption", "noPlaywrightNetworkidle", "noPlaywrightPagePause", @@ -4803,7 +4798,6 @@ impl Nursery { RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[45]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[46]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[47]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[48]), ]; } impl RuleGroupExt for Nursery { @@ -4875,20 +4869,15 @@ impl RuleGroupExt for Nursery { { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[11])); } - if let Some(rule) = self.no_playwright_focused_test.as_ref() - && rule.is_enabled() - { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[12])); - } if let Some(rule) = self.no_playwright_force_option.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[12])); } if let Some(rule) = self.no_playwright_networkidle.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[14])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13])); } if let Some(rule) = self.no_playwright_page_pause.as_ref() && rule.is_enabled() @@ -5058,7 +5047,7 @@ impl RuleGroupExt for Nursery { if let Some(rule) = self.use_vue_multi_word_component_names.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[48])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[47])); } index_set } @@ -5124,20 +5113,15 @@ impl RuleGroupExt for Nursery { { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[11])); } - if let Some(rule) = self.no_playwright_focused_test.as_ref() - && rule.is_disabled() - { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[12])); - } if let Some(rule) = self.no_playwright_force_option.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[12])); } if let Some(rule) = self.no_playwright_networkidle.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[14])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13])); } if let Some(rule) = self.no_playwright_page_pause.as_ref() && rule.is_disabled() @@ -5307,7 +5291,7 @@ impl RuleGroupExt for Nursery { if let Some(rule) = self.use_vue_multi_word_component_names.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[48])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[47])); } index_set } @@ -5387,10 +5371,6 @@ impl RuleGroupExt for Nursery { .no_playwright_eval .as_ref() .map(|conf| (conf.level(), conf.get_options())), - "noPlaywrightFocusedTest" => self - .no_playwright_focused_test - .as_ref() - .map(|conf| (conf.level(), conf.get_options())), "noPlaywrightForceOption" => self .no_playwright_force_option .as_ref() @@ -5555,7 +5535,6 @@ impl From for Nursery { no_non_null_asserted_optional_chain: Some(value.into()), no_playwright_element_handle: Some(value.into()), no_playwright_eval: Some(value.into()), - no_playwright_focused_test: Some(value.into()), no_playwright_force_option: Some(value.into()), no_playwright_networkidle: Some(value.into()), no_playwright_page_pause: Some(value.into()), diff --git a/crates/biome_js_analyze/src/lint/nursery.rs b/crates/biome_js_analyze/src/lint/nursery.rs index 56ab34e1e59a..38ecf4318174 100644 --- a/crates/biome_js_analyze/src/lint/nursery.rs +++ b/crates/biome_js_analyze/src/lint/nursery.rs @@ -14,7 +14,6 @@ pub mod no_next_async_client_component; pub mod no_non_null_asserted_optional_chain; pub mod no_playwright_element_handle; pub mod no_playwright_eval; -pub mod no_playwright_focused_test; pub mod no_playwright_force_option; pub mod no_playwright_networkidle; pub mod no_playwright_page_pause; @@ -50,4 +49,4 @@ pub mod use_qwik_valid_lexical_scope; pub mod use_react_function_components; pub mod use_sorted_classes; pub mod use_vue_multi_word_component_names; -declare_lint_group! { pub Nursery { name : "nursery" , rules : [self :: missing_playwright_await :: MissingPlaywrightAwait , self :: no_deprecated_imports :: NoDeprecatedImports , self :: no_empty_source :: NoEmptySource , self :: no_floating_promises :: NoFloatingPromises , self :: no_import_cycles :: NoImportCycles , self :: no_jsx_literals :: NoJsxLiterals , self :: no_misused_promises :: NoMisusedPromises , self :: no_next_async_client_component :: NoNextAsyncClientComponent , self :: no_non_null_asserted_optional_chain :: NoNonNullAssertedOptionalChain , self :: no_playwright_element_handle :: NoPlaywrightElementHandle , self :: no_playwright_eval :: NoPlaywrightEval , self :: no_playwright_focused_test :: NoPlaywrightFocusedTest , self :: no_playwright_force_option :: NoPlaywrightForceOption , self :: no_playwright_networkidle :: NoPlaywrightNetworkidle , self :: no_playwright_page_pause :: NoPlaywrightPagePause , self :: no_playwright_skipped_test :: NoPlaywrightSkippedTest , self :: no_playwright_useless_await :: NoPlaywrightUselessAwait , self :: no_playwright_valid_describe_callback :: NoPlaywrightValidDescribeCallback , self :: no_playwright_wait_for_navigation :: NoPlaywrightWaitForNavigation , self :: no_playwright_wait_for_selector :: NoPlaywrightWaitForSelector , self :: no_playwright_wait_for_timeout :: NoPlaywrightWaitForTimeout , self :: no_qwik_use_visible_task :: NoQwikUseVisibleTask , self :: no_react_forward_ref :: NoReactForwardRef , self :: no_secrets :: NoSecrets , self :: no_shadow :: NoShadow , self :: no_unnecessary_conditions :: NoUnnecessaryConditions , self :: no_unresolved_imports :: NoUnresolvedImports , self :: no_unused_expressions :: NoUnusedExpressions , self :: no_useless_catch_binding :: NoUselessCatchBinding , self :: no_useless_undefined :: NoUselessUndefined , self :: no_vue_data_object_declaration :: NoVueDataObjectDeclaration , self :: no_vue_duplicate_keys :: NoVueDuplicateKeys , self :: no_vue_reserved_keys :: NoVueReservedKeys , self :: no_vue_reserved_props :: NoVueReservedProps , self :: use_anchor_href :: UseAnchorHref , self :: use_consistent_arrow_return :: UseConsistentArrowReturn , self :: use_consistent_type_definitions :: UseConsistentTypeDefinitions , self :: use_exhaustive_switch_cases :: UseExhaustiveSwitchCases , self :: use_explicit_type :: UseExplicitType , self :: use_image_size :: UseImageSize , self :: use_max_params :: UseMaxParams , self :: use_qwik_classlist :: UseQwikClasslist , self :: use_qwik_method_usage :: UseQwikMethodUsage , self :: use_qwik_valid_lexical_scope :: UseQwikValidLexicalScope , self :: use_react_function_components :: UseReactFunctionComponents , self :: use_sorted_classes :: UseSortedClasses , self :: use_vue_multi_word_component_names :: UseVueMultiWordComponentNames ,] } } +declare_lint_group! { pub Nursery { name : "nursery" , rules : [self :: missing_playwright_await :: MissingPlaywrightAwait , self :: no_deprecated_imports :: NoDeprecatedImports , self :: no_empty_source :: NoEmptySource , self :: no_floating_promises :: NoFloatingPromises , self :: no_import_cycles :: NoImportCycles , self :: no_jsx_literals :: NoJsxLiterals , self :: no_misused_promises :: NoMisusedPromises , self :: no_next_async_client_component :: NoNextAsyncClientComponent , self :: no_non_null_asserted_optional_chain :: NoNonNullAssertedOptionalChain , self :: no_playwright_element_handle :: NoPlaywrightElementHandle , self :: no_playwright_eval :: NoPlaywrightEval , self :: no_playwright_force_option :: NoPlaywrightForceOption , self :: no_playwright_networkidle :: NoPlaywrightNetworkidle , self :: no_playwright_page_pause :: NoPlaywrightPagePause , self :: no_playwright_skipped_test :: NoPlaywrightSkippedTest , self :: no_playwright_useless_await :: NoPlaywrightUselessAwait , self :: no_playwright_valid_describe_callback :: NoPlaywrightValidDescribeCallback , self :: no_playwright_wait_for_navigation :: NoPlaywrightWaitForNavigation , self :: no_playwright_wait_for_selector :: NoPlaywrightWaitForSelector , self :: no_playwright_wait_for_timeout :: NoPlaywrightWaitForTimeout , self :: no_qwik_use_visible_task :: NoQwikUseVisibleTask , self :: no_react_forward_ref :: NoReactForwardRef , self :: no_secrets :: NoSecrets , self :: no_shadow :: NoShadow , self :: no_unnecessary_conditions :: NoUnnecessaryConditions , self :: no_unresolved_imports :: NoUnresolvedImports , self :: no_unused_expressions :: NoUnusedExpressions , self :: no_useless_catch_binding :: NoUselessCatchBinding , self :: no_useless_undefined :: NoUselessUndefined , self :: no_vue_data_object_declaration :: NoVueDataObjectDeclaration , self :: no_vue_duplicate_keys :: NoVueDuplicateKeys , self :: no_vue_reserved_keys :: NoVueReservedKeys , self :: no_vue_reserved_props :: NoVueReservedProps , self :: use_anchor_href :: UseAnchorHref , self :: use_consistent_arrow_return :: UseConsistentArrowReturn , self :: use_consistent_type_definitions :: UseConsistentTypeDefinitions , self :: use_exhaustive_switch_cases :: UseExhaustiveSwitchCases , self :: use_explicit_type :: UseExplicitType , self :: use_image_size :: UseImageSize , self :: use_max_params :: UseMaxParams , self :: use_qwik_classlist :: UseQwikClasslist , self :: use_qwik_method_usage :: UseQwikMethodUsage , self :: use_qwik_valid_lexical_scope :: UseQwikValidLexicalScope , self :: use_react_function_components :: UseReactFunctionComponents , self :: use_sorted_classes :: UseSortedClasses , self :: use_vue_multi_word_component_names :: UseVueMultiWordComponentNames ,] } } diff --git a/crates/biome_js_analyze/src/lint/nursery/missing_playwright_await.rs b/crates/biome_js_analyze/src/lint/nursery/missing_playwright_await.rs index c97d0909027a..7315b27c4b96 100644 --- a/crates/biome_js_analyze/src/lint/nursery/missing_playwright_await.rs +++ b/crates/biome_js_analyze/src/lint/nursery/missing_playwright_await.rs @@ -1,11 +1,12 @@ use biome_analyze::{ - Ast, FixKind, Rule, RuleDiagnostic, RuleSource, context::RuleContext, declare_lint_rule, + Ast, FixKind, Rule, RuleDiagnostic, RuleDomain, RuleSource, context::RuleContext, + declare_lint_rule, }; use biome_console::markup; use biome_diagnostics::Applicability; use biome_js_factory::make; use biome_js_syntax::{AnyJsExpression, JsArrowFunctionExpression, JsCallExpression, JsModule, T}; -use biome_rowan::{AstNode, BatchMutationExt}; +use biome_rowan::{AstNode, BatchMutationExt, TokenText}; use crate::{JsRuleAction, ast_utils::is_in_async_function}; @@ -59,6 +60,7 @@ declare_lint_rule! { sources: &[RuleSource::EslintPlaywright("missing-playwright-await").same()], recommended: false, fix_kind: FixKind::Unsafe, + domains: &[RuleDomain::Playwright], } } @@ -96,7 +98,7 @@ const ASYNC_PLAYWRIGHT_MATCHERS: &[&str] = &[ ]; pub enum MissingAwaitType { - ExpectMatcher(String), + ExpectMatcher(TokenText), ExpectPoll, TestStep, } @@ -119,10 +121,10 @@ impl Rule for MissingPlaywrightAwait { } // Check for expect calls with async matchers - if let Some(matcher_name) = get_async_expect_matcher(call_expr) { - if !is_properly_handled(call_expr) { - return Some(matcher_name); - } + if let Some(matcher_name) = get_async_expect_matcher(call_expr) + && !is_properly_handled(call_expr) + { + return Some(matcher_name); } None @@ -131,34 +133,47 @@ impl Rule for MissingPlaywrightAwait { fn diagnostic(ctx: &RuleContext, state: &Self::State) -> Option { let node = ctx.query(); - let (message, note) = match state { - MissingAwaitType::ExpectMatcher(matcher) => ( - markup! { - "Async matcher "{{matcher}}" must be awaited or returned." - }, - markup! { - "Add ""await"" before the expect call or return the promise." - }, - ), - MissingAwaitType::ExpectPoll => ( - markup! { - "expect.poll"" must be awaited or returned." - }, - markup! { + match state { + MissingAwaitType::ExpectMatcher(matcher) => { + let matcher_text = matcher.text(); + Some( + RuleDiagnostic::new( + rule_category!(), + node.range(), + markup! { + "Async matcher "{matcher_text}" must be awaited or returned." + }, + ) + .note(markup! { + "Add ""await"" before the expect call or return the promise." + }), + ) + } + MissingAwaitType::ExpectPoll => Some( + RuleDiagnostic::new( + rule_category!(), + node.range(), + markup! { + "expect.poll"" must be awaited or returned." + }, + ) + .note(markup! { "Add ""await"" before the expect call or return the promise." - }, + }), ), - MissingAwaitType::TestStep => ( - markup! { - "test.step"" must be awaited or returned." - }, - markup! { + MissingAwaitType::TestStep => Some( + RuleDiagnostic::new( + rule_category!(), + node.range(), + markup! { + "test.step"" must be awaited or returned." + }, + ) + .note(markup! { "Add ""await"" before the test.step call or return the promise." - }, + }), ), - }; - - Some(RuleDiagnostic::new(rule_category!(), node.range(), message).note(note)) + } } fn action(ctx: &RuleContext, _: &Self::State) -> Option { @@ -189,32 +204,40 @@ impl Rule for MissingPlaywrightAwait { } } -fn is_test_step_call(call_expr: &JsCallExpression) -> bool { - let callee = call_expr.callee().ok(); - - // Check for test.step pattern - if let Some(AnyJsExpression::JsStaticMemberExpression(member)) = callee { - if let Ok(member_name) = member.member() { - if let Some(name) = member_name.as_js_name() { - if let Ok(token) = name.value_token() { - if token.text_trimmed() == "step" { - // Check if object is "test" - if let Ok(object) = member.object() { - if let AnyJsExpression::JsIdentifierExpression(id) = object { - if let Ok(name) = id.name() { - if let Ok(token) = name.value_token() { - return token.text_trimmed() == "test"; - } - } - } - } - } - } - } +/// Generic helper to check if a call expression matches a pattern like `object.member()` +/// where both the object identifier and member name match expected values. +fn is_member_call_pattern( + call_expr: &JsCallExpression, + object_name: &str, + member_name: &str, +) -> bool { + // Helper closure using ? operator for cleaner control flow + let check = || -> Option { + let callee = call_expr.callee().ok()?; + let member_expr = callee.as_js_static_member_expression()?; + + // Check member name + let member_node = member_expr.member().ok()?; + let name = member_node.as_js_name()?; + let token = name.value_token().ok()?; + if token.text_trimmed() != member_name { + return Some(false); } - } - false + // Check object name + let object = member_expr.object().ok()?; + let id = object.as_js_identifier_expression()?; + let obj_name = id.name().ok()?; + let obj_token = obj_name.value_token().ok()?; + + Some(obj_token.text_trimmed() == object_name) + }; + + check().unwrap_or(false) +} + +fn is_test_step_call(call_expr: &JsCallExpression) -> bool { + is_member_call_pattern(call_expr, "test", "step") } fn get_async_expect_matcher(call_expr: &JsCallExpression) -> Option { @@ -227,10 +250,10 @@ fn get_async_expect_matcher(call_expr: &JsCallExpression) -> Option Option bool { match expr { AnyJsExpression::JsStaticMemberExpression(member) => { - if let Ok(member_name) = member.member() { - if let Some(name) = member_name.as_js_name() { - if let Ok(token) = name.value_token() { - if token.text_trimmed() == "poll" { - return true; - } - } - } + if let Ok(member_name) = member.member() + && let Some(name) = member_name.as_js_name() + && let Ok(token) = name.value_token() + && token.text_trimmed() == "poll" + { + return true; } if let Ok(object) = member.object() { return has_poll_in_chain(&object); @@ -284,23 +305,21 @@ fn has_expect_in_chain(expr: &AnyJsExpression) -> bool { if let Ok(callee) = call.callee() { match callee { AnyJsExpression::JsIdentifierExpression(id) => { - if let Ok(name) = id.name() { - if let Ok(token) = name.value_token() { - return token.text_trimmed() == "expect"; - } + if let Ok(name) = id.name() + && let Ok(token) = name.value_token() + { + return token.text_trimmed() == "expect"; } false } AnyJsExpression::JsStaticMemberExpression(member) => { // Could be expect.soft or similar - if let Ok(object) = member.object() { - if let AnyJsExpression::JsIdentifierExpression(id) = object { - if let Ok(name) = id.name() { - if let Ok(token) = name.value_token() { - return token.text_trimmed() == "expect"; - } - } - } + if let Ok(object) = member.object() + && let AnyJsExpression::JsIdentifierExpression(id) = object + && let Ok(name) = id.name() + && let Ok(token) = name.value_token() + { + return token.text_trimmed() == "expect"; } false } @@ -326,10 +345,10 @@ fn is_call_awaited_or_returned(call_expr: &JsCallExpression) -> bool { let parent = call_expr.syntax().parent(); // Check if it's awaited - if let Some(parent) = &parent { - if parent.kind() == biome_js_syntax::JsSyntaxKind::JS_AWAIT_EXPRESSION { - return true; - } + if let Some(parent) = &parent + && parent.kind() == biome_js_syntax::JsSyntaxKind::JS_AWAIT_EXPRESSION + { + return true; } // Check if it's in a return statement @@ -341,17 +360,16 @@ fn is_call_awaited_or_returned(call_expr: &JsCallExpression) -> bool { } biome_js_syntax::JsSyntaxKind::JS_ARROW_FUNCTION_EXPRESSION => { // If it's an arrow function with expression body, check if our call is exactly the body - if let Some(arrow) = JsArrowFunctionExpression::cast_ref(&node) { - if let Ok(body) = arrow.body() { - if let Some(body_expr) = body.as_any_js_expression() { - // Only return true if the call expression is exactly the arrow body - // (not just nested somewhere inside it) - if call_expr.syntax().text_trimmed_range() - == body_expr.syntax().text_trimmed_range() - { - return true; - } - } + if let Some(arrow) = JsArrowFunctionExpression::cast_ref(&node) + && let Ok(body) = arrow.body() + && let Some(body_expr) = body.as_any_js_expression() + { + // Only return true if the call expression is exactly the arrow body + // (not just nested somewhere inside it) + if call_expr.syntax().text_trimmed_range() + == body_expr.syntax().text_trimmed_range() + { + return true; } } break; @@ -389,22 +407,15 @@ fn find_enclosing_promise_all(call_expr: &JsCallExpression) -> Option Option bool { - if let Ok(callee) = call.callee() { - if let Some(member) = callee.as_js_static_member_expression() { - if let Ok(member_name) = member.member() { - if let Some(name) = member_name.as_js_name() { - if let Ok(token) = name.value_token() { - if token.text_trimmed() == "all" { - // Check if object is Promise - if let Ok(object) = member.object() { - if let AnyJsExpression::JsIdentifierExpression(id) = object { - if let Ok(name) = id.name() { - if let Ok(token) = name.value_token() { - return token.text_trimmed() == "Promise"; - } - } - } - } - } - } - } - } - } - } - false + is_member_call_pattern(call, "Promise", "all") } /// Checks if a node is within an async context (async function or module with TLA support). diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_element_handle.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_element_handle.rs index 70771c170de4..9ff9881a4d01 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_playwright_element_handle.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_playwright_element_handle.rs @@ -1,5 +1,5 @@ use biome_analyze::{ - Ast, Rule, RuleDiagnostic, RuleSource, context::RuleContext, declare_lint_rule, + Ast, Rule, RuleDiagnostic, RuleDomain, RuleSource, context::RuleContext, declare_lint_rule, }; use biome_console::markup; use biome_js_syntax::{JsCallExpression, JsStaticMemberExpression}; @@ -50,6 +50,7 @@ declare_lint_rule! { language: "js", sources: &[RuleSource::EslintPlaywright("no-element-handle").same()], recommended: false, + domains: &[RuleDomain::Playwright], } } diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_eval.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_eval.rs index 1fd0b01b9daa..3e81cf65f9f9 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_playwright_eval.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_playwright_eval.rs @@ -1,5 +1,5 @@ use biome_analyze::{ - Ast, Rule, RuleDiagnostic, RuleSource, context::RuleContext, declare_lint_rule, + Ast, Rule, RuleDiagnostic, RuleDomain, RuleSource, context::RuleContext, declare_lint_rule, }; use biome_console::markup; use biome_js_syntax::{JsCallExpression, JsStaticMemberExpression}; @@ -39,6 +39,7 @@ declare_lint_rule! { language: "js", sources: &[RuleSource::EslintPlaywright("no-eval").same()], recommended: false, + domains: &[RuleDomain::Playwright], } } diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_focused_test.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_focused_test.rs deleted file mode 100644 index 56ba51ffc7fb..000000000000 --- a/crates/biome_js_analyze/src/lint/nursery/no_playwright_focused_test.rs +++ /dev/null @@ -1,130 +0,0 @@ -use biome_analyze::{ - Ast, Rule, RuleDiagnostic, RuleSource, context::RuleContext, declare_lint_rule, -}; -use biome_console::markup; -use biome_js_syntax::{JsCallExpression, JsStaticMemberExpression}; -use biome_rowan::AstNode; - -declare_lint_rule! { - /// Disallow usage of `.only` annotation in Playwright tests. - /// - /// Focused tests using `.only` should not be committed to version control. - /// They cause the test runner to skip all other tests, which can lead to - /// incomplete test runs in CI/CD pipelines. - /// - /// ## Examples - /// - /// ### Invalid - /// - /// ```js,expect_diagnostic - /// test.only('focus this test', async ({ page }) => {}); - /// ``` - /// - /// ```js,expect_diagnostic - /// test.describe.only('focus suite', () => { - /// test('one', async ({ page }) => {}); - /// }); - /// ``` - /// - /// ### Valid - /// - /// ```js - /// test('this test', async ({ page }) => {}); - /// ``` - /// - /// ```js - /// test.describe('test suite', () => { - /// test('one', async ({ page }) => {}); - /// }); - /// ``` - /// - pub NoPlaywrightFocusedTest { - version: "next", - name: "noPlaywrightFocusedTest", - language: "js", - sources: &[RuleSource::EslintPlaywright("no-focused-test").same()], - recommended: false, - } -} - -impl Rule for NoPlaywrightFocusedTest { - type Query = Ast; - type State = (); - type Signals = Option; - type Options = (); - - fn run(ctx: &RuleContext) -> Self::Signals { - let call_expr = ctx.query(); - let callee = call_expr.callee().ok()?; - - // Check if this is a member expression like test.only() or describe.only() - let member_expr = JsStaticMemberExpression::cast_ref(callee.syntax())?; - - // Check if the member being accessed is "only" - let member_name = member_expr.member().ok()?; - let member_text = member_name.as_js_name()?.value_token().ok()?; - - if member_text.text_trimmed() != "only" { - return None; - } - - // Check if the object is test/describe or a chain like test.describe - let object = member_expr.object().ok()?; - - fn is_test_or_describe_object(expr: &biome_js_syntax::AnyJsExpression) -> bool { - match expr { - biome_js_syntax::AnyJsExpression::JsIdentifierExpression(id) => { - if let Ok(name) = id.name() { - if let Ok(token) = name.value_token() { - let text = token.text_trimmed(); - return text == "test" || text == "describe"; - } - } - false - } - biome_js_syntax::AnyJsExpression::JsStaticMemberExpression(member) => { - // Handle test.describe.only or similar chains - if let Ok(member_name) = member.member() { - if let Some(name) = member_name.as_js_name() { - if let Ok(token) = name.value_token() { - let text = token.text_trimmed(); - if text == "describe" || text == "serial" || text == "parallel" { - if let Ok(obj) = member.object() { - return is_test_or_describe_object(&obj); - } - } - } - } - } - false - } - _ => false, - } - } - - if is_test_or_describe_object(&object) { - Some(()) - } else { - None - } - } - - fn diagnostic(ctx: &RuleContext, _: &Self::State) -> Option { - let node = ctx.query(); - Some( - RuleDiagnostic::new( - rule_category!(), - node.range(), - markup! { - "Unexpected focused test using "".only"" annotation." - }, - ) - .note(markup! { - "Focused tests should not be committed to version control as they prevent other tests from running." - }) - .note(markup! { - "Remove the "".only"" annotation to run all tests." - }), - ) - } -} diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_force_option.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_force_option.rs index 637456e107f8..fc8825aaf425 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_playwright_force_option.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_playwright_force_option.rs @@ -1,9 +1,9 @@ use biome_analyze::{ - Ast, Rule, RuleDiagnostic, RuleSource, context::RuleContext, declare_lint_rule, + Ast, Rule, RuleDiagnostic, RuleDomain, RuleSource, context::RuleContext, declare_lint_rule, }; use biome_console::markup; use biome_js_syntax::{AnyJsExpression, JsCallExpression, JsObjectExpression}; -use biome_rowan::{AstNode, AstNodeList}; +use biome_rowan::AstNode; declare_lint_rule! { /// Disallow usage of the `{ force: true }` option. @@ -47,6 +47,7 @@ declare_lint_rule! { language: "js", sources: &[RuleSource::EslintPlaywright("no-force-option").same()], recommended: false, + domains: &[RuleDomain::Playwright], } } @@ -89,12 +90,11 @@ impl Rule for NoPlaywrightForceOption { let args = call_expr.arguments().ok()?; for arg in args.args().into_iter().flatten() { - if let Some(expr) = arg.as_any_js_expression() { - if let AnyJsExpression::JsObjectExpression(obj_expr) = expr { - if has_force_true(&obj_expr) { - return Some(()); - } - } + if let Some(expr) = arg.as_any_js_expression() + && let AnyJsExpression::JsObjectExpression(obj_expr) = expr + && has_force_true(obj_expr) + { + return Some(()); } } @@ -125,26 +125,19 @@ fn has_force_true(obj_expr: &JsObjectExpression) -> bool { for member in obj_expr.members().into_iter().flatten() { if let Some(prop) = member.as_js_property_object_member() { // Check if property name is 'force' - if let Ok(name) = prop.name() { - if let Some(name_node) = name.as_js_literal_member_name() { - if let Ok(name_token) = name_node.value() { - if name_token.text_trimmed() == "force" { - // Check if value is true - if let Ok(value) = prop.value() { - if let Some(literal) = value.as_any_js_literal_expression() { - if let Some(bool_lit) = - literal.as_js_boolean_literal_expression() - { - if let Ok(value_token) = bool_lit.value_token() { - if value_token.text_trimmed() == "true" { - return true; - } - } - } - } - } - } - } + if let Ok(name) = prop.name() + && let Some(name_node) = name.as_js_literal_member_name() + && let Ok(name_token) = name_node.value() + && name_token.text_trimmed() == "force" + { + // Check if value is true + if let Ok(value) = prop.value() + && let Some(literal) = value.as_any_js_literal_expression() + && let Some(bool_lit) = literal.as_js_boolean_literal_expression() + && let Ok(value_token) = bool_lit.value_token() + && value_token.text_trimmed() == "true" + { + return true; } } } diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_networkidle.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_networkidle.rs index 255f5a5c7538..27d9eba9e463 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_playwright_networkidle.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_playwright_networkidle.rs @@ -1,11 +1,11 @@ use biome_analyze::{ - Ast, Rule, RuleDiagnostic, RuleSource, context::RuleContext, declare_lint_rule, + Ast, Rule, RuleDiagnostic, RuleDomain, RuleSource, context::RuleContext, declare_lint_rule, }; use biome_console::markup; use biome_js_syntax::{ AnyJsExpression, JsCallExpression, JsObjectExpression, JsStaticMemberExpression, }; -use biome_rowan::{AstNode, AstNodeList}; +use biome_rowan::AstNode; declare_lint_rule! { /// Disallow usage of the `networkidle` option. @@ -42,6 +42,7 @@ declare_lint_rule! { language: "js", sources: &[RuleSource::EslintPlaywright("no-networkidle").same()], recommended: false, + domains: &[RuleDomain::Playwright], } } @@ -73,14 +74,13 @@ impl Rule for NoPlaywrightNetworkidle { return None; }; - if let Some(expr) = first_arg.as_any_js_expression() { - if let Some(literal) = expr.as_any_js_literal_expression() { - if let Some(string_lit) = literal.as_js_string_literal_expression() { - let value = string_lit.inner_string_text().ok()?; - if value.text() == "networkidle" { - return Some(()); - } - } + if let Some(expr) = first_arg.as_any_js_expression() + && let Some(literal) = expr.as_any_js_literal_expression() + && let Some(string_lit) = literal.as_js_string_literal_expression() + { + let value = string_lit.inner_string_text().ok()?; + if value.text() == "networkidle" { + return Some(()); } } return None; @@ -92,12 +92,11 @@ impl Rule for NoPlaywrightNetworkidle { // Navigation methods typically have options as the second argument for arg in args.args().into_iter().flatten() { - if let Some(expr) = arg.as_any_js_expression() { - if let AnyJsExpression::JsObjectExpression(obj_expr) = expr { - if has_networkidle_option(&obj_expr) { - return Some(()); - } - } + if let Some(expr) = arg.as_any_js_expression() + && let AnyJsExpression::JsObjectExpression(obj_expr) = expr + && has_networkidle_option(obj_expr) + { + return Some(()); } } } @@ -129,26 +128,19 @@ fn has_networkidle_option(obj_expr: &JsObjectExpression) -> bool { for member in obj_expr.members().into_iter().flatten() { if let Some(prop) = member.as_js_property_object_member() { // Check if property name is 'waitUntil' - if let Ok(name) = prop.name() { - if let Some(name_node) = name.as_js_literal_member_name() { - if let Ok(name_token) = name_node.value() { - if name_token.text_trimmed() == "waitUntil" { - // Check if value is 'networkidle' - if let Ok(value) = prop.value() { - if let Some(literal_expr) = value.as_any_js_literal_expression() { - if let Some(string_lit) = - literal_expr.as_js_string_literal_expression() - { - if let Ok(inner) = string_lit.inner_string_text() { - if inner.text() == "networkidle" { - return true; - } - } - } - } - } - } - } + if let Ok(name) = prop.name() + && let Some(name_node) = name.as_js_literal_member_name() + && let Ok(name_token) = name_node.value() + && name_token.text_trimmed() == "waitUntil" + { + // Check if value is 'networkidle' + if let Ok(value) = prop.value() + && let Some(literal_expr) = value.as_any_js_literal_expression() + && let Some(string_lit) = literal_expr.as_js_string_literal_expression() + && let Ok(inner) = string_lit.inner_string_text() + && inner.text() == "networkidle" + { + return true; } } } diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_page_pause.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_page_pause.rs index 4d4661cab2a1..1246b54ba019 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_playwright_page_pause.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_playwright_page_pause.rs @@ -1,5 +1,5 @@ use biome_analyze::{ - Ast, Rule, RuleDiagnostic, RuleSource, context::RuleContext, declare_lint_rule, + Ast, Rule, RuleDiagnostic, RuleDomain, RuleSource, context::RuleContext, declare_lint_rule, }; use biome_console::markup; use biome_js_syntax::{JsCallExpression, JsStaticMemberExpression}; @@ -42,6 +42,7 @@ declare_lint_rule! { language: "js", sources: &[RuleSource::EslintPlaywright("no-page-pause").same()], recommended: false, + domains: &[RuleDomain::Playwright], } } diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_skipped_test.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_skipped_test.rs index 19ab1cd1cd17..963c8653eafd 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_playwright_skipped_test.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_playwright_skipped_test.rs @@ -1,5 +1,5 @@ use biome_analyze::{ - Ast, Rule, RuleDiagnostic, RuleSource, context::RuleContext, declare_lint_rule, + Ast, Rule, RuleDiagnostic, RuleDomain, RuleSource, context::RuleContext, declare_lint_rule, }; use biome_console::markup; use biome_js_syntax::{JsCallExpression, JsStaticMemberExpression}; @@ -43,6 +43,7 @@ declare_lint_rule! { language: "js", sources: &[RuleSource::EslintPlaywright("no-skipped-test").same()], recommended: false, + domains: &[RuleDomain::Playwright], } } @@ -73,26 +74,25 @@ impl Rule for NoPlaywrightSkippedTest { fn is_test_or_describe_object(expr: &biome_js_syntax::AnyJsExpression) -> bool { match expr { biome_js_syntax::AnyJsExpression::JsIdentifierExpression(id) => { - if let Ok(name) = id.name() { - if let Ok(token) = name.value_token() { - let text = token.text_trimmed(); - return text == "test" || text == "describe"; - } + if let Ok(name) = id.name() + && let Ok(token) = name.value_token() + { + let text = token.text_trimmed(); + return text == "test" || text == "describe"; } false } biome_js_syntax::AnyJsExpression::JsStaticMemberExpression(member) => { // Handle test.describe.skip or similar chains - if let Ok(member_name) = member.member() { - if let Some(name) = member_name.as_js_name() { - if let Ok(token) = name.value_token() { - let text = token.text_trimmed(); - if text == "describe" || text == "serial" || text == "parallel" { - if let Ok(obj) = member.object() { - return is_test_or_describe_object(&obj); - } - } - } + if let Ok(member_name) = member.member() + && let Some(name) = member_name.as_js_name() + && let Ok(token) = name.value_token() + { + let text = token.text_trimmed(); + if (text == "describe" || text == "serial" || text == "parallel") + && let Ok(obj) = member.object() + { + return is_test_or_describe_object(&obj); } } false diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_useless_await.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_useless_await.rs index 29d5be4ecfec..fa32cb103a59 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_playwright_useless_await.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_playwright_useless_await.rs @@ -1,12 +1,13 @@ use biome_analyze::{ - Ast, FixKind, Rule, RuleDiagnostic, RuleSource, context::RuleContext, declare_lint_rule, + Ast, FixKind, Rule, RuleDiagnostic, RuleDomain, RuleSource, context::RuleContext, + declare_lint_rule, }; use biome_console::markup; use biome_diagnostics::Applicability; use biome_js_syntax::{ AnyJsExpression, JsAwaitExpression, JsCallExpression, JsStaticMemberExpression, }; -use biome_rowan::{AstNode, BatchMutationExt}; +use biome_rowan::{AstNode, BatchMutationExt, TokenText}; use crate::JsRuleAction; @@ -57,6 +58,7 @@ declare_lint_rule! { sources: &[RuleSource::EslintPlaywright("no-useless-await").same()], recommended: false, fix_kind: FixKind::Safe, + domains: &[RuleDomain::Playwright], } } @@ -161,7 +163,7 @@ impl Rule for NoPlaywrightUselessAwait { } // Check for expect calls with sync matchers - if is_sync_expect_call(&call_expr) { + if is_sync_expect_call(call_expr) { return Some(()); } @@ -210,28 +212,27 @@ impl Rule for NoPlaywrightUselessAwait { fn is_page_or_frame(expr: &AnyJsExpression) -> bool { match expr { AnyJsExpression::JsIdentifierExpression(id) => { - if let Ok(name) = id.name() { - if let Ok(token) = name.value_token() { - let text = token.text_trimmed(); - return text == "page" - || text == "frame" - || text.ends_with("Page") - || text.ends_with("Frame"); - } + if let Ok(name) = id.name() + && let Ok(token) = name.value_token() + { + let text = token.text_trimmed(); + return text == "page" + || text == "frame" + || text.ends_with("Page") + || text.ends_with("Frame"); } false } AnyJsExpression::JsStaticMemberExpression(member) => { - if let Ok(member_name) = member.member() { - if let Some(name) = member_name.as_js_name() { - if let Ok(token) = name.value_token() { - let text = token.text_trimmed(); - return text == "page" - || text == "frame" - || text.ends_with("Page") - || text.ends_with("Frame"); - } - } + if let Ok(member_name) = member.member() + && let Some(name) = member_name.as_js_name() + && let Ok(token) = name.value_token() + { + let text = token.text_trimmed(); + return text == "page" + || text == "frame" + || text.ends_with("Page") + || text.ends_with("Frame"); } false } @@ -262,9 +263,9 @@ fn is_sync_expect_call(call_expr: &JsCallExpression) -> bool { Some(t) => t, None => return false, }; - let matcher_name = token.text_trimmed().to_string(); + let matcher_name: TokenText = token.token_text_trimmed(); - if !SYNC_EXPECT_MATCHERS.contains(&matcher_name.as_str()) { + if !SYNC_EXPECT_MATCHERS.contains(&matcher_name.text()) { return false; } @@ -276,26 +277,24 @@ fn is_sync_expect_call(call_expr: &JsCallExpression) -> bool { // Check if it's expect (not expect.poll or expect with resolves/rejects) match expect_callee { Some(AnyJsExpression::JsIdentifierExpression(id)) => { - if let Ok(name) = id.name() { - if let Ok(token) = name.value_token() { - if token.text_trimmed() == "expect" { - // Make sure there's no "poll", "resolves", or "rejects" in the chain - return !has_async_modifier(&expect_call, call_expr); - } - } + if let Ok(name) = id.name() + && let Ok(token) = name.value_token() + && token.text_trimmed() == "expect" + { + // Make sure there's no "poll", "resolves", or "rejects" in the chain + return !has_async_modifier(&expect_call, call_expr); } } Some(AnyJsExpression::JsStaticMemberExpression(expect_member)) => { // Check for expect.soft, but not expect.poll - if let Ok(member) = expect_member.member() { - if let Some(name) = member.as_js_name() { - if let Ok(token) = name.value_token() { - let member_text = token.text_trimmed(); - // soft is OK, poll makes it async - if member_text == "soft" { - return !has_async_modifier(&expect_call, call_expr); - } - } + if let Ok(member) = expect_member.member() + && let Some(name) = member.as_js_name() + && let Ok(token) = name.value_token() + { + let member_text = token.text_trimmed(); + // soft is OK, poll makes it async + if member_text == "soft" { + return !has_async_modifier(&expect_call, call_expr); } } } @@ -313,14 +312,13 @@ fn has_async_modifier(expect_call: &JsCallExpression, final_call: &JsCallExpress while current != *expect_syntax { if let Some(member) = JsStaticMemberExpression::cast_ref(¤t) { - if let Ok(member_name) = member.member() { - if let Some(name) = member_name.as_js_name() { - if let Ok(token) = name.value_token() { - let text = token.text_trimmed(); - if text == "poll" || text == "resolves" || text == "rejects" { - return true; - } - } + if let Ok(member_name) = member.member() + && let Some(name) = member_name.as_js_name() + && let Ok(token) = name.value_token() + { + let text = token.text_trimmed(); + if text == "poll" || text == "resolves" || text == "rejects" { + return true; } } if let Some(parent) = member.syntax().parent() { diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_valid_describe_callback.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_valid_describe_callback.rs index 8eee2b6ae91c..9afa701560c3 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_playwright_valid_describe_callback.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_playwright_valid_describe_callback.rs @@ -1,5 +1,5 @@ use biome_analyze::{ - Ast, Rule, RuleDiagnostic, RuleSource, context::RuleContext, declare_lint_rule, + Ast, Rule, RuleDiagnostic, RuleDomain, RuleSource, context::RuleContext, declare_lint_rule, }; use biome_console::markup; use biome_js_syntax::{AnyJsExpression, JsCallExpression}; @@ -48,6 +48,7 @@ declare_lint_rule! { language: "js", sources: &[RuleSource::EslintPlaywright("valid-describe-callback").same()], recommended: false, + domains: &[RuleDomain::Playwright], } } @@ -133,10 +134,10 @@ impl Rule for NoPlaywrightValidDescribeCallback { } // Check if has parameters - if let Ok(params) = func.parameters() { - if params.items().len() > 0 { - return Some(InvalidReason::HasParameters); - } + if let Ok(params) = func.parameters() + && !params.items().is_empty() + { + return Some(InvalidReason::HasParameters); } } _ => return None, // Not a function, but we won't report this @@ -148,17 +149,23 @@ impl Rule for NoPlaywrightValidDescribeCallback { fn diagnostic(ctx: &RuleContext, state: &Self::State) -> Option { let node = ctx.query(); - let (message, note) = match state { + let (message, note, explanation) = match state { InvalidReason::Async => ( markup! { "Describe callback should not be ""async""." }, + markup! { "Describe blocks are meant to organize tests, not contain asynchronous logic. Async operations should be placed within individual test callbacks." }, markup! { "Remove the ""async"" keyword from the describe callback." }, ), InvalidReason::HasParameters => ( markup! { "Describe callback should not have parameters." }, + markup! { "Describe callbacks are invoked without arguments by the test framework." }, markup! { "Remove parameters from the describe callback." }, ), }; - Some(RuleDiagnostic::new(rule_category!(), node.range(), message).note(note)) + Some( + RuleDiagnostic::new(rule_category!(), node.range(), message) + .note(note) + .note(explanation), + ) } } diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_navigation.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_navigation.rs index 8c4a19210ec2..f1eac04d71d8 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_navigation.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_navigation.rs @@ -1,5 +1,5 @@ use biome_analyze::{ - Ast, Rule, RuleDiagnostic, RuleSource, context::RuleContext, declare_lint_rule, + Ast, Rule, RuleDiagnostic, RuleDomain, RuleSource, context::RuleContext, declare_lint_rule, }; use biome_console::markup; use biome_js_syntax::{JsCallExpression, JsStaticMemberExpression}; @@ -44,6 +44,7 @@ declare_lint_rule! { language: "js", sources: &[RuleSource::EslintPlaywright("no-wait-for-navigation").same()], recommended: false, + domains: &[RuleDomain::Playwright], } } diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_selector.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_selector.rs index fda8c606d6e4..8648fef8a7c3 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_selector.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_selector.rs @@ -1,5 +1,5 @@ use biome_analyze::{ - Ast, Rule, RuleDiagnostic, RuleSource, context::RuleContext, declare_lint_rule, + Ast, Rule, RuleDiagnostic, RuleDomain, RuleSource, context::RuleContext, declare_lint_rule, }; use biome_console::markup; use biome_js_syntax::{JsCallExpression, JsStaticMemberExpression}; @@ -45,6 +45,7 @@ declare_lint_rule! { language: "js", sources: &[RuleSource::EslintPlaywright("no-wait-for-selector").same()], recommended: false, + domains: &[RuleDomain::Playwright], } } diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_timeout.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_timeout.rs index 4febeef312ff..ff9e51d57c28 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_timeout.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_timeout.rs @@ -1,5 +1,5 @@ use biome_analyze::{ - Ast, Rule, RuleDiagnostic, RuleSource, context::RuleContext, declare_lint_rule, + Ast, Rule, RuleDiagnostic, RuleDomain, RuleSource, context::RuleContext, declare_lint_rule, }; use biome_console::markup; use biome_js_syntax::{JsCallExpression, JsStaticMemberExpression}; @@ -44,6 +44,7 @@ declare_lint_rule! { language: "js", sources: &[RuleSource::EslintPlaywright("no-wait-for-timeout").same()], recommended: false, + domains: &[RuleDomain::Playwright], } } diff --git a/crates/biome_js_analyze/src/lint/suspicious/no_focused_tests.rs b/crates/biome_js_analyze/src/lint/suspicious/no_focused_tests.rs index 86fa1462ead3..491f935c9661 100644 --- a/crates/biome_js_analyze/src/lint/suspicious/no_focused_tests.rs +++ b/crates/biome_js_analyze/src/lint/suspicious/no_focused_tests.rs @@ -50,6 +50,7 @@ declare_lint_rule! { sources: &[ RuleSource::EslintJest("no-focused-tests").inspired(), RuleSource::EslintVitest("no-focused-tests").inspired(), + RuleSource::EslintPlaywright("no-focused-test").same(), ], fix_kind: FixKind::Unsafe, domains: &[RuleDomain::Test], diff --git a/crates/biome_rule_options/src/lib.rs b/crates/biome_rule_options/src/lib.rs index 8adaa31cce3c..c14925b730f6 100644 --- a/crates/biome_rule_options/src/lib.rs +++ b/crates/biome_rule_options/src/lib.rs @@ -143,7 +143,6 @@ pub mod no_parameter_assign; pub mod no_parameter_properties; pub mod no_playwright_element_handle; pub mod no_playwright_eval; -pub mod no_playwright_focused_test; pub mod no_playwright_force_option; pub mod no_playwright_networkidle; pub mod no_playwright_page_pause; diff --git a/crates/biome_rule_options/src/no_playwright_focused_test.rs b/crates/biome_rule_options/src/no_playwright_focused_test.rs deleted file mode 100644 index df36136e4c3a..000000000000 --- a/crates/biome_rule_options/src/no_playwright_focused_test.rs +++ /dev/null @@ -1,6 +0,0 @@ -use biome_deserialize_macros::Deserializable; -use serde::{Deserialize, Serialize}; -#[derive(Default, Clone, Debug, Deserialize, Deserializable, Eq, PartialEq, Serialize)] -#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] -#[serde(rename_all = "camelCase", deny_unknown_fields, default)] -pub struct NoPlaywrightFocusedTestOptions {} From 7194c7222448d51ac227d0af03c2e8cd1086abe7 Mon Sep 17 00:00:00 2001 From: Josh Delsman <12201+voxxit@users.noreply.github.com> Date: Sat, 18 Oct 2025 23:18:47 -0500 Subject: [PATCH 14/24] feat(biome_js_analyze): add 13 new Playwright lint rules to nursery This commit introduces 13 new lint rules for Playwright, enhancing the linting capabilities for Playwright-specific code. The new rules include checks for awaiting async APIs, usage of element handles, and deprecated methods. Additionally, the existing `noFocusedTests` rule has been updated to detect Playwright's `test.only()` pattern. The following rules have been added: - `missingPlaywrightAwait` - `noPlaywrightElementHandle` - `noPlaywrightEval` - `noPlaywrightForceOption` - `noPlaywrightNetworkidle` - `noPlaywrightPagePause` - `noPlaywrightSkippedTest` - `noPlaywrightUselessAwait` - `noPlaywrightValidDescribeCallback` - `noPlaywrightWaitForNavigation` - `noPlaywrightWaitForSelector` - `noPlaywrightWaitForTimeout` These additions aim to improve code quality and adherence to best practices in Playwright usage. --- .changeset/common-lizards-sniff.md | 19 +++++++++++- .../nursery/no_playwright_element_handle.rs | 29 +++++++++-------- .../src/lint/nursery/no_playwright_eval.rs | 31 +++++++++---------- .../lint/nursery/no_playwright_page_pause.rs | 13 +++----- .../no_playwright_wait_for_navigation.rs | 13 +++----- .../no_playwright_wait_for_selector.rs | 13 +++----- .../nursery/no_playwright_wait_for_timeout.rs | 15 +++------ .../@biomejs/backend-jsonrpc/src/workspace.ts | 3 +- 8 files changed, 66 insertions(+), 70 deletions(-) diff --git a/.changeset/common-lizards-sniff.md b/.changeset/common-lizards-sniff.md index d7ae7fe07de0..dabf3f9905f0 100644 --- a/.changeset/common-lizards-sniff.md +++ b/.changeset/common-lizards-sniff.md @@ -2,4 +2,21 @@ "@biomejs/biome": patch --- -Added 13 new Playwright lint rules to the nursery (from eslint-plugin-playwright) +Added 13 new Playwright lint rules to the nursery (from eslint-plugin-playwright). + +The following rules are now available: + +- [`missingPlaywrightAwait`](https://biomejs.dev/linter/rules/missing-playwright-await/): Enforce awaiting async Playwright APIs. +- [`noPlaywrightElementHandle`](https://biomejs.dev/linter/rules/no-playwright-element-handle/): Prefer locators over element handles (`page.$()` and `page.$$()`). +- [`noPlaywrightEval`](https://biomejs.dev/linter/rules/no-playwright-eval/): Disallow `page.$eval()` and `page.$$eval()` methods. +- [`noPlaywrightForceOption`](https://biomejs.dev/linter/rules/no-playwright-force-option/): Disallow the `force` option on user interactions. +- [`noPlaywrightNetworkidle`](https://biomejs.dev/linter/rules/no-playwright-networkidle/): Disallow deprecated `networkidle` wait option. +- [`noPlaywrightPagePause`](https://biomejs.dev/linter/rules/no-playwright-page-pause/): Disallow `page.pause()` debugging calls in committed code. +- [`noPlaywrightSkippedTest`](https://biomejs.dev/linter/rules/no-playwright-skipped-test/): Disallow skipped tests with `test.skip()`. +- [`noPlaywrightUselessAwait`](https://biomejs.dev/linter/rules/no-playwright-useless-await/): Disallow unnecessary `await` on synchronous Playwright methods. +- [`noPlaywrightValidDescribeCallback`](https://biomejs.dev/linter/rules/no-playwright-valid-describe-callback/): Validate describe callback signatures are not async. +- [`noPlaywrightWaitForNavigation`](https://biomejs.dev/linter/rules/no-playwright-wait-for-navigation/): Prefer modern navigation APIs over deprecated `waitForNavigation()`. +- [`noPlaywrightWaitForSelector`](https://biomejs.dev/linter/rules/no-playwright-wait-for-selector/): Prefer locators over deprecated `waitForSelector()`. +- [`noPlaywrightWaitForTimeout`](https://biomejs.dev/linter/rules/no-playwright-wait-for-timeout/): Disallow hard-coded timeouts with `waitForTimeout()`. + +Additionally, the existing `noFocusedTests` rule now detects Playwright's `test.only()` pattern. diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_element_handle.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_element_handle.rs index 9ff9881a4d01..f5d099a6d273 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_playwright_element_handle.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_playwright_element_handle.rs @@ -3,7 +3,7 @@ use biome_analyze::{ }; use biome_console::markup; use biome_js_syntax::{JsCallExpression, JsStaticMemberExpression}; -use biome_rowan::AstNode; +use biome_rowan::{AstNode, TokenText}; declare_lint_rule! { /// Disallow usage of element handles (`page.$()` and `page.$$()`). @@ -56,7 +56,7 @@ declare_lint_rule! { impl Rule for NoPlaywrightElementHandle { type Query = Ast; - type State = String; + type State = TokenText; type Signals = Option; type Options = (); @@ -67,8 +67,11 @@ impl Rule for NoPlaywrightElementHandle { let member_expr = JsStaticMemberExpression::cast_ref(callee.syntax())?; let member_name = member_expr.member().ok()?; - let member_text = member_name.as_js_name()?.value_token().ok()?; - let member_str = member_text.text_trimmed(); + let member_str = member_name + .as_js_name()? + .value_token() + .ok()? + .token_text_trimmed(); // Check if the method is $ or $$ if member_str != "$" && member_str != "$$" { @@ -77,21 +80,16 @@ impl Rule for NoPlaywrightElementHandle { let object = member_expr.object().ok()?; let object_text = match object { - biome_js_syntax::AnyJsExpression::JsIdentifierExpression(id) => id - .name() - .ok()? - .value_token() - .ok()? - .text_trimmed() - .to_string(), + biome_js_syntax::AnyJsExpression::JsIdentifierExpression(id) => { + id.name().ok()?.value_token().ok()?.token_text_trimmed() + } biome_js_syntax::AnyJsExpression::JsStaticMemberExpression(member) => member .member() .ok()? .as_js_name()? .value_token() .ok()? - .text_trimmed() - .to_string(), + .token_text_trimmed(), _ => return None, }; @@ -101,7 +99,7 @@ impl Rule for NoPlaywrightElementHandle { || object_text.ends_with("Page") || object_text.ends_with("Frame") { - Some(member_str.to_string()) + Some(member_str) } else { None } @@ -109,6 +107,7 @@ impl Rule for NoPlaywrightElementHandle { fn diagnostic(ctx: &RuleContext, state: &Self::State) -> Option { let node = ctx.query(); + let state_text = state.text(); Some( RuleDiagnostic::new( rule_category!(), @@ -118,7 +117,7 @@ impl Rule for NoPlaywrightElementHandle { }, ) .note(markup! { - "Element handles like ""page."{{state}}"()"" are discouraged." + "Element handles like ""page."{{state_text}}"()"" are discouraged." }) .note(markup! { "Use ""page.locator()"" or other locator methods like ""getByRole()"" instead." diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_eval.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_eval.rs index 3e81cf65f9f9..7ed416e2a469 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_playwright_eval.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_playwright_eval.rs @@ -3,7 +3,7 @@ use biome_analyze::{ }; use biome_console::markup; use biome_js_syntax::{JsCallExpression, JsStaticMemberExpression}; -use biome_rowan::AstNode; +use biome_rowan::{AstNode, TokenText}; declare_lint_rule! { /// Disallow usage of `page.$eval()` and `page.$$eval()`. @@ -45,7 +45,7 @@ declare_lint_rule! { impl Rule for NoPlaywrightEval { type Query = Ast; - type State = String; + type State = TokenText; type Signals = Option; type Options = (); @@ -56,8 +56,11 @@ impl Rule for NoPlaywrightEval { let member_expr = JsStaticMemberExpression::cast_ref(callee.syntax())?; let member_name = member_expr.member().ok()?; - let member_text = member_name.as_js_name()?.value_token().ok()?; - let member_str = member_text.text_trimmed(); + let member_str = member_name + .as_js_name()? + .value_token() + .ok()? + .token_text_trimmed(); // Check if the method is $eval or $$eval if member_str != "$eval" && member_str != "$$eval" { @@ -66,21 +69,16 @@ impl Rule for NoPlaywrightEval { let object = member_expr.object().ok()?; let object_text = match object { - biome_js_syntax::AnyJsExpression::JsIdentifierExpression(id) => id - .name() - .ok()? - .value_token() - .ok()? - .text_trimmed() - .to_string(), + biome_js_syntax::AnyJsExpression::JsIdentifierExpression(id) => { + id.name().ok()?.value_token().ok()?.token_text_trimmed() + } biome_js_syntax::AnyJsExpression::JsStaticMemberExpression(member) => member .member() .ok()? .as_js_name()? .value_token() .ok()? - .text_trimmed() - .to_string(), + .token_text_trimmed(), _ => return None, }; @@ -89,7 +87,7 @@ impl Rule for NoPlaywrightEval { || object_text.ends_with("Page") || object_text.ends_with("Frame") { - Some(member_str.to_string()) + Some(member_str) } else { None } @@ -97,14 +95,15 @@ impl Rule for NoPlaywrightEval { fn diagnostic(ctx: &RuleContext, state: &Self::State) -> Option { let node = ctx.query(); - let is_eval = state == "$eval"; + let state_text = state.text(); + let is_eval = state_text == "$eval"; Some( RuleDiagnostic::new( rule_category!(), node.range(), markup! { - "Unexpected use of ""page."{{state}}"()""." + "Unexpected use of ""page."{{state_text}}"()""." }, ) .note(markup! { diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_page_pause.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_page_pause.rs index 1246b54ba019..3b0f4ae702f6 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_playwright_page_pause.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_playwright_page_pause.rs @@ -70,13 +70,9 @@ impl Rule for NoPlaywrightPagePause { // Check if the object is "page" or "frame" let object = member_expr.object().ok()?; let object_text = match object { - biome_js_syntax::AnyJsExpression::JsIdentifierExpression(id) => id - .name() - .ok()? - .value_token() - .ok()? - .text_trimmed() - .to_string(), + biome_js_syntax::AnyJsExpression::JsIdentifierExpression(id) => { + id.name().ok()?.value_token().ok()?.token_text_trimmed() + } biome_js_syntax::AnyJsExpression::JsStaticMemberExpression(member) => { // Handle cases like "context.page.pause()" member @@ -85,8 +81,7 @@ impl Rule for NoPlaywrightPagePause { .as_js_name()? .value_token() .ok()? - .text_trimmed() - .to_string() + .token_text_trimmed() } _ => return None, }; diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_navigation.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_navigation.rs index f1eac04d71d8..a0ca24ab4390 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_navigation.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_navigation.rs @@ -69,21 +69,16 @@ impl Rule for NoPlaywrightWaitForNavigation { let object = member_expr.object().ok()?; let object_text = match object { - biome_js_syntax::AnyJsExpression::JsIdentifierExpression(id) => id - .name() - .ok()? - .value_token() - .ok()? - .text_trimmed() - .to_string(), + biome_js_syntax::AnyJsExpression::JsIdentifierExpression(id) => { + id.name().ok()?.value_token().ok()?.token_text_trimmed() + } biome_js_syntax::AnyJsExpression::JsStaticMemberExpression(member) => member .member() .ok()? .as_js_name()? .value_token() .ok()? - .text_trimmed() - .to_string(), + .token_text_trimmed(), _ => return None, }; diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_selector.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_selector.rs index 8648fef8a7c3..cff4186e8da2 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_selector.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_selector.rs @@ -70,21 +70,16 @@ impl Rule for NoPlaywrightWaitForSelector { let object = member_expr.object().ok()?; let object_text = match object { - biome_js_syntax::AnyJsExpression::JsIdentifierExpression(id) => id - .name() - .ok()? - .value_token() - .ok()? - .text_trimmed() - .to_string(), + biome_js_syntax::AnyJsExpression::JsIdentifierExpression(id) => { + id.name().ok()?.value_token().ok()?.token_text_trimmed() + } biome_js_syntax::AnyJsExpression::JsStaticMemberExpression(member) => member .member() .ok()? .as_js_name()? .value_token() .ok()? - .text_trimmed() - .to_string(), + .token_text_trimmed(), _ => return None, }; diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_timeout.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_timeout.rs index ff9e51d57c28..418867ce697f 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_timeout.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_timeout.rs @@ -69,21 +69,16 @@ impl Rule for NoPlaywrightWaitForTimeout { let object = member_expr.object().ok()?; let object_text = match object { - biome_js_syntax::AnyJsExpression::JsIdentifierExpression(id) => id - .name() - .ok()? - .value_token() - .ok()? - .text_trimmed() - .to_string(), + biome_js_syntax::AnyJsExpression::JsIdentifierExpression(id) => { + id.name().ok()?.value_token().ok()?.token_text_trimmed() + } biome_js_syntax::AnyJsExpression::JsStaticMemberExpression(member) => member .member() .ok()? .as_js_name()? .value_token() .ok()? - .text_trimmed() - .to_string(), + .token_text_trimmed(), _ => return None, }; @@ -105,7 +100,7 @@ impl Rule for NoPlaywrightWaitForTimeout { rule_category!(), node.range(), markup! { - "Unexpected use of ""page.waitForTimeout()""." + "Unexpected use of ""waitForTimeout()""." }, ) .note(markup! { diff --git a/packages/@biomejs/backend-jsonrpc/src/workspace.ts b/packages/@biomejs/backend-jsonrpc/src/workspace.ts index 482142630869..d8e0f2b9c938 100644 --- a/packages/@biomejs/backend-jsonrpc/src/workspace.ts +++ b/packages/@biomejs/backend-jsonrpc/src/workspace.ts @@ -909,7 +909,8 @@ export type RuleDomain = | "qwik" | "vue" | "project" - | "tailwind"; + | "tailwind" + | "playwright"; export type RuleDomainValue = "all" | "none" | "recommended"; export type SeverityOrGroup_for_A11y = GroupPlainConfiguration | A11y; export type SeverityOrGroup_for_Complexity = From 5cc62d5dce665a2597d4e344d9a0599a791e827d Mon Sep 17 00:00:00 2001 From: Josh Delsman <12201+voxxit@users.noreply.github.com> Date: Sat, 18 Oct 2025 23:55:51 -0500 Subject: [PATCH 15/24] refactor(biome_js_analyze): enhance missingPlaywrightAwait rule and update tests This commit improves the `missingPlaywrightAwait` lint rule by adding checks for Promise combinators and ensuring that they are awaited when necessary. The logic now correctly identifies when a Promise combinator is not awaited and suggests adding an `await` to preserve concurrency semantics. Additionally, the rule's test cases have been updated to reflect these changes, including new diagnostics for invalid usages of `expect.poll` and `Promise.all`. Obsolete test snapshots related to focused tests have also been removed, streamlining the test suite. These enhancements aim to improve linting accuracy and maintainability for Playwright-specific code. --- .../lint/nursery/missing_playwright_await.rs | 49 ++++- .../nursery/no_playwright_force_option.rs | 9 +- .../invalid/bracket-notation.js | 28 +++ .../invalid/bracket-notation.js.snap | 36 ++++ .../invalid/expect-poll-sync-matchers.js | 16 ++ .../invalid/expect-poll-sync-matchers.js.snap | 130 ++++++++++++ .../invalid/expect-poll.js.snap | 27 ++- .../invalid/nested-expects.js | 12 ++ .../invalid/nested-expects.js.snap | 100 +++++++++ .../invalid/promise-all-not-awaited.js.snap | 26 ++- .../invalid/promise-combinators.js | 20 ++ .../invalid/promise-combinators.js.snap | 189 ++++++++++++++++++ .../valid/bracket-notation-valid.js | 29 +++ .../valid/bracket-notation-valid.js.snap | 37 ++++ .../valid/promise-combinators-valid.js | 29 +++ .../valid/promise-combinators-valid.js.snap | 37 ++++ .../invalid/bracket-notation.js | 12 ++ .../invalid/bracket-notation.js.snap | 20 ++ .../invalid/nested-calls.js | 9 + .../invalid/nested-calls.js.snap | 99 +++++++++ .../invalid/bracket-notation.js | 12 ++ .../invalid/bracket-notation.js.snap | 20 ++ .../invalid/multiple-arguments.js | 8 + .../invalid/multiple-arguments.js.snap | 91 +++++++++ .../invalid/describe-only.js | 5 - .../invalid/describe-only.js.snap | 35 ---- .../invalid/describe-parallel-only.js | 5 - .../invalid/describe-parallel-only.js.snap | 35 ---- .../invalid/test-only.js | 2 - .../invalid/test-only.js.snap | 28 --- .../invalid/bracket-notation-methods.js | 12 ++ .../invalid/bracket-notation-methods.js.snap | 20 ++ .../invalid/force-string-literal.js | 4 + .../invalid/force-string-literal.js.snap | 49 +++++ .../invalid/select-option-force.js | 6 + .../invalid/select-option-force.js.snap | 70 +++++++ .../invalid/all-methods.js | 14 ++ .../invalid/all-methods.js.snap | 154 ++++++++++++++ .../invalid/bracket-notation.js | 10 + .../invalid/bracket-notation.js.snap | 18 ++ .../valid/other-wait-options.js | 12 ++ .../valid/other-wait-options.js.snap | 20 ++ .../invalid/bracket-notation.js | 8 + .../invalid/bracket-notation.js.snap | 16 ++ .../invalid/async.js.snap | 3 +- .../invalid/has-params.js.snap | 3 +- .../invalid/in-test.js.snap | 3 +- .../invalid/simple.js.snap | 3 +- 48 files changed, 1433 insertions(+), 147 deletions(-) create mode 100644 crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/bracket-notation.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/bracket-notation.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/expect-poll-sync-matchers.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/expect-poll-sync-matchers.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/nested-expects.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/nested-expects.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/promise-combinators.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/promise-combinators.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/bracket-notation-valid.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/bracket-notation-valid.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/promise-combinators-valid.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/promise-combinators-valid.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/bracket-notation.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/bracket-notation.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/nested-calls.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/nested-calls.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/bracket-notation.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/bracket-notation.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/multiple-arguments.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/multiple-arguments.js.snap delete mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightFocusedTest/invalid/describe-only.js delete mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightFocusedTest/invalid/describe-only.js.snap delete mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightFocusedTest/invalid/describe-parallel-only.js delete mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightFocusedTest/invalid/describe-parallel-only.js.snap delete mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightFocusedTest/invalid/test-only.js delete mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightFocusedTest/invalid/test-only.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/bracket-notation-methods.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/bracket-notation-methods.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/force-string-literal.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/force-string-literal.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/select-option-force.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/select-option-force.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/all-methods.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/all-methods.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/bracket-notation.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/bracket-notation.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/valid/other-wait-options.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/valid/other-wait-options.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/bracket-notation.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/bracket-notation.js.snap diff --git a/crates/biome_js_analyze/src/lint/nursery/missing_playwright_await.rs b/crates/biome_js_analyze/src/lint/nursery/missing_playwright_await.rs index 7315b27c4b96..89126d35cc36 100644 --- a/crates/biome_js_analyze/src/lint/nursery/missing_playwright_await.rs +++ b/crates/biome_js_analyze/src/lint/nursery/missing_playwright_await.rs @@ -184,9 +184,32 @@ impl Rule for MissingPlaywrightAwait { return None; } - let mut mutation = ctx.root().begin(); + // Check if inside a Promise combinator (Promise.all, etc.) that itself isn't awaited + // If so, fix the outer combinator to preserve concurrency semantics + if let Some(promise_combinator) = find_enclosing_promise_all(call_expr) { + if !is_call_awaited_or_returned(&promise_combinator) { + let mut mutation = ctx.root().begin(); + let await_expr = make::js_await_expression( + make::token(T![await]), + promise_combinator.clone().into(), + ); + + mutation.replace_element( + promise_combinator.into_syntax().into(), + await_expr.into_syntax().into(), + ); + + return Some(JsRuleAction::new( + ctx.metadata().action_category(ctx.category(), ctx.group()), + Applicability::MaybeIncorrect, + markup! { "Add await to Promise combinator" }.to_owned(), + mutation, + )); + } + } - // Create an await expression + // Normal case: fix the call directly + let mut mutation = ctx.root().begin(); let await_expr = make::js_await_expression(make::token(T![await]), call_expr.clone().into()); @@ -252,19 +275,20 @@ fn get_async_expect_matcher(call_expr: &JsCallExpression) -> Option Option Option bool { +fn is_promise_combinator(call: &JsCallExpression) -> bool { is_member_call_pattern(call, "Promise", "all") + || is_member_call_pattern(call, "Promise", "allSettled") + || is_member_call_pattern(call, "Promise", "race") + || is_member_call_pattern(call, "Promise", "any") } /// Checks if a node is within an async context (async function or module with TLA support). diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_force_option.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_force_option.rs index fc8825aaf425..a166120ea0fc 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_playwright_force_option.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_playwright_force_option.rs @@ -124,11 +124,10 @@ impl Rule for NoPlaywrightForceOption { fn has_force_true(obj_expr: &JsObjectExpression) -> bool { for member in obj_expr.members().into_iter().flatten() { if let Some(prop) = member.as_js_property_object_member() { - // Check if property name is 'force' - if let Ok(name) = prop.name() - && let Some(name_node) = name.as_js_literal_member_name() - && let Ok(name_token) = name_node.value() - && name_token.text_trimmed() == "force" + // Check if property name is 'force' - .name() handles both identifier and string literal + if let Ok(prop_name) = prop.name() + && let Some(name_text) = prop_name.name() + && name_text.text() == "force" { // Check if value is true if let Ok(value) = prop.value() diff --git a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/bracket-notation.js b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/bracket-notation.js new file mode 100644 index 000000000000..26382aac44c6 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/bracket-notation.js @@ -0,0 +1,28 @@ +// Test bracket notation for various Playwright async APIs +test('bracket notation for test.step', async ({ page }) => { + test["step"]('do something', async () => { + await page.click('button'); + }); +}); + +test('template literal bracket notation for test.step', async ({ page }) => { + test[`step`]('do something', async () => { + await page.click('button'); + }); +}); + +test('bracket notation for expect.soft', async ({ page }) => { + expect["soft"](page).toBeVisible(); +}); + +test('template literal for expect.soft', async ({ page }) => { + expect[`soft`](page).toBeVisible(); +}); + +test('bracket notation for matcher', async ({ page }) => { + expect(page)["toBeVisible"](); +}); + +test('template literal for matcher', async ({ page }) => { + expect(page)[`toBeVisible`](); +}); diff --git a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/bracket-notation.js.snap b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/bracket-notation.js.snap new file mode 100644 index 000000000000..8802d63dbbc6 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/bracket-notation.js.snap @@ -0,0 +1,36 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: bracket-notation.js +--- +# Input +```js +// Test bracket notation for various Playwright async APIs +test('bracket notation for test.step', async ({ page }) => { + test["step"]('do something', async () => { + await page.click('button'); + }); +}); + +test('template literal bracket notation for test.step', async ({ page }) => { + test[`step`]('do something', async () => { + await page.click('button'); + }); +}); + +test('bracket notation for expect.soft', async ({ page }) => { + expect["soft"](page).toBeVisible(); +}); + +test('template literal for expect.soft', async ({ page }) => { + expect[`soft`](page).toBeVisible(); +}); + +test('bracket notation for matcher', async ({ page }) => { + expect(page)["toBeVisible"](); +}); + +test('template literal for matcher', async ({ page }) => { + expect(page)[`toBeVisible`](); +}); + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/expect-poll-sync-matchers.js b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/expect-poll-sync-matchers.js new file mode 100644 index 000000000000..f7158c8c765c --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/expect-poll-sync-matchers.js @@ -0,0 +1,16 @@ +// expect.poll with synchronous matchers should still require await +test('toBe matcher', async () => { + expect.poll(() => getValue()).toBe(42); +}); + +test('toEqual matcher', async () => { + expect.poll(() => getObject()).toEqual({ foo: 'bar' }); +}); + +test('toMatch matcher', async () => { + expect.poll(() => getString()).toMatch(/pattern/); +}); + +test('toStrictEqual matcher', async () => { + expect.poll(() => getData()).toStrictEqual({ a: 1 }); +}); diff --git a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/expect-poll-sync-matchers.js.snap b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/expect-poll-sync-matchers.js.snap new file mode 100644 index 000000000000..7d5394f69c39 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/expect-poll-sync-matchers.js.snap @@ -0,0 +1,130 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: expect-poll-sync-matchers.js +--- +# Input +```js +// expect.poll with synchronous matchers should still require await +test('toBe matcher', async () => { + expect.poll(() => getValue()).toBe(42); +}); + +test('toEqual matcher', async () => { + expect.poll(() => getObject()).toEqual({ foo: 'bar' }); +}); + +test('toMatch matcher', async () => { + expect.poll(() => getString()).toMatch(/pattern/); +}); + +test('toStrictEqual matcher', async () => { + expect.poll(() => getData()).toStrictEqual({ a: 1 }); +}); + +``` + +# Diagnostics +``` +expect-poll-sync-matchers.js:3:5 lint/nursery/missingPlaywrightAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━ + + i expect.poll must be awaited or returned. + + 1 │ // expect.poll with synchronous matchers should still require await + 2 │ test('toBe matcher', async () => { + > 3 │ expect.poll(() => getValue()).toBe(42); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 4 │ }); + 5 │ + + i Add await before the expect call or return the promise. + + i Unsafe fix: Add await + + 1 1 │ // expect.poll with synchronous matchers should still require await + 2 2 │ test('toBe matcher', async () => { + 3 │ - ····expect.poll(()·=>·getValue()).toBe(42); + 3 │ + ····await + 4 │ + ····expect.poll(()·=>·getValue()).toBe(42); + 4 5 │ }); + 5 6 │ + + +``` + +``` +expect-poll-sync-matchers.js:7:5 lint/nursery/missingPlaywrightAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━ + + i expect.poll must be awaited or returned. + + 6 │ test('toEqual matcher', async () => { + > 7 │ expect.poll(() => getObject()).toEqual({ foo: 'bar' }); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 8 │ }); + 9 │ + + i Add await before the expect call or return the promise. + + i Unsafe fix: Add await + + 5 5 │ + 6 6 │ test('toEqual matcher', async () => { + 7 │ - ····expect.poll(()·=>·getObject()).toEqual({·foo:·'bar'·}); + 7 │ + ····await + 8 │ + ····expect.poll(()·=>·getObject()).toEqual({·foo:·'bar'·}); + 8 9 │ }); + 9 10 │ + + +``` + +``` +expect-poll-sync-matchers.js:11:5 lint/nursery/missingPlaywrightAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━ + + i expect.poll must be awaited or returned. + + 10 │ test('toMatch matcher', async () => { + > 11 │ expect.poll(() => getString()).toMatch(/pattern/); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 12 │ }); + 13 │ + + i Add await before the expect call or return the promise. + + i Unsafe fix: Add await + + 9 9 │ + 10 10 │ test('toMatch matcher', async () => { + 11 │ - ····expect.poll(()·=>·getString()).toMatch(/pattern/); + 11 │ + ····await + 12 │ + ····expect.poll(()·=>·getString()).toMatch(/pattern/); + 12 13 │ }); + 13 14 │ + + +``` + +``` +expect-poll-sync-matchers.js:15:5 lint/nursery/missingPlaywrightAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━ + + i expect.poll must be awaited or returned. + + 14 │ test('toStrictEqual matcher', async () => { + > 15 │ expect.poll(() => getData()).toStrictEqual({ a: 1 }); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 16 │ }); + 17 │ + + i Add await before the expect call or return the promise. + + i Unsafe fix: Add await + + 13 13 │ + 14 14 │ test('toStrictEqual matcher', async () => { + 15 │ - ····expect.poll(()·=>·getData()).toStrictEqual({·a:·1·}); + 15 │ + ····await + 16 │ + ····expect.poll(()·=>·getData()).toStrictEqual({·a:·1·}); + 16 17 │ }); + 17 18 │ + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/expect-poll.js.snap b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/expect-poll.js.snap index d7a3c166b590..1ff3f0877a74 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/expect-poll.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/expect-poll.js.snap @@ -1,6 +1,5 @@ --- source: crates/biome_js_analyze/tests/spec_tests.rs -assertion_line: 152 expression: expect-poll.js --- # Input @@ -10,4 +9,30 @@ test('example', async () => { }); +``` + +# Diagnostics +``` +expect-poll.js:2:5 lint/nursery/missingPlaywrightAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i expect.poll must be awaited or returned. + + 1 │ test('example', async () => { + > 2 │ expect.poll(() => foo).toBe(true); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 3 │ }); + 4 │ + + i Add await before the expect call or return the promise. + + i Unsafe fix: Add await + + 1 1 │ test('example', async () => { + 2 │ - ····expect.poll(()·=>·foo).toBe(true); + 2 │ + ····await + 3 │ + ····expect.poll(()·=>·foo).toBe(true); + 3 4 │ }); + 4 5 │ + + ``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/nested-expects.js b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/nested-expects.js new file mode 100644 index 000000000000..fd83bbf38005 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/nested-expects.js @@ -0,0 +1,12 @@ +// Test nested expect calls and chaining +test('nested not operator', async ({ page }) => { + expect(page).not.toBeVisible(); +}); + +test('chained not and soft', async ({ page }) => { + expect.soft(page).not.toHaveText("foo"); +}); + +test('multiple not calls', async ({ page }) => { + expect(page).not.not.toBeEnabled(); +}); diff --git a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/nested-expects.js.snap b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/nested-expects.js.snap new file mode 100644 index 000000000000..d5639d3e2e8c --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/nested-expects.js.snap @@ -0,0 +1,100 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: nested-expects.js +--- +# Input +```js +// Test nested expect calls and chaining +test('nested not operator', async ({ page }) => { + expect(page).not.toBeVisible(); +}); + +test('chained not and soft', async ({ page }) => { + expect.soft(page).not.toHaveText("foo"); +}); + +test('multiple not calls', async ({ page }) => { + expect(page).not.not.toBeEnabled(); +}); + +``` + +# Diagnostics +``` +nested-expects.js:3:5 lint/nursery/missingPlaywrightAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Async matcher toBeVisible must be awaited or returned. + + 1 │ // Test nested expect calls and chaining + 2 │ test('nested not operator', async ({ page }) => { + > 3 │ expect(page).not.toBeVisible(); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 4 │ }); + 5 │ + + i Add await before the expect call or return the promise. + + i Unsafe fix: Add await + + 1 1 │ // Test nested expect calls and chaining + 2 2 │ test('nested not operator', async ({ page }) => { + 3 │ - ····expect(page).not.toBeVisible(); + 3 │ + ····await + 4 │ + ····expect(page).not.toBeVisible(); + 4 5 │ }); + 5 6 │ + + +``` + +``` +nested-expects.js:7:5 lint/nursery/missingPlaywrightAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Async matcher toHaveText must be awaited or returned. + + 6 │ test('chained not and soft', async ({ page }) => { + > 7 │ expect.soft(page).not.toHaveText("foo"); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 8 │ }); + 9 │ + + i Add await before the expect call or return the promise. + + i Unsafe fix: Add await + + 5 5 │ + 6 6 │ test('chained not and soft', async ({ page }) => { + 7 │ - ····expect.soft(page).not.toHaveText("foo"); + 7 │ + ····await + 8 │ + ····expect.soft(page).not.toHaveText("foo"); + 8 9 │ }); + 9 10 │ + + +``` + +``` +nested-expects.js:11:5 lint/nursery/missingPlaywrightAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Async matcher toBeEnabled must be awaited or returned. + + 10 │ test('multiple not calls', async ({ page }) => { + > 11 │ expect(page).not.not.toBeEnabled(); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 12 │ }); + 13 │ + + i Add await before the expect call or return the promise. + + i Unsafe fix: Add await + + 9 9 │ + 10 10 │ test('multiple not calls', async ({ page }) => { + 11 │ - ····expect(page).not.not.toBeEnabled(); + 11 │ + ····await + 12 │ + ····expect(page).not.not.toBeEnabled(); + 12 13 │ }); + 13 14 │ + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/promise-all-not-awaited.js.snap b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/promise-all-not-awaited.js.snap index 3cfc16f2dcfc..908836b1549c 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/promise-all-not-awaited.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/promise-all-not-awaited.js.snap @@ -29,15 +29,14 @@ promise-all-not-awaited.js:3:9 lint/nursery/missingPlaywrightAwait FIXABLE ━ i Add await before the expect call or return the promise. - i Unsafe fix: Add await + i Unsafe fix: Add await to Promise combinator 1 1 │ test('example', async ({ page }) => { - 2 2 │ Promise.all([ - 3 │ - ········expect(page.locator('.one')).toBeVisible(), - 3 │ + ········await - 4 │ + ········expect(page.locator('.one')).toBeVisible(), + 2 │ - ····Promise.all([ + 2 │ + ····await + 3 │ + ····Promise.all([ + 3 4 │ expect(page.locator('.one')).toBeVisible(), 4 5 │ expect(page.locator('.two')).toBeVisible() - 5 6 │ ]); ``` @@ -56,15 +55,14 @@ promise-all-not-awaited.js:4:9 lint/nursery/missingPlaywrightAwait FIXABLE ━ i Add await before the expect call or return the promise. - i Unsafe fix: Add await + i Unsafe fix: Add await to Promise combinator - 2 2 │ Promise.all([ - 3 3 │ expect(page.locator('.one')).toBeVisible(), - 4 │ - ········expect(page.locator('.two')).toBeVisible() - 4 │ + ········await - 5 │ + ········expect(page.locator('.two')).toBeVisible() - 5 6 │ ]); - 6 7 │ }); + 1 1 │ test('example', async ({ page }) => { + 2 │ - ····Promise.all([ + 2 │ + ····await + 3 │ + ····Promise.all([ + 3 4 │ expect(page.locator('.one')).toBeVisible(), + 4 5 │ expect(page.locator('.two')).toBeVisible() ``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/promise-combinators.js b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/promise-combinators.js new file mode 100644 index 000000000000..3e9e422870e5 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/promise-combinators.js @@ -0,0 +1,20 @@ +test('Promise.allSettled', async ({ page }) => { + Promise.allSettled([ + expect(page).toBeVisible(), + expect(page).toHaveText('foo') + ]); +}); + +test('Promise.race', async ({ page }) => { + Promise.race([ + expect(page).toBeVisible(), + expect(page).toHaveText('foo') + ]); +}); + +test('Promise.any', async ({ page }) => { + Promise.any([ + expect(page).toBeVisible(), + expect(page).toHaveText('foo') + ]); +}); diff --git a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/promise-combinators.js.snap b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/promise-combinators.js.snap new file mode 100644 index 000000000000..5d8a6d7cc051 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/invalid/promise-combinators.js.snap @@ -0,0 +1,189 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: promise-combinators.js +--- +# Input +```js +test('Promise.allSettled', async ({ page }) => { + Promise.allSettled([ + expect(page).toBeVisible(), + expect(page).toHaveText('foo') + ]); +}); + +test('Promise.race', async ({ page }) => { + Promise.race([ + expect(page).toBeVisible(), + expect(page).toHaveText('foo') + ]); +}); + +test('Promise.any', async ({ page }) => { + Promise.any([ + expect(page).toBeVisible(), + expect(page).toHaveText('foo') + ]); +}); + +``` + +# Diagnostics +``` +promise-combinators.js:3:9 lint/nursery/missingPlaywrightAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Async matcher toBeVisible must be awaited or returned. + + 1 │ test('Promise.allSettled', async ({ page }) => { + 2 │ Promise.allSettled([ + > 3 │ expect(page).toBeVisible(), + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^ + 4 │ expect(page).toHaveText('foo') + 5 │ ]); + + i Add await before the expect call or return the promise. + + i Unsafe fix: Add await to Promise combinator + + 1 1 │ test('Promise.allSettled', async ({ page }) => { + 2 │ - ····Promise.allSettled([ + 2 │ + ····await + 3 │ + ····Promise.allSettled([ + 3 4 │ expect(page).toBeVisible(), + 4 5 │ expect(page).toHaveText('foo') + + +``` + +``` +promise-combinators.js:4:9 lint/nursery/missingPlaywrightAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Async matcher toHaveText must be awaited or returned. + + 2 │ Promise.allSettled([ + 3 │ expect(page).toBeVisible(), + > 4 │ expect(page).toHaveText('foo') + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 5 │ ]); + 6 │ }); + + i Add await before the expect call or return the promise. + + i Unsafe fix: Add await to Promise combinator + + 1 1 │ test('Promise.allSettled', async ({ page }) => { + 2 │ - ····Promise.allSettled([ + 2 │ + ····await + 3 │ + ····Promise.allSettled([ + 3 4 │ expect(page).toBeVisible(), + 4 5 │ expect(page).toHaveText('foo') + + +``` + +``` +promise-combinators.js:10:9 lint/nursery/missingPlaywrightAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Async matcher toBeVisible must be awaited or returned. + + 8 │ test('Promise.race', async ({ page }) => { + 9 │ Promise.race([ + > 10 │ expect(page).toBeVisible(), + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^ + 11 │ expect(page).toHaveText('foo') + 12 │ ]); + + i Add await before the expect call or return the promise. + + i Unsafe fix: Add await to Promise combinator + + 7 7 │ + 8 8 │ test('Promise.race', async ({ page }) => { + 9 │ - ····Promise.race([ + 9 │ + ····await + 10 │ + ····Promise.race([ + 10 11 │ expect(page).toBeVisible(), + 11 12 │ expect(page).toHaveText('foo') + + +``` + +``` +promise-combinators.js:11:9 lint/nursery/missingPlaywrightAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Async matcher toHaveText must be awaited or returned. + + 9 │ Promise.race([ + 10 │ expect(page).toBeVisible(), + > 11 │ expect(page).toHaveText('foo') + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 12 │ ]); + 13 │ }); + + i Add await before the expect call or return the promise. + + i Unsafe fix: Add await to Promise combinator + + 7 7 │ + 8 8 │ test('Promise.race', async ({ page }) => { + 9 │ - ····Promise.race([ + 9 │ + ····await + 10 │ + ····Promise.race([ + 10 11 │ expect(page).toBeVisible(), + 11 12 │ expect(page).toHaveText('foo') + + +``` + +``` +promise-combinators.js:17:9 lint/nursery/missingPlaywrightAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Async matcher toBeVisible must be awaited or returned. + + 15 │ test('Promise.any', async ({ page }) => { + 16 │ Promise.any([ + > 17 │ expect(page).toBeVisible(), + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^ + 18 │ expect(page).toHaveText('foo') + 19 │ ]); + + i Add await before the expect call or return the promise. + + i Unsafe fix: Add await to Promise combinator + + 14 14 │ + 15 15 │ test('Promise.any', async ({ page }) => { + 16 │ - ····Promise.any([ + 16 │ + ····await + 17 │ + ····Promise.any([ + 17 18 │ expect(page).toBeVisible(), + 18 19 │ expect(page).toHaveText('foo') + + +``` + +``` +promise-combinators.js:18:9 lint/nursery/missingPlaywrightAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Async matcher toHaveText must be awaited or returned. + + 16 │ Promise.any([ + 17 │ expect(page).toBeVisible(), + > 18 │ expect(page).toHaveText('foo') + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 19 │ ]); + 20 │ }); + + i Add await before the expect call or return the promise. + + i Unsafe fix: Add await to Promise combinator + + 14 14 │ + 15 15 │ test('Promise.any', async ({ page }) => { + 16 │ - ····Promise.any([ + 16 │ + ····await + 17 │ + ····Promise.any([ + 17 18 │ expect(page).toBeVisible(), + 18 19 │ expect(page).toHaveText('foo') + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/bracket-notation-valid.js b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/bracket-notation-valid.js new file mode 100644 index 000000000000..cfc3d5b44d57 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/bracket-notation-valid.js @@ -0,0 +1,29 @@ +// should not generate diagnostics +// Valid: awaited bracket notation +test('awaited bracket notation for test.step', async ({ page }) => { + await test["step"]('do something', async () => { + await page.click('button'); + }); +}); + +test('awaited template literal for test.step', async ({ page }) => { + await test[`step`]('do something', async () => { + await page.click('button'); + }); +}); + +test('awaited bracket notation for expect.soft', async ({ page }) => { + await expect["soft"](page).toBeVisible(); +}); + +test('awaited template literal for expect.soft', async ({ page }) => { + await expect[`soft`](page).toBeVisible(); +}); + +test('awaited bracket notation for matcher', async ({ page }) => { + await expect(page)["toBeVisible"](); +}); + +test('awaited template literal for matcher', async ({ page }) => { + await expect(page)[`toBeVisible`](); +}); diff --git a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/bracket-notation-valid.js.snap b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/bracket-notation-valid.js.snap new file mode 100644 index 000000000000..85e14c82071b --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/bracket-notation-valid.js.snap @@ -0,0 +1,37 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: bracket-notation-valid.js +--- +# Input +```js +// should not generate diagnostics +// Valid: awaited bracket notation +test('awaited bracket notation for test.step', async ({ page }) => { + await test["step"]('do something', async () => { + await page.click('button'); + }); +}); + +test('awaited template literal for test.step', async ({ page }) => { + await test[`step`]('do something', async () => { + await page.click('button'); + }); +}); + +test('awaited bracket notation for expect.soft', async ({ page }) => { + await expect["soft"](page).toBeVisible(); +}); + +test('awaited template literal for expect.soft', async ({ page }) => { + await expect[`soft`](page).toBeVisible(); +}); + +test('awaited bracket notation for matcher', async ({ page }) => { + await expect(page)["toBeVisible"](); +}); + +test('awaited template literal for matcher', async ({ page }) => { + await expect(page)[`toBeVisible`](); +}); + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/promise-combinators-valid.js b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/promise-combinators-valid.js new file mode 100644 index 000000000000..2a94677ab2b0 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/promise-combinators-valid.js @@ -0,0 +1,29 @@ +// should not generate diagnostics +// Valid: awaited Promise combinators +test('awaited Promise.all', async ({ page }) => { + await Promise.all([ + expect(page).toBeVisible(), + expect(page).toHaveText('foo') + ]); +}); + +test('awaited Promise.allSettled', async ({ page }) => { + await Promise.allSettled([ + expect(page).toBeVisible(), + expect(page).toHaveText('foo') + ]); +}); + +test('returned Promise.race', async ({ page }) => { + return Promise.race([ + expect(page).toBeVisible(), + expect(page).toHaveText('foo') + ]); +}); + +test('returned Promise.any', async ({ page }) => { + return Promise.any([ + expect(page).toBeVisible(), + expect(page).toHaveText('foo') + ]); +}); diff --git a/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/promise-combinators-valid.js.snap b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/promise-combinators-valid.js.snap new file mode 100644 index 000000000000..e6a8bf71e81a --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/missingPlaywrightAwait/valid/promise-combinators-valid.js.snap @@ -0,0 +1,37 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: promise-combinators-valid.js +--- +# Input +```js +// should not generate diagnostics +// Valid: awaited Promise combinators +test('awaited Promise.all', async ({ page }) => { + await Promise.all([ + expect(page).toBeVisible(), + expect(page).toHaveText('foo') + ]); +}); + +test('awaited Promise.allSettled', async ({ page }) => { + await Promise.allSettled([ + expect(page).toBeVisible(), + expect(page).toHaveText('foo') + ]); +}); + +test('returned Promise.race', async ({ page }) => { + return Promise.race([ + expect(page).toBeVisible(), + expect(page).toHaveText('foo') + ]); +}); + +test('returned Promise.any', async ({ page }) => { + return Promise.any([ + expect(page).toBeVisible(), + expect(page).toHaveText('foo') + ]); +}); + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/bracket-notation.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/bracket-notation.js new file mode 100644 index 000000000000..9d8c2fed33b6 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/bracket-notation.js @@ -0,0 +1,12 @@ +// Test bracket notation for element handles +const handle1 = await page["$"]("button"); + +const handle2 = await page[`$`]("button"); + +const handles1 = await page["$$"]("a"); + +const handles2 = await page[`$$`]("a"); + +await this.page["$"]("input"); + +await this.page[`$$`]("div"); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/bracket-notation.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/bracket-notation.js.snap new file mode 100644 index 000000000000..cdf1a0688733 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/bracket-notation.js.snap @@ -0,0 +1,20 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: bracket-notation.js +--- +# Input +```js +// Test bracket notation for element handles +const handle1 = await page["$"]("button"); + +const handle2 = await page[`$`]("button"); + +const handles1 = await page["$$"]("a"); + +const handles2 = await page[`$$`]("a"); + +await this.page["$"]("input"); + +await this.page[`$$`]("div"); + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/nested-calls.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/nested-calls.js new file mode 100644 index 000000000000..fe7269fbf94b --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/nested-calls.js @@ -0,0 +1,9 @@ +// Test nested element handle calls +const button1 = await (await page.$("button")); + +const button2 = await page.$("#foo"); + +await (await page.$$("div")); + +let handle; +handle = await page.$("input"); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/nested-calls.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/nested-calls.js.snap new file mode 100644 index 000000000000..bfe09b2ceeaf --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/nested-calls.js.snap @@ -0,0 +1,99 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: nested-calls.js +--- +# Input +```js +// Test nested element handle calls +const button1 = await (await page.$("button")); + +const button2 = await page.$("#foo"); + +await (await page.$$("div")); + +let handle; +handle = await page.$("input"); + +``` + +# Diagnostics +``` +nested-calls.js:2:30 lint/nursery/noPlaywrightElementHandle ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected use of element handles. + + 1 │ // Test nested element handle calls + > 2 │ const button1 = await (await page.$("button")); + │ ^^^^^^^^^^^^^^^^ + 3 │ + 4 │ const button2 = await page.$("#foo"); + + i Element handles like page.$() are discouraged. + + i Use page.locator() or other locator methods like getByRole() instead. + + i Locators auto-wait and are more reliable than element handles. + + +``` + +``` +nested-calls.js:4:23 lint/nursery/noPlaywrightElementHandle ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected use of element handles. + + 2 │ const button1 = await (await page.$("button")); + 3 │ + > 4 │ const button2 = await page.$("#foo"); + │ ^^^^^^^^^^^^^^ + 5 │ + 6 │ await (await page.$$("div")); + + i Element handles like page.$() are discouraged. + + i Use page.locator() or other locator methods like getByRole() instead. + + i Locators auto-wait and are more reliable than element handles. + + +``` + +``` +nested-calls.js:6:14 lint/nursery/noPlaywrightElementHandle ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected use of element handles. + + 4 │ const button2 = await page.$("#foo"); + 5 │ + > 6 │ await (await page.$$("div")); + │ ^^^^^^^^^^^^^^ + 7 │ + 8 │ let handle; + + i Element handles like page.$$() are discouraged. + + i Use page.locator() or other locator methods like getByRole() instead. + + i Locators auto-wait and are more reliable than element handles. + + +``` + +``` +nested-calls.js:9:16 lint/nursery/noPlaywrightElementHandle ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected use of element handles. + + 8 │ let handle; + > 9 │ handle = await page.$("input"); + │ ^^^^^^^^^^^^^^^ + 10 │ + + i Element handles like page.$() are discouraged. + + i Use page.locator() or other locator methods like getByRole() instead. + + i Locators auto-wait and are more reliable than element handles. + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/bracket-notation.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/bracket-notation.js new file mode 100644 index 000000000000..92b934f91d31 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/bracket-notation.js @@ -0,0 +1,12 @@ +// Test bracket notation for $eval and $$eval +await page["$eval"]("#search", el => el.value); + +await page[`$eval`]("#search", el => el.value); + +await page["$$eval"]("div", els => els.length); + +await page[`$$eval`]("div", els => els.length); + +await this.page["$eval"]("#input", el => el.checked); + +await this.page[`$$eval`]("span", els => els.map(e => e.textContent)); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/bracket-notation.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/bracket-notation.js.snap new file mode 100644 index 000000000000..380dc872aa47 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/bracket-notation.js.snap @@ -0,0 +1,20 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: bracket-notation.js +--- +# Input +```js +// Test bracket notation for $eval and $$eval +await page["$eval"]("#search", el => el.value); + +await page[`$eval`]("#search", el => el.value); + +await page["$$eval"]("div", els => els.length); + +await page[`$$eval`]("div", els => els.length); + +await this.page["$eval"]("#input", el => el.checked); + +await this.page[`$$eval`]("span", els => els.map(e => e.textContent)); + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/multiple-arguments.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/multiple-arguments.js new file mode 100644 index 000000000000..e724511ec5e6 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/multiple-arguments.js @@ -0,0 +1,8 @@ +// Test $eval and $$eval with multiple arguments +await page.$eval(".main-container", (e, suffix) => e.outerHTML + suffix, "hello"); + +await page.$$eval("div", (divs, min) => divs.length >= min, 10); + +await page.$eval("#search", (el, prop) => el[prop], "value"); + +await this.page.$$eval("span", (els, className) => els.filter(e => e.className === className), "active"); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/multiple-arguments.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/multiple-arguments.js.snap new file mode 100644 index 000000000000..b8a48fbecf6a --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/multiple-arguments.js.snap @@ -0,0 +1,91 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: multiple-arguments.js +--- +# Input +```js +// Test $eval and $$eval with multiple arguments +await page.$eval(".main-container", (e, suffix) => e.outerHTML + suffix, "hello"); + +await page.$$eval("div", (divs, min) => divs.length >= min, 10); + +await page.$eval("#search", (el, prop) => el[prop], "value"); + +await this.page.$$eval("span", (els, className) => els.filter(e => e.className === className), "active"); + +``` + +# Diagnostics +``` +multiple-arguments.js:2:7 lint/nursery/noPlaywrightEval ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected use of page.$eval(). + + 1 │ // Test $eval and $$eval with multiple arguments + > 2 │ await page.$eval(".main-container", (e, suffix) => e.outerHTML + suffix, "hello"); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 3 │ + 4 │ await page.$$eval("div", (divs, min) => divs.length >= min, 10); + + i Use locator.evaluate() instead. + + i Locator-based evaluation is more reliable and follows Playwright's recommended patterns. + + +``` + +``` +multiple-arguments.js:4:7 lint/nursery/noPlaywrightEval ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected use of page.$$eval(). + + 2 │ await page.$eval(".main-container", (e, suffix) => e.outerHTML + suffix, "hello"); + 3 │ + > 4 │ await page.$$eval("div", (divs, min) => divs.length >= min, 10); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 5 │ + 6 │ await page.$eval("#search", (el, prop) => el[prop], "value"); + + i Use locator.evaluateAll() instead. + + i Locator-based evaluation is more reliable and follows Playwright's recommended patterns. + + +``` + +``` +multiple-arguments.js:6:7 lint/nursery/noPlaywrightEval ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected use of page.$eval(). + + 4 │ await page.$$eval("div", (divs, min) => divs.length >= min, 10); + 5 │ + > 6 │ await page.$eval("#search", (el, prop) => el[prop], "value"); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 7 │ + 8 │ await this.page.$$eval("span", (els, className) => els.filter(e => e.className === className), "active"); + + i Use locator.evaluate() instead. + + i Locator-based evaluation is more reliable and follows Playwright's recommended patterns. + + +``` + +``` +multiple-arguments.js:8:7 lint/nursery/noPlaywrightEval ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected use of page.$$eval(). + + 6 │ await page.$eval("#search", (el, prop) => el[prop], "value"); + 7 │ + > 8 │ await this.page.$$eval("span", (els, className) => els.filter(e => e.className === className), "active"); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 9 │ + + i Use locator.evaluateAll() instead. + + i Locator-based evaluation is more reliable and follows Playwright's recommended patterns. + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightFocusedTest/invalid/describe-only.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightFocusedTest/invalid/describe-only.js deleted file mode 100644 index 2b18e341690a..000000000000 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightFocusedTest/invalid/describe-only.js +++ /dev/null @@ -1,5 +0,0 @@ -test.describe.only('focus suite', () => { - test('one', async ({ page }) => {}); - test('two', async ({ page }) => {}); -}); - diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightFocusedTest/invalid/describe-only.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightFocusedTest/invalid/describe-only.js.snap deleted file mode 100644 index 14b7e6a25634..000000000000 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightFocusedTest/invalid/describe-only.js.snap +++ /dev/null @@ -1,35 +0,0 @@ ---- -source: crates/biome_js_analyze/tests/spec_tests.rs -assertion_line: 152 -expression: describe-only.js ---- -# Input -```js -test.describe.only('focus suite', () => { - test('one', async ({ page }) => {}); - test('two', async ({ page }) => {}); -}); - - -``` - -# Diagnostics -``` -describe-only.js:1:1 lint/nursery/noPlaywrightFocusedTest ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - i Unexpected focused test using .only annotation. - - > 1 │ test.describe.only('focus suite', () => { - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - > 2 │ test('one', async ({ page }) => {}); - > 3 │ test('two', async ({ page }) => {}); - > 4 │ }); - │ ^^ - 5 │ - - i Focused tests should not be committed to version control as they prevent other tests from running. - - i Remove the .only annotation to run all tests. - - -``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightFocusedTest/invalid/describe-parallel-only.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightFocusedTest/invalid/describe-parallel-only.js deleted file mode 100644 index 4c2e33dca173..000000000000 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightFocusedTest/invalid/describe-parallel-only.js +++ /dev/null @@ -1,5 +0,0 @@ -test.describe.parallel.only('focus two tests in parallel mode', () => { - test('one', async ({ page }) => {}); - test('two', async ({ page }) => {}); -}); - diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightFocusedTest/invalid/describe-parallel-only.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightFocusedTest/invalid/describe-parallel-only.js.snap deleted file mode 100644 index 520df026971a..000000000000 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightFocusedTest/invalid/describe-parallel-only.js.snap +++ /dev/null @@ -1,35 +0,0 @@ ---- -source: crates/biome_js_analyze/tests/spec_tests.rs -assertion_line: 152 -expression: describe-parallel-only.js ---- -# Input -```js -test.describe.parallel.only('focus two tests in parallel mode', () => { - test('one', async ({ page }) => {}); - test('two', async ({ page }) => {}); -}); - - -``` - -# Diagnostics -``` -describe-parallel-only.js:1:1 lint/nursery/noPlaywrightFocusedTest ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - i Unexpected focused test using .only annotation. - - > 1 │ test.describe.parallel.only('focus two tests in parallel mode', () => { - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - > 2 │ test('one', async ({ page }) => {}); - > 3 │ test('two', async ({ page }) => {}); - > 4 │ }); - │ ^^ - 5 │ - - i Focused tests should not be committed to version control as they prevent other tests from running. - - i Remove the .only annotation to run all tests. - - -``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightFocusedTest/invalid/test-only.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightFocusedTest/invalid/test-only.js deleted file mode 100644 index 5bace96b9226..000000000000 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightFocusedTest/invalid/test-only.js +++ /dev/null @@ -1,2 +0,0 @@ -test.only('focus this test', async ({ page }) => {}); - diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightFocusedTest/invalid/test-only.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightFocusedTest/invalid/test-only.js.snap deleted file mode 100644 index 3764bef27690..000000000000 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightFocusedTest/invalid/test-only.js.snap +++ /dev/null @@ -1,28 +0,0 @@ ---- -source: crates/biome_js_analyze/tests/spec_tests.rs -assertion_line: 152 -expression: test-only.js ---- -# Input -```js -test.only('focus this test', async ({ page }) => {}); - - -``` - -# Diagnostics -``` -test-only.js:1:1 lint/nursery/noPlaywrightFocusedTest ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - i Unexpected focused test using .only annotation. - - > 1 │ test.only('focus this test', async ({ page }) => {}); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 2 │ - - i Focused tests should not be committed to version control as they prevent other tests from running. - - i Remove the .only annotation to run all tests. - - -``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/bracket-notation-methods.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/bracket-notation-methods.js new file mode 100644 index 000000000000..462953fb6af6 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/bracket-notation-methods.js @@ -0,0 +1,12 @@ +// Test bracket notation for methods with force option +await page.locator("button")["click"]({ force: true }); + +await page.locator("button")[`click`]({ force: true }); + +await page.locator("input")["fill"]("text", { force: true }); + +await page.locator("input")[`fill`]("text", { force: true }); + +await page.locator("checkbox")["check"]({ force: true }); + +await page.locator("checkbox")[`check`]({ force: true }); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/bracket-notation-methods.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/bracket-notation-methods.js.snap new file mode 100644 index 000000000000..21a6216032e8 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/bracket-notation-methods.js.snap @@ -0,0 +1,20 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: bracket-notation-methods.js +--- +# Input +```js +// Test bracket notation for methods with force option +await page.locator("button")["click"]({ force: true }); + +await page.locator("button")[`click`]({ force: true }); + +await page.locator("input")["fill"]("text", { force: true }); + +await page.locator("input")[`fill`]("text", { force: true }); + +await page.locator("checkbox")["check"]({ force: true }); + +await page.locator("checkbox")[`check`]({ force: true }); + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/force-string-literal.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/force-string-literal.js new file mode 100644 index 000000000000..ade21bf11b79 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/force-string-literal.js @@ -0,0 +1,4 @@ +// Test string literal syntax for force option +await page.locator('button').click({ "force": true }); + +await page.locator('input').fill('text', { 'force': true }); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/force-string-literal.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/force-string-literal.js.snap new file mode 100644 index 000000000000..8e9978d46fc8 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/force-string-literal.js.snap @@ -0,0 +1,49 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: force-string-literal.js +--- +# Input +```js +// Test string literal syntax for force option +await page.locator('button').click({ "force": true }); + +await page.locator('input').fill('text', { 'force': true }); + +``` + +# Diagnostics +``` +force-string-literal.js:2:7 lint/nursery/noPlaywrightForceOption ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected use of { force: true } option. + + 1 │ // Test string literal syntax for force option + > 2 │ await page.locator('button').click({ "force": true }); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 3 │ + 4 │ await page.locator('input').fill('text', { 'force': true }); + + i The force option bypasses actionability checks and can lead to unreliable tests. + + i Fix the underlying issue instead of forcing the action. + + +``` + +``` +force-string-literal.js:4:7 lint/nursery/noPlaywrightForceOption ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected use of { force: true } option. + + 2 │ await page.locator('button').click({ "force": true }); + 3 │ + > 4 │ await page.locator('input').fill('text', { 'force': true }); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 5 │ + + i The force option bypasses actionability checks and can lead to unreliable tests. + + i Fix the underlying issue instead of forcing the action. + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/select-option-force.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/select-option-force.js new file mode 100644 index 000000000000..dd100e200c49 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/select-option-force.js @@ -0,0 +1,6 @@ +// Test selectOption with force option (it has different signature with first options argument) +await page.locator("select").selectOption({ label: "Blue" }, { force: true }); + +await page.locator("select").selectOption("value", { force: true }); + +await page.locator("select").selectOption(["option1", "option2"], { force: true }); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/select-option-force.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/select-option-force.js.snap new file mode 100644 index 000000000000..b43e8a851897 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/select-option-force.js.snap @@ -0,0 +1,70 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: select-option-force.js +--- +# Input +```js +// Test selectOption with force option (it has different signature with first options argument) +await page.locator("select").selectOption({ label: "Blue" }, { force: true }); + +await page.locator("select").selectOption("value", { force: true }); + +await page.locator("select").selectOption(["option1", "option2"], { force: true }); + +``` + +# Diagnostics +``` +select-option-force.js:2:7 lint/nursery/noPlaywrightForceOption ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected use of { force: true } option. + + 1 │ // Test selectOption with force option (it has different signature with first options argument) + > 2 │ await page.locator("select").selectOption({ label: "Blue" }, { force: true }); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 3 │ + 4 │ await page.locator("select").selectOption("value", { force: true }); + + i The force option bypasses actionability checks and can lead to unreliable tests. + + i Fix the underlying issue instead of forcing the action. + + +``` + +``` +select-option-force.js:4:7 lint/nursery/noPlaywrightForceOption ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected use of { force: true } option. + + 2 │ await page.locator("select").selectOption({ label: "Blue" }, { force: true }); + 3 │ + > 4 │ await page.locator("select").selectOption("value", { force: true }); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 5 │ + 6 │ await page.locator("select").selectOption(["option1", "option2"], { force: true }); + + i The force option bypasses actionability checks and can lead to unreliable tests. + + i Fix the underlying issue instead of forcing the action. + + +``` + +``` +select-option-force.js:6:7 lint/nursery/noPlaywrightForceOption ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected use of { force: true } option. + + 4 │ await page.locator("select").selectOption("value", { force: true }); + 5 │ + > 6 │ await page.locator("select").selectOption(["option1", "option2"], { force: true }); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 7 │ + + i The force option bypasses actionability checks and can lead to unreliable tests. + + i Fix the underlying issue instead of forcing the action. + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/all-methods.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/all-methods.js new file mode 100644 index 000000000000..5b2dd92bce72 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/all-methods.js @@ -0,0 +1,14 @@ +// Test all methods that support networkidle option +page.waitForLoadState("networkidle"); + +page.waitForURL("http://example.com", { waitUntil: "networkidle" }); + +page.goto("http://example.com", { waitUntil: "networkidle" }); + +page.reload({ waitUntil: "networkidle" }); + +page.setContent("", { waitUntil: "networkidle" }); + +page.goBack({ waitUntil: "networkidle" }); + +page.goForward({ waitUntil: "networkidle" }); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/all-methods.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/all-methods.js.snap new file mode 100644 index 000000000000..098e3a07b78b --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/all-methods.js.snap @@ -0,0 +1,154 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: all-methods.js +--- +# Input +```js +// Test all methods that support networkidle option +page.waitForLoadState("networkidle"); + +page.waitForURL("http://example.com", { waitUntil: "networkidle" }); + +page.goto("http://example.com", { waitUntil: "networkidle" }); + +page.reload({ waitUntil: "networkidle" }); + +page.setContent("", { waitUntil: "networkidle" }); + +page.goBack({ waitUntil: "networkidle" }); + +page.goForward({ waitUntil: "networkidle" }); + +``` + +# Diagnostics +``` +all-methods.js:2:1 lint/nursery/noPlaywrightNetworkidle ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected use of networkidle option. + + 1 │ // Test all methods that support networkidle option + > 2 │ page.waitForLoadState("networkidle"); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 3 │ + 4 │ page.waitForURL("http://example.com", { waitUntil: "networkidle" }); + + i The networkidle event is unreliable and can lead to flaky tests. + + i Use web-first assertions or wait for specific elements instead. + + +``` + +``` +all-methods.js:4:1 lint/nursery/noPlaywrightNetworkidle ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected use of networkidle option. + + 2 │ page.waitForLoadState("networkidle"); + 3 │ + > 4 │ page.waitForURL("http://example.com", { waitUntil: "networkidle" }); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 5 │ + 6 │ page.goto("http://example.com", { waitUntil: "networkidle" }); + + i The networkidle event is unreliable and can lead to flaky tests. + + i Use web-first assertions or wait for specific elements instead. + + +``` + +``` +all-methods.js:6:1 lint/nursery/noPlaywrightNetworkidle ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected use of networkidle option. + + 4 │ page.waitForURL("http://example.com", { waitUntil: "networkidle" }); + 5 │ + > 6 │ page.goto("http://example.com", { waitUntil: "networkidle" }); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 7 │ + 8 │ page.reload({ waitUntil: "networkidle" }); + + i The networkidle event is unreliable and can lead to flaky tests. + + i Use web-first assertions or wait for specific elements instead. + + +``` + +``` +all-methods.js:8:1 lint/nursery/noPlaywrightNetworkidle ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected use of networkidle option. + + 6 │ page.goto("http://example.com", { waitUntil: "networkidle" }); + 7 │ + > 8 │ page.reload({ waitUntil: "networkidle" }); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 9 │ + 10 │ page.setContent("", { waitUntil: "networkidle" }); + + i The networkidle event is unreliable and can lead to flaky tests. + + i Use web-first assertions or wait for specific elements instead. + + +``` + +``` +all-methods.js:10:1 lint/nursery/noPlaywrightNetworkidle ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected use of networkidle option. + + 8 │ page.reload({ waitUntil: "networkidle" }); + 9 │ + > 10 │ page.setContent("", { waitUntil: "networkidle" }); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 11 │ + 12 │ page.goBack({ waitUntil: "networkidle" }); + + i The networkidle event is unreliable and can lead to flaky tests. + + i Use web-first assertions or wait for specific elements instead. + + +``` + +``` +all-methods.js:12:1 lint/nursery/noPlaywrightNetworkidle ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected use of networkidle option. + + 10 │ page.setContent("", { waitUntil: "networkidle" }); + 11 │ + > 12 │ page.goBack({ waitUntil: "networkidle" }); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 13 │ + 14 │ page.goForward({ waitUntil: "networkidle" }); + + i The networkidle event is unreliable and can lead to flaky tests. + + i Use web-first assertions or wait for specific elements instead. + + +``` + +``` +all-methods.js:14:1 lint/nursery/noPlaywrightNetworkidle ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected use of networkidle option. + + 12 │ page.goBack({ waitUntil: "networkidle" }); + 13 │ + > 14 │ page.goForward({ waitUntil: "networkidle" }); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 15 │ + + i The networkidle event is unreliable and can lead to flaky tests. + + i Use web-first assertions or wait for specific elements instead. + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/bracket-notation.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/bracket-notation.js new file mode 100644 index 000000000000..19e05b262f2f --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/bracket-notation.js @@ -0,0 +1,10 @@ +// Test bracket notation for networkidle option +page["waitForLoadState"]("networkidle"); + +page[`waitForLoadState`]("networkidle"); + +page["waitForURL"](url, { waitUntil: "networkidle" }); + +page[`goto`](url, { waitUntil: "networkidle" }); + +page["reload"](url, { waitUntil: "networkidle" }); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/bracket-notation.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/bracket-notation.js.snap new file mode 100644 index 000000000000..c43d68f11769 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/bracket-notation.js.snap @@ -0,0 +1,18 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: bracket-notation.js +--- +# Input +```js +// Test bracket notation for networkidle option +page["waitForLoadState"]("networkidle"); + +page[`waitForLoadState`]("networkidle"); + +page["waitForURL"](url, { waitUntil: "networkidle" }); + +page[`goto`](url, { waitUntil: "networkidle" }); + +page["reload"](url, { waitUntil: "networkidle" }); + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/valid/other-wait-options.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/valid/other-wait-options.js new file mode 100644 index 000000000000..e89b09c78025 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/valid/other-wait-options.js @@ -0,0 +1,12 @@ +// Valid: using other waitUntil options +page.waitForLoadState(); + +page.waitForLoadState("load"); + +page.waitForLoadState("domcontentloaded"); + +page.waitForURL(url, { waitUntil: "load" }); + +page.goto(url, { waitUntil: "domcontentloaded" }); + +page.reload({ waitUntil: "commit" }); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/valid/other-wait-options.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/valid/other-wait-options.js.snap new file mode 100644 index 000000000000..0afa5f259403 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/valid/other-wait-options.js.snap @@ -0,0 +1,20 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: other-wait-options.js +--- +# Input +```js +// Valid: using other waitUntil options +page.waitForLoadState(); + +page.waitForLoadState("load"); + +page.waitForLoadState("domcontentloaded"); + +page.waitForURL(url, { waitUntil: "load" }); + +page.goto(url, { waitUntil: "domcontentloaded" }); + +page.reload({ waitUntil: "commit" }); + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/bracket-notation.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/bracket-notation.js new file mode 100644 index 000000000000..e2a22a80b29f --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/bracket-notation.js @@ -0,0 +1,8 @@ +// Test bracket notation for page.pause() +await page["pause"](); + +await page[`pause`](); + +await this.page["pause"](); + +await this.page[`pause`](); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/bracket-notation.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/bracket-notation.js.snap new file mode 100644 index 000000000000..396085d351c7 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/bracket-notation.js.snap @@ -0,0 +1,16 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: bracket-notation.js +--- +# Input +```js +// Test bracket notation for page.pause() +await page["pause"](); + +await page[`pause`](); + +await this.page["pause"](); + +await this.page[`pause`](); + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/async.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/async.js.snap index 986a2444398c..1ebc7662028f 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/async.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/async.js.snap @@ -1,6 +1,5 @@ --- source: crates/biome_js_analyze/tests/spec_tests.rs -assertion_line: 152 expression: async.js --- # Input @@ -26,6 +25,8 @@ async.js:1:1 lint/nursery/noPlaywrightValidDescribeCallback ━━━━━━ │ ^^ 4 │ + i Describe blocks are meant to organize tests, not contain asynchronous logic. Async operations should be placed within individual test callbacks. + i Remove the async keyword from the describe callback. diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/has-params.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/has-params.js.snap index d1c8e565a3a5..d4170f203bcb 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/has-params.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/has-params.js.snap @@ -1,6 +1,5 @@ --- source: crates/biome_js_analyze/tests/spec_tests.rs -assertion_line: 152 expression: has-params.js --- # Input @@ -26,6 +25,8 @@ has-params.js:1:1 lint/nursery/noPlaywrightValidDescribeCallback ━━━━━ │ ^^ 4 │ + i Describe callbacks are invoked without arguments by the test framework. + i Remove parameters from the describe callback. diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/invalid/in-test.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/invalid/in-test.js.snap index d34e388d0e05..04a84ac482a3 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/invalid/in-test.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/invalid/in-test.js.snap @@ -1,6 +1,5 @@ --- source: crates/biome_js_analyze/tests/spec_tests.rs -assertion_line: 152 expression: in-test.js --- # Input @@ -17,7 +16,7 @@ test('wait', async ({ page }) => { ``` in-test.js:3:11 lint/nursery/noPlaywrightWaitForTimeout ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - i Unexpected use of page.waitForTimeout(). + i Unexpected use of waitForTimeout(). 1 │ test('wait', async ({ page }) => { 2 │ await page.click('button'); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/invalid/simple.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/invalid/simple.js.snap index 82397701d722..ed77b7947c17 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/invalid/simple.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/invalid/simple.js.snap @@ -1,6 +1,5 @@ --- source: crates/biome_js_analyze/tests/spec_tests.rs -assertion_line: 152 expression: simple.js --- # Input @@ -14,7 +13,7 @@ await page.waitForTimeout(5000); ``` simple.js:1:7 lint/nursery/noPlaywrightWaitForTimeout ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - i Unexpected use of page.waitForTimeout(). + i Unexpected use of waitForTimeout(). > 1 │ await page.waitForTimeout(5000); │ ^^^^^^^^^^^^^^^^^^^^^^^^^ From 69e6acfa2cb91d6f90cf978ba74c48c9870426ca Mon Sep 17 00:00:00 2001 From: Josh Delsman <12201+voxxit@users.noreply.github.com> Date: Sat, 18 Oct 2025 23:59:53 -0500 Subject: [PATCH 16/24] fix(biome_configuration): correct rule indices in linter rules This commit updates the indices of various lint rules in the `rules.rs` file to ensure proper alignment with the intended rule group structure. The changes involve shifting the indices for multiple rules related to Playwright and Vue, correcting their positions to maintain consistency in rule filtering. This fix aims to enhance the accuracy of the linter's functionality and ensure that all rules are correctly referenced. --- .../src/analyzer/linter/rules.rs | 132 +++++++++--------- 1 file changed, 66 insertions(+), 66 deletions(-) diff --git a/crates/biome_configuration/src/analyzer/linter/rules.rs b/crates/biome_configuration/src/analyzer/linter/rules.rs index 1034a3f52b28..ae18c93a9b93 100644 --- a/crates/biome_configuration/src/analyzer/linter/rules.rs +++ b/crates/biome_configuration/src/analyzer/linter/rules.rs @@ -4882,167 +4882,167 @@ impl RuleGroupExt for Nursery { if let Some(rule) = self.no_playwright_page_pause.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[15])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[14])); } if let Some(rule) = self.no_playwright_skipped_test.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[16])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[15])); } if let Some(rule) = self.no_playwright_useless_await.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[17])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[16])); } if let Some(rule) = self.no_playwright_valid_describe_callback.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[18])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[17])); } if let Some(rule) = self.no_playwright_wait_for_navigation.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[19])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[18])); } if let Some(rule) = self.no_playwright_wait_for_selector.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[19])); } if let Some(rule) = self.no_playwright_wait_for_timeout.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20])); } if let Some(rule) = self.no_qwik_use_visible_task.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21])); } if let Some(rule) = self.no_react_forward_ref.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22])); } if let Some(rule) = self.no_secrets.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23])); } if let Some(rule) = self.no_shadow.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[25])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24])); } if let Some(rule) = self.no_unnecessary_conditions.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[26])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[25])); } if let Some(rule) = self.no_unresolved_imports.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[27])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[26])); } if let Some(rule) = self.no_unused_expressions.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[28])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[27])); } if let Some(rule) = self.no_useless_catch_binding.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[29])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[28])); } if let Some(rule) = self.no_useless_undefined.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[30])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[29])); } if let Some(rule) = self.no_vue_data_object_declaration.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[31])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[30])); } if let Some(rule) = self.no_vue_duplicate_keys.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[32])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[31])); } if let Some(rule) = self.no_vue_reserved_keys.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[33])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[32])); } if let Some(rule) = self.no_vue_reserved_props.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[34])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[33])); } if let Some(rule) = self.use_anchor_href.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[35])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[34])); } if let Some(rule) = self.use_consistent_arrow_return.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[36])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[35])); } if let Some(rule) = self.use_consistent_type_definitions.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[37])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[36])); } if let Some(rule) = self.use_deprecated_date.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[38])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[37])); } if let Some(rule) = self.use_exhaustive_switch_cases.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[39])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[38])); } if let Some(rule) = self.use_explicit_type.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[40])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[39])); } if let Some(rule) = self.use_image_size.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[41])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[40])); } if let Some(rule) = self.use_max_params.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[42])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[41])); } if let Some(rule) = self.use_qwik_classlist.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[43])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[42])); } if let Some(rule) = self.use_qwik_method_usage.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[44])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[43])); } if let Some(rule) = self.use_qwik_valid_lexical_scope.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[45])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[44])); } if let Some(rule) = self.use_react_function_components.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[46])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[45])); } if let Some(rule) = self.use_sorted_classes.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[47])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[46])); } if let Some(rule) = self.use_vue_multi_word_component_names.as_ref() && rule.is_enabled() @@ -5126,167 +5126,167 @@ impl RuleGroupExt for Nursery { if let Some(rule) = self.no_playwright_page_pause.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[15])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[14])); } if let Some(rule) = self.no_playwright_skipped_test.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[16])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[15])); } if let Some(rule) = self.no_playwright_useless_await.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[17])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[16])); } if let Some(rule) = self.no_playwright_valid_describe_callback.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[18])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[17])); } if let Some(rule) = self.no_playwright_wait_for_navigation.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[19])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[18])); } if let Some(rule) = self.no_playwright_wait_for_selector.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[19])); } if let Some(rule) = self.no_playwright_wait_for_timeout.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20])); } if let Some(rule) = self.no_qwik_use_visible_task.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21])); } if let Some(rule) = self.no_react_forward_ref.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22])); } if let Some(rule) = self.no_secrets.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23])); } if let Some(rule) = self.no_shadow.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[25])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24])); } if let Some(rule) = self.no_unnecessary_conditions.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[26])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[25])); } if let Some(rule) = self.no_unresolved_imports.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[27])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[26])); } if let Some(rule) = self.no_unused_expressions.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[28])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[27])); } if let Some(rule) = self.no_useless_catch_binding.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[29])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[28])); } if let Some(rule) = self.no_useless_undefined.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[30])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[29])); } if let Some(rule) = self.no_vue_data_object_declaration.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[31])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[30])); } if let Some(rule) = self.no_vue_duplicate_keys.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[32])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[31])); } if let Some(rule) = self.no_vue_reserved_keys.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[33])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[32])); } if let Some(rule) = self.no_vue_reserved_props.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[34])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[33])); } if let Some(rule) = self.use_anchor_href.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[35])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[34])); } if let Some(rule) = self.use_consistent_arrow_return.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[36])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[35])); } if let Some(rule) = self.use_consistent_type_definitions.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[37])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[36])); } if let Some(rule) = self.use_deprecated_date.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[38])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[37])); } if let Some(rule) = self.use_exhaustive_switch_cases.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[39])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[38])); } if let Some(rule) = self.use_explicit_type.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[40])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[39])); } if let Some(rule) = self.use_image_size.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[41])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[40])); } if let Some(rule) = self.use_max_params.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[42])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[41])); } if let Some(rule) = self.use_qwik_classlist.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[43])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[42])); } if let Some(rule) = self.use_qwik_method_usage.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[44])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[43])); } if let Some(rule) = self.use_qwik_valid_lexical_scope.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[45])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[44])); } if let Some(rule) = self.use_react_function_components.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[46])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[45])); } if let Some(rule) = self.use_sorted_classes.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[47])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[46])); } if let Some(rule) = self.use_vue_multi_word_component_names.as_ref() && rule.is_disabled() From 55697e8f9315a48a988234b3213496525d1bebed Mon Sep 17 00:00:00 2001 From: Josh Delsman <12201+voxxit@users.noreply.github.com> Date: Sun, 19 Oct 2025 00:01:52 -0500 Subject: [PATCH 17/24] refactor(biome_jsonrpc): remove noPlaywrightFocusedTest rule and update RuleDomain This commit removes the `noPlaywrightFocusedTest` rule and its associated configurations from the JSON schema and TypeScript definitions, streamlining the linting rules for Playwright. Additionally, the `RuleDomain` type has been updated to include `playwright`, enhancing the categorization of Playwright-specific rules. These changes aim to improve the clarity and maintainability of the codebase. --- .../@biomejs/backend-jsonrpc/src/workspace.ts | 22 +---------- .../@biomejs/biome/configuration_schema.json | 37 +++---------------- 2 files changed, 7 insertions(+), 52 deletions(-) diff --git a/packages/@biomejs/backend-jsonrpc/src/workspace.ts b/packages/@biomejs/backend-jsonrpc/src/workspace.ts index d8e0f2b9c938..d5d572b125ae 100644 --- a/packages/@biomejs/backend-jsonrpc/src/workspace.ts +++ b/packages/@biomejs/backend-jsonrpc/src/workspace.ts @@ -906,11 +906,11 @@ export type RuleDomain = | "test" | "solid" | "next" + | "playwright" | "qwik" | "vue" | "project" - | "tailwind" - | "playwright"; + | "tailwind"; export type RuleDomainValue = "all" | "none" | "recommended"; export type SeverityOrGroup_for_A11y = GroupPlainConfiguration | A11y; export type SeverityOrGroup_for_Complexity = @@ -1674,10 +1674,6 @@ export interface Nursery { * Disallow usage of page.$eval() and page.$$eval(). */ noPlaywrightEval?: RuleConfiguration_for_NoPlaywrightEvalOptions; - /** - * Disallow usage of .only annotation in Playwright tests. - */ - noPlaywrightFocusedTest?: RuleConfiguration_for_NoPlaywrightFocusedTestOptions; /** * Disallow usage of the { force: true } option. */ @@ -3099,9 +3095,6 @@ export type RuleConfiguration_for_NoPlaywrightElementHandleOptions = export type RuleConfiguration_for_NoPlaywrightEvalOptions = | RulePlainConfiguration | RuleWithOptions_for_NoPlaywrightEvalOptions; -export type RuleConfiguration_for_NoPlaywrightFocusedTestOptions = - | RulePlainConfiguration - | RuleWithOptions_for_NoPlaywrightFocusedTestOptions; export type RuleConfiguration_for_NoPlaywrightForceOptionOptions = | RulePlainConfiguration | RuleWithOptions_for_NoPlaywrightForceOptionOptions; @@ -5627,16 +5620,6 @@ export interface RuleWithOptions_for_NoPlaywrightEvalOptions { */ options: NoPlaywrightEvalOptions; } -export interface RuleWithOptions_for_NoPlaywrightFocusedTestOptions { - /** - * The severity of the emitted diagnostics by the rule - */ - level: RulePlainConfiguration; - /** - * Rule's options - */ - options: NoPlaywrightFocusedTestOptions; -} export interface RuleWithOptions_for_NoPlaywrightForceOptionOptions { /** * The severity of the emitted diagnostics by the rule @@ -8463,7 +8446,6 @@ export interface NoNextAsyncClientComponentOptions {} export interface NoNonNullAssertedOptionalChainOptions {} export interface NoPlaywrightElementHandleOptions {} export interface NoPlaywrightEvalOptions {} -export interface NoPlaywrightFocusedTestOptions {} export interface NoPlaywrightForceOptionOptions {} export interface NoPlaywrightNetworkidleOptions {} export interface NoPlaywrightPagePauseOptions {} diff --git a/packages/@biomejs/biome/configuration_schema.json b/packages/@biomejs/biome/configuration_schema.json index 9d71ad537b1e..0cfcc4857b75 100644 --- a/packages/@biomejs/biome/configuration_schema.json +++ b/packages/@biomejs/biome/configuration_schema.json @@ -3947,16 +3947,6 @@ "type": "object", "additionalProperties": false }, - "NoPlaywrightFocusedTestConfiguration": { - "anyOf": [ - { "$ref": "#/definitions/RulePlainConfiguration" }, - { "$ref": "#/definitions/RuleWithNoPlaywrightFocusedTestOptions" } - ] - }, - "NoPlaywrightFocusedTestOptions": { - "type": "object", - "additionalProperties": false - }, "NoPlaywrightForceOptionConfiguration": { "anyOf": [ { "$ref": "#/definitions/RulePlainConfiguration" }, @@ -5224,13 +5214,6 @@ { "type": "null" } ] }, - "noPlaywrightFocusedTest": { - "description": "Disallow usage of .only annotation in Playwright tests.", - "anyOf": [ - { "$ref": "#/definitions/NoPlaywrightFocusedTestConfiguration" }, - { "type": "null" } - ] - }, "noPlaywrightForceOption": { "description": "Disallow usage of the { force: true } option.", "anyOf": [ @@ -6028,6 +6011,11 @@ "type": "string", "enum": ["next"] }, + { + "description": "Playwright testing library rules", + "type": "string", + "enum": ["playwright"] + }, { "description": "Qwik framework rules", "type": "string", @@ -8466,21 +8454,6 @@ }, "additionalProperties": false }, - "RuleWithNoPlaywrightFocusedTestOptions": { - "type": "object", - "required": ["level"], - "properties": { - "level": { - "description": "The severity of the emitted diagnostics by the rule", - "allOf": [{ "$ref": "#/definitions/RulePlainConfiguration" }] - }, - "options": { - "description": "Rule's options", - "allOf": [{ "$ref": "#/definitions/NoPlaywrightFocusedTestOptions" }] - } - }, - "additionalProperties": false - }, "RuleWithNoPlaywrightForceOptionOptions": { "type": "object", "required": ["level"], From 95b7c54dba9bceb76860fa8df1edc393ce758848 Mon Sep 17 00:00:00 2001 From: Josh Delsman <12201+voxxit@users.noreply.github.com> Date: Sun, 19 Oct 2025 00:11:45 -0500 Subject: [PATCH 18/24] refactor(biome_js_analyze): update Playwright lint rules to use TokenText for state This commit modifies several Playwright lint rules to utilize `TokenText` as the state type instead of a unit type. The changes enhance the diagnostic messages by including the specific receiver in the output, improving clarity for users. The affected rules include `noPlaywrightPagePause`, `noPlaywrightWaitForNavigation`, `noPlaywrightWaitForSelector`, and `noPlaywrightWaitForTimeout`. Additionally, the `noPlaywrightValidDescribeCallback` rule has been updated to handle new invalid reasons for missing or non-function callbacks. These improvements aim to provide more informative feedback during linting. --- .../lint/nursery/no_playwright_page_pause.rs | 13 +- .../nursery/no_playwright_useless_await.rs | 76 ++++++---- .../no_playwright_valid_describe_callback.rs | 26 +++- .../no_playwright_wait_for_navigation.rs | 13 +- .../no_playwright_wait_for_selector.rs | 13 +- .../nursery/no_playwright_wait_for_timeout.rs | 13 +- .../invalid/frame.js.snap | 5 +- .../invalid/expect-mixed-chains.js | 9 ++ .../invalid/expect-mixed-chains.js.snap | 130 +++++++++++++++++ .../valid/expect-with-async-modifiers.js | 18 +++ .../valid/expect-with-async-modifiers.js.snap | 26 ++++ .../invalid/missing-callback.js | 4 + .../invalid/missing-callback.js.snap | 49 +++++++ .../invalid/not-function.js | 12 ++ .../invalid/not-function.js.snap | 133 ++++++++++++++++++ .../invalid/in-test.js.snap | 4 +- .../invalid/simple.js.snap | 4 +- 17 files changed, 488 insertions(+), 60 deletions(-) create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/expect-mixed-chains.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/expect-mixed-chains.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/valid/expect-with-async-modifiers.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/valid/expect-with-async-modifiers.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/missing-callback.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/missing-callback.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/not-function.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/not-function.js.snap diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_page_pause.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_page_pause.rs index 3b0f4ae702f6..e07a7e288213 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_playwright_page_pause.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_playwright_page_pause.rs @@ -3,7 +3,7 @@ use biome_analyze::{ }; use biome_console::markup; use biome_js_syntax::{JsCallExpression, JsStaticMemberExpression}; -use biome_rowan::AstNode; +use biome_rowan::{AstNode, TokenText}; declare_lint_rule! { /// Disallow using `page.pause()`. @@ -48,7 +48,7 @@ declare_lint_rule! { impl Rule for NoPlaywrightPagePause { type Query = Ast; - type State = (); + type State = TokenText; type Signals = Option; type Options = (); @@ -92,24 +92,25 @@ impl Rule for NoPlaywrightPagePause { || object_text.ends_with("Page") || object_text.ends_with("Frame") { - Some(()) + Some(object_text) } else { None } } - fn diagnostic(ctx: &RuleContext, _: &Self::State) -> Option { + fn diagnostic(ctx: &RuleContext, state: &Self::State) -> Option { let node = ctx.query(); + let receiver = state.text(); Some( RuleDiagnostic::new( rule_category!(), node.range(), markup! { - "Unexpected use of ""page.pause()""." + "Unexpected use of "{receiver}".pause()""." }, ) .note(markup! { - "page.pause()"" is a debugging utility and should not be committed to version control." + {receiver}".pause()"" is a debugging utility and should not be committed to version control." }) .note(markup! { "Remove the ""pause()"" call or use a proper debugging strategy." diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_useless_await.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_useless_await.rs index fa32cb103a59..8d8ef0b11ce3 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_playwright_useless_await.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_playwright_useless_await.rs @@ -5,7 +5,7 @@ use biome_analyze::{ use biome_console::markup; use biome_diagnostics::Applicability; use biome_js_syntax::{ - AnyJsExpression, JsAwaitExpression, JsCallExpression, JsStaticMemberExpression, + AnyJsExpression, JsAwaitExpression, JsCallExpression, }; use biome_rowan::{AstNode, BatchMutationExt, TokenText}; @@ -306,34 +306,60 @@ fn is_sync_expect_call(call_expr: &JsCallExpression) -> bool { } fn has_async_modifier(expect_call: &JsCallExpression, final_call: &JsCallExpression) -> bool { - // Walk the chain from expect_call to final_call looking for "poll", "resolves", "rejects" - let mut current = final_call.syntax().clone(); - let expect_syntax = expect_call.syntax(); + // Walk the chain from the final call down through the object/callee chain + // to the expect call, looking for "poll", "resolves", "rejects" - while current != *expect_syntax { - if let Some(member) = JsStaticMemberExpression::cast_ref(¤t) { - if let Ok(member_name) = member.member() - && let Some(name) = member_name.as_js_name() - && let Ok(token) = name.value_token() - { - let text = token.text_trimmed(); - if text == "poll" || text == "resolves" || text == "rejects" { - return true; + // Start from the final call's callee (the member expression) + let final_callee = match final_call.callee().ok() { + Some(AnyJsExpression::JsStaticMemberExpression(member)) => member, + _ => return false, + }; + + // Walk down the object chain + let mut current_expr = final_callee.object().ok(); + + while let Some(expr) = current_expr { + match expr { + // If we find a member expression, check if it's an async modifier + AnyJsExpression::JsStaticMemberExpression(member) => { + if let Ok(member_name) = member.member() + && let Some(name) = member_name.as_js_name() + && let Ok(token) = name.value_token() + { + let text = token.text_trimmed(); + if text == "resolves" || text == "rejects" { + return true; + } } + // Continue walking down + current_expr = member.object().ok(); } - if let Some(parent) = member.syntax().parent() { - current = parent; - } else { - break; - } - } else if let Some(call) = JsCallExpression::cast_ref(¤t) { - if let Some(parent) = call.syntax().parent() { - current = parent; - } else { - break; + // If we find a call expression, check if it's the expect call + AnyJsExpression::JsCallExpression(call) => { + // Check if this is the expect call we started from + if call.syntax() == expect_call.syntax() { + // Reached the expect call, no async modifiers found + return false; + } + + // Check if it's expect.poll() by examining the callee + if let Ok(AnyJsExpression::JsStaticMemberExpression(callee_member)) = call.callee() + && let Ok(member_name) = callee_member.member() + && let Some(name) = member_name.as_js_name() + && let Ok(token) = name.value_token() + && token.text_trimmed() == "poll" + { + return true; + } + + // Continue walking down the callee chain + if let Ok(callee) = call.callee() { + current_expr = Some(callee); + } else { + break; + } } - } else { - break; + _ => break, } } diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_valid_describe_callback.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_valid_describe_callback.rs index 9afa701560c3..d70fb15923fd 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_playwright_valid_describe_callback.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_playwright_valid_describe_callback.rs @@ -55,6 +55,8 @@ declare_lint_rule! { pub enum InvalidReason { Async, HasParameters, + MissingCallback, + NotFunction, } impl Rule for NoPlaywrightValidDescribeCallback { @@ -101,10 +103,16 @@ impl Rule for NoPlaywrightValidDescribeCallback { // Get the callback argument (should be the second argument for describe calls) let args = call_expr.arguments().ok()?; - let [_, Some(callback_arg)] = args.get_arguments_by_index([0, 1]) else { - return None; + let [_, callback_arg] = args.get_arguments_by_index([0, 1]); + + // Check if callback is missing + let Some(callback_arg) = callback_arg else { + return Some(InvalidReason::MissingCallback); + }; + + let Some(callback_expr) = callback_arg.as_any_js_expression() else { + return Some(InvalidReason::NotFunction); }; - let callback_expr = callback_arg.as_any_js_expression()?; // Check if it's a function match callback_expr { @@ -140,7 +148,7 @@ impl Rule for NoPlaywrightValidDescribeCallback { return Some(InvalidReason::HasParameters); } } - _ => return None, // Not a function, but we won't report this + _ => return Some(InvalidReason::NotFunction), } None @@ -160,6 +168,16 @@ impl Rule for NoPlaywrightValidDescribeCallback { markup! { "Describe callbacks are invoked without arguments by the test framework." }, markup! { "Remove parameters from the describe callback." }, ), + InvalidReason::MissingCallback => ( + markup! { "Describe requires a callback function." }, + markup! { "The second argument to describe must be a function that contains the test definitions." }, + markup! { "Add a callback function as the second argument to describe." }, + ), + InvalidReason::NotFunction => ( + markup! { "Describe callback must be a function." }, + markup! { "The second argument to describe must be a function, not a ""string"", ""number"", ""object"", or other type." }, + markup! { "Replace the callback with a function expression or arrow function." }, + ), }; Some( diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_navigation.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_navigation.rs index a0ca24ab4390..5306d844cb2b 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_navigation.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_navigation.rs @@ -3,7 +3,7 @@ use biome_analyze::{ }; use biome_console::markup; use biome_js_syntax::{JsCallExpression, JsStaticMemberExpression}; -use biome_rowan::AstNode; +use biome_rowan::{AstNode, TokenText}; declare_lint_rule! { /// Disallow using `page.waitForNavigation()`. @@ -50,7 +50,7 @@ declare_lint_rule! { impl Rule for NoPlaywrightWaitForNavigation { type Query = Ast; - type State = (); + type State = TokenText; type Signals = Option; type Options = (); @@ -87,24 +87,25 @@ impl Rule for NoPlaywrightWaitForNavigation { || object_text.ends_with("Page") || object_text.ends_with("Frame") { - Some(()) + Some(object_text) } else { None } } - fn diagnostic(ctx: &RuleContext, _: &Self::State) -> Option { + fn diagnostic(ctx: &RuleContext, state: &Self::State) -> Option { let node = ctx.query(); + let receiver = state.text(); Some( RuleDiagnostic::new( rule_category!(), node.range(), markup! { - "Unexpected use of ""page.waitForNavigation()""." + "Unexpected use of "{receiver}".waitForNavigation()""." }, ) .note(markup! { - "waitForNavigation()"" is deprecated. Use ""page.waitForURL()"" or ""page.waitForLoadState()"" instead." + "waitForNavigation()"" is deprecated. Use "{receiver}".waitForURL()"" or "{receiver}".waitForLoadState()"" instead." }) .note(markup! { "These alternatives are more reliable and provide better control over navigation waiting." diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_selector.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_selector.rs index cff4186e8da2..15df14a53275 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_selector.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_selector.rs @@ -3,7 +3,7 @@ use biome_analyze::{ }; use biome_console::markup; use biome_js_syntax::{JsCallExpression, JsStaticMemberExpression}; -use biome_rowan::AstNode; +use biome_rowan::{AstNode, TokenText}; declare_lint_rule! { /// Disallow using `page.waitForSelector()`. @@ -51,7 +51,7 @@ declare_lint_rule! { impl Rule for NoPlaywrightWaitForSelector { type Query = Ast; - type State = (); + type State = TokenText; type Signals = Option; type Options = (); @@ -88,24 +88,25 @@ impl Rule for NoPlaywrightWaitForSelector { || object_text.ends_with("Page") || object_text.ends_with("Frame") { - Some(()) + Some(object_text) } else { None } } - fn diagnostic(ctx: &RuleContext, _: &Self::State) -> Option { + fn diagnostic(ctx: &RuleContext, state: &Self::State) -> Option { let node = ctx.query(); + let receiver = state.text(); Some( RuleDiagnostic::new( rule_category!(), node.range(), markup! { - "Unexpected use of ""page.waitForSelector()""." + "Unexpected use of "{receiver}".waitForSelector()""." }, ) .note(markup! { - "Use locator-based ""page.locator()"" or ""page.getByRole()"" APIs instead." + "Use locator-based "{receiver}".locator()"" or "{receiver}".getByRole()"" APIs instead." }) .note(markup! { "Locators automatically wait for elements to be ready, making explicit waits unnecessary." diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_timeout.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_timeout.rs index 418867ce697f..5f2d5900c9bb 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_timeout.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_playwright_wait_for_timeout.rs @@ -3,7 +3,7 @@ use biome_analyze::{ }; use biome_console::markup; use biome_js_syntax::{JsCallExpression, JsStaticMemberExpression}; -use biome_rowan::AstNode; +use biome_rowan::{AstNode, TokenText}; declare_lint_rule! { /// Disallow using `page.waitForTimeout()`. @@ -50,7 +50,7 @@ declare_lint_rule! { impl Rule for NoPlaywrightWaitForTimeout { type Query = Ast; - type State = (); + type State = TokenText; type Signals = Option; type Options = (); @@ -87,24 +87,25 @@ impl Rule for NoPlaywrightWaitForTimeout { || object_text.ends_with("Page") || object_text.ends_with("Frame") { - Some(()) + Some(object_text) } else { None } } - fn diagnostic(ctx: &RuleContext, _: &Self::State) -> Option { + fn diagnostic(ctx: &RuleContext, state: &Self::State) -> Option { let node = ctx.query(); + let receiver = state.text(); Some( RuleDiagnostic::new( rule_category!(), node.range(), markup! { - "Unexpected use of ""waitForTimeout()""." + "Unexpected use of "{receiver}".waitForTimeout()""." }, ) .note(markup! { - "Prefer using built-in wait methods like ""waitForLoadState()"", ""waitForURL()"", or ""waitForFunction()"" instead." + "Prefer using built-in wait methods like "{receiver}".waitForLoadState()"", "{receiver}".waitForURL()"", or "{receiver}".waitForFunction()"" instead." }) .note(markup! { "Hardcoded timeouts are flaky and make tests slower. Use conditions that wait for specific events." diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/frame.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/frame.js.snap index 0011d4fb336d..6cb37b2e5caf 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/frame.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/frame.js.snap @@ -1,6 +1,5 @@ --- source: crates/biome_js_analyze/tests/spec_tests.rs -assertion_line: 152 expression: frame.js --- # Input @@ -15,14 +14,14 @@ await frame.pause(); ``` frame.js:2:7 lint/nursery/noPlaywrightPagePause ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - i Unexpected use of page.pause(). + i Unexpected use of frame.pause(). 1 │ const frame = page.frame('iframe'); > 2 │ await frame.pause(); │ ^^^^^^^^^^^^^ 3 │ - i page.pause() is a debugging utility and should not be committed to version control. + i frame.pause() is a debugging utility and should not be committed to version control. i Remove the pause() call or use a proper debugging strategy. diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/expect-mixed-chains.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/expect-mixed-chains.js new file mode 100644 index 000000000000..dbef0d6edca2 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/expect-mixed-chains.js @@ -0,0 +1,9 @@ +// Invalid: sync expect without async modifiers should trigger the rule +await expect(1).toBe(1); +await expect(value).toEqual(expectedValue); +await expect(str).toMatch(/pattern/); +await expect(arr).toHaveLength(3); + +// Invalid: expect.soft with sync matcher (soft doesn't make it async) +await expect.soft(value).toBe(123); + diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/expect-mixed-chains.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/expect-mixed-chains.js.snap new file mode 100644 index 000000000000..118e4e02c424 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/expect-mixed-chains.js.snap @@ -0,0 +1,130 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: expect-mixed-chains.js +--- +# Input +```js +// Invalid: sync expect without async modifiers should trigger the rule +await expect(1).toBe(1); +await expect(value).toEqual(expectedValue); +await expect(str).toMatch(/pattern/); +await expect(arr).toHaveLength(3); + +// Invalid: expect.soft with sync matcher (soft doesn't make it async) +await expect.soft(value).toBe(123); + + +``` + +# Diagnostics +``` +expect-mixed-chains.js:2:1 lint/nursery/noPlaywrightUselessAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unnecessary await expression. + + 1 │ // Invalid: sync expect without async modifiers should trigger the rule + > 2 │ await expect(1).toBe(1); + │ ^^^^^^^^^^^^^^^^^^^^^^^ + 3 │ await expect(value).toEqual(expectedValue); + 4 │ await expect(str).toMatch(/pattern/); + + i This method does not return a Promise. + + i Remove the await keyword. + + i Safe fix: Remove unnecessary await + + 2 │ await·expect(1).toBe(1); + │ ------ + +``` + +``` +expect-mixed-chains.js:3:1 lint/nursery/noPlaywrightUselessAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unnecessary await expression. + + 1 │ // Invalid: sync expect without async modifiers should trigger the rule + 2 │ await expect(1).toBe(1); + > 3 │ await expect(value).toEqual(expectedValue); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 4 │ await expect(str).toMatch(/pattern/); + 5 │ await expect(arr).toHaveLength(3); + + i This method does not return a Promise. + + i Remove the await keyword. + + i Safe fix: Remove unnecessary await + + 3 │ await·expect(value).toEqual(expectedValue); + │ ------ + +``` + +``` +expect-mixed-chains.js:4:1 lint/nursery/noPlaywrightUselessAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unnecessary await expression. + + 2 │ await expect(1).toBe(1); + 3 │ await expect(value).toEqual(expectedValue); + > 4 │ await expect(str).toMatch(/pattern/); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 5 │ await expect(arr).toHaveLength(3); + 6 │ + + i This method does not return a Promise. + + i Remove the await keyword. + + i Safe fix: Remove unnecessary await + + 4 │ await·expect(str).toMatch(/pattern/); + │ ------ + +``` + +``` +expect-mixed-chains.js:5:1 lint/nursery/noPlaywrightUselessAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unnecessary await expression. + + 3 │ await expect(value).toEqual(expectedValue); + 4 │ await expect(str).toMatch(/pattern/); + > 5 │ await expect(arr).toHaveLength(3); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 6 │ + 7 │ // Invalid: expect.soft with sync matcher (soft doesn't make it async) + + i This method does not return a Promise. + + i Remove the await keyword. + + i Safe fix: Remove unnecessary await + + 5 │ await·expect(arr).toHaveLength(3); + │ ------ + +``` + +``` +expect-mixed-chains.js:8:1 lint/nursery/noPlaywrightUselessAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unnecessary await expression. + + 7 │ // Invalid: expect.soft with sync matcher (soft doesn't make it async) + > 8 │ await expect.soft(value).toBe(123); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 9 │ + + i This method does not return a Promise. + + i Remove the await keyword. + + i Safe fix: Remove unnecessary await + + 8 │ await·expect.soft(value).toBe(123); + │ ------ + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/valid/expect-with-async-modifiers.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/valid/expect-with-async-modifiers.js new file mode 100644 index 000000000000..a06f2cff36f5 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/valid/expect-with-async-modifiers.js @@ -0,0 +1,18 @@ +// Valid: expect with .resolves modifier +await expect(promise).resolves.toBe(1); +await expect(fetchData()).resolves.toEqual({ foo: 'bar' }); +await expect(asyncOperation()).resolves.toBeDefined(); + +// Valid: expect with .rejects modifier +await expect(promise).rejects.toThrow(); +await expect(failingAsync()).rejects.toBeInstanceOf(Error); +await expect(badRequest()).rejects.toMatch('error'); + +// Valid: expect.poll with sync matchers +await expect.poll(() => getValue()).toBe(true); +await expect.poll(() => counter).toBeGreaterThan(5); + +// Valid: chained async modifiers +await expect(promise).resolves.not.toBe(null); +await expect(promise).rejects.not.toBeUndefined(); + diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/valid/expect-with-async-modifiers.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/valid/expect-with-async-modifiers.js.snap new file mode 100644 index 000000000000..f3c1a43e5eb2 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/valid/expect-with-async-modifiers.js.snap @@ -0,0 +1,26 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: expect-with-async-modifiers.js +--- +# Input +```js +// Valid: expect with .resolves modifier +await expect(promise).resolves.toBe(1); +await expect(fetchData()).resolves.toEqual({ foo: 'bar' }); +await expect(asyncOperation()).resolves.toBeDefined(); + +// Valid: expect with .rejects modifier +await expect(promise).rejects.toThrow(); +await expect(failingAsync()).rejects.toBeInstanceOf(Error); +await expect(badRequest()).rejects.toMatch('error'); + +// Valid: expect.poll with sync matchers +await expect.poll(() => getValue()).toBe(true); +await expect.poll(() => counter).toBeGreaterThan(5); + +// Valid: chained async modifiers +await expect(promise).resolves.not.toBe(null); +await expect(promise).rejects.not.toBeUndefined(); + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/missing-callback.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/missing-callback.js new file mode 100644 index 000000000000..f9b26f95fdd2 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/missing-callback.js @@ -0,0 +1,4 @@ +// Test missing callback +test.describe("foo"); + +describe("bar"); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/missing-callback.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/missing-callback.js.snap new file mode 100644 index 000000000000..3f0046beece2 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/missing-callback.js.snap @@ -0,0 +1,49 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: missing-callback.js +--- +# Input +```js +// Test missing callback +test.describe("foo"); + +describe("bar"); + +``` + +# Diagnostics +``` +missing-callback.js:2:1 lint/nursery/noPlaywrightValidDescribeCallback ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Describe requires a callback function. + + 1 │ // Test missing callback + > 2 │ test.describe("foo"); + │ ^^^^^^^^^^^^^^^^^^^^ + 3 │ + 4 │ describe("bar"); + + i The second argument to describe must be a function that contains the test definitions. + + i Add a callback function as the second argument to describe. + + +``` + +``` +missing-callback.js:4:1 lint/nursery/noPlaywrightValidDescribeCallback ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Describe requires a callback function. + + 2 │ test.describe("foo"); + 3 │ + > 4 │ describe("bar"); + │ ^^^^^^^^^^^^^^^ + 5 │ + + i The second argument to describe must be a function that contains the test definitions. + + i Add a callback function as the second argument to describe. + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/not-function.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/not-function.js new file mode 100644 index 000000000000..a1ef4d7ff456 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/not-function.js @@ -0,0 +1,12 @@ +// Test non-function callbacks +test.describe("foo", "foo2"); + +test.describe("bar", 42); + +test.describe("baz", { tag: ["@slow"] }); + +describe("qux", null); + +describe("suite", undefined); + +test.describe("another", someVariable); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/not-function.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/not-function.js.snap new file mode 100644 index 000000000000..67354d45db81 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/not-function.js.snap @@ -0,0 +1,133 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: not-function.js +--- +# Input +```js +// Test non-function callbacks +test.describe("foo", "foo2"); + +test.describe("bar", 42); + +test.describe("baz", { tag: ["@slow"] }); + +describe("qux", null); + +describe("suite", undefined); + +test.describe("another", someVariable); + +``` + +# Diagnostics +``` +not-function.js:2:1 lint/nursery/noPlaywrightValidDescribeCallback ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Describe callback must be a function. + + 1 │ // Test non-function callbacks + > 2 │ test.describe("foo", "foo2"); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 3 │ + 4 │ test.describe("bar", 42); + + i The second argument to describe must be a function, not a string, number, object, or other type. + + i Replace the callback with a function expression or arrow function. + + +``` + +``` +not-function.js:4:1 lint/nursery/noPlaywrightValidDescribeCallback ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Describe callback must be a function. + + 2 │ test.describe("foo", "foo2"); + 3 │ + > 4 │ test.describe("bar", 42); + │ ^^^^^^^^^^^^^^^^^^^^^^^^ + 5 │ + 6 │ test.describe("baz", { tag: ["@slow"] }); + + i The second argument to describe must be a function, not a string, number, object, or other type. + + i Replace the callback with a function expression or arrow function. + + +``` + +``` +not-function.js:6:1 lint/nursery/noPlaywrightValidDescribeCallback ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Describe callback must be a function. + + 4 │ test.describe("bar", 42); + 5 │ + > 6 │ test.describe("baz", { tag: ["@slow"] }); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 7 │ + 8 │ describe("qux", null); + + i The second argument to describe must be a function, not a string, number, object, or other type. + + i Replace the callback with a function expression or arrow function. + + +``` + +``` +not-function.js:8:1 lint/nursery/noPlaywrightValidDescribeCallback ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Describe callback must be a function. + + 6 │ test.describe("baz", { tag: ["@slow"] }); + 7 │ + > 8 │ describe("qux", null); + │ ^^^^^^^^^^^^^^^^^^^^^ + 9 │ + 10 │ describe("suite", undefined); + + i The second argument to describe must be a function, not a string, number, object, or other type. + + i Replace the callback with a function expression or arrow function. + + +``` + +``` +not-function.js:10:1 lint/nursery/noPlaywrightValidDescribeCallback ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Describe callback must be a function. + + 8 │ describe("qux", null); + 9 │ + > 10 │ describe("suite", undefined); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 11 │ + 12 │ test.describe("another", someVariable); + + i The second argument to describe must be a function, not a string, number, object, or other type. + + i Replace the callback with a function expression or arrow function. + + +``` + +``` +not-function.js:12:1 lint/nursery/noPlaywrightValidDescribeCallback ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Describe callback must be a function. + + 10 │ describe("suite", undefined); + 11 │ + > 12 │ test.describe("another", someVariable); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 13 │ + + i The second argument to describe must be a function, not a string, number, object, or other type. + + i Replace the callback with a function expression or arrow function. + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/invalid/in-test.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/invalid/in-test.js.snap index 04a84ac482a3..2b23422fc820 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/invalid/in-test.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/invalid/in-test.js.snap @@ -16,7 +16,7 @@ test('wait', async ({ page }) => { ``` in-test.js:3:11 lint/nursery/noPlaywrightWaitForTimeout ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - i Unexpected use of waitForTimeout(). + i Unexpected use of page.waitForTimeout(). 1 │ test('wait', async ({ page }) => { 2 │ await page.click('button'); @@ -25,7 +25,7 @@ in-test.js:3:11 lint/nursery/noPlaywrightWaitForTimeout ━━━━━━━━ 4 │ }); 5 │ - i Prefer using built-in wait methods like waitForLoadState(), waitForURL(), or waitForFunction() instead. + i Prefer using built-in wait methods like page.waitForLoadState(), page.waitForURL(), or page.waitForFunction() instead. i Hardcoded timeouts are flaky and make tests slower. Use conditions that wait for specific events. diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/invalid/simple.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/invalid/simple.js.snap index ed77b7947c17..b91f95b1e934 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/invalid/simple.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/invalid/simple.js.snap @@ -13,13 +13,13 @@ await page.waitForTimeout(5000); ``` simple.js:1:7 lint/nursery/noPlaywrightWaitForTimeout ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - i Unexpected use of waitForTimeout(). + i Unexpected use of page.waitForTimeout(). > 1 │ await page.waitForTimeout(5000); │ ^^^^^^^^^^^^^^^^^^^^^^^^^ 2 │ - i Prefer using built-in wait methods like waitForLoadState(), waitForURL(), or waitForFunction() instead. + i Prefer using built-in wait methods like page.waitForLoadState(), page.waitForURL(), or page.waitForFunction() instead. i Hardcoded timeouts are flaky and make tests slower. Use conditions that wait for specific events. From 59d18c5050f16a2fdf5389d54f456f4f8f94335e Mon Sep 17 00:00:00 2001 From: Josh Delsman <12201+voxxit@users.noreply.github.com> Date: Sun, 19 Oct 2025 00:12:59 -0500 Subject: [PATCH 19/24] refactor(biome_js_analyze): clean up imports in no_playwright_useless_await rule --- .../src/lint/nursery/no_playwright_useless_await.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_useless_await.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_useless_await.rs index 8d8ef0b11ce3..b8519dfd47af 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_playwright_useless_await.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_playwright_useless_await.rs @@ -4,9 +4,7 @@ use biome_analyze::{ }; use biome_console::markup; use biome_diagnostics::Applicability; -use biome_js_syntax::{ - AnyJsExpression, JsAwaitExpression, JsCallExpression, -}; +use biome_js_syntax::{AnyJsExpression, JsAwaitExpression, JsCallExpression}; use biome_rowan::{AstNode, BatchMutationExt, TokenText}; use crate::JsRuleAction; From 99118496b9c6c6f08d6b85ba2bf4445566b4e6c4 Mon Sep 17 00:00:00 2001 From: Josh Delsman <12201+voxxit@users.noreply.github.com> Date: Sun, 19 Oct 2025 00:16:27 -0500 Subject: [PATCH 20/24] refactor(biome_js_analyze): simplify logic in missingPlaywrightAwait rule This commit refactors the `missingPlaywrightAwait` lint rule to streamline the logic for checking if a Promise combinator is awaited. The conditional check has been simplified using a single `if let` statement, enhancing code readability while maintaining the same functionality. This change aims to improve the maintainability of the lint rule. --- .../lint/nursery/missing_playwright_await.rs | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/crates/biome_js_analyze/src/lint/nursery/missing_playwright_await.rs b/crates/biome_js_analyze/src/lint/nursery/missing_playwright_await.rs index 89126d35cc36..d8f2489fbb51 100644 --- a/crates/biome_js_analyze/src/lint/nursery/missing_playwright_await.rs +++ b/crates/biome_js_analyze/src/lint/nursery/missing_playwright_await.rs @@ -186,26 +186,26 @@ impl Rule for MissingPlaywrightAwait { // Check if inside a Promise combinator (Promise.all, etc.) that itself isn't awaited // If so, fix the outer combinator to preserve concurrency semantics - if let Some(promise_combinator) = find_enclosing_promise_all(call_expr) { - if !is_call_awaited_or_returned(&promise_combinator) { - let mut mutation = ctx.root().begin(); - let await_expr = make::js_await_expression( - make::token(T![await]), - promise_combinator.clone().into(), - ); - - mutation.replace_element( - promise_combinator.into_syntax().into(), - await_expr.into_syntax().into(), - ); - - return Some(JsRuleAction::new( - ctx.metadata().action_category(ctx.category(), ctx.group()), - Applicability::MaybeIncorrect, - markup! { "Add await to Promise combinator" }.to_owned(), - mutation, - )); - } + if let Some(promise_combinator) = find_enclosing_promise_all(call_expr) + && !is_call_awaited_or_returned(&promise_combinator) + { + let mut mutation = ctx.root().begin(); + let await_expr = make::js_await_expression( + make::token(T![await]), + promise_combinator.clone().into(), + ); + + mutation.replace_element( + promise_combinator.into_syntax().into(), + await_expr.into_syntax().into(), + ); + + return Some(JsRuleAction::new( + ctx.metadata().action_category(ctx.category(), ctx.group()), + Applicability::MaybeIncorrect, + markup! { "Add await to Promise combinator" }.to_owned(), + mutation, + )); } // Normal case: fix the call directly From 95487c4eee019ffe98057f25b666c1567bc10e70 Mon Sep 17 00:00:00 2001 From: Josh Delsman <206746594+josh-ninjatrader@users.noreply.github.com> Date: Sun, 30 Nov 2025 15:27:14 -0600 Subject: [PATCH 21/24] feat(lint): enhance Playwright linting rules and add new tests (pr feedback) - Updated `noFocusedTests` rule to detect Playwright's `test.only()` pattern. - Enhanced `noSkippedTests` to handle Playwright's `test.skip()` pattern. - Introduced new tests for various Playwright linting scenarios, including valid and invalid cases for `usePlaywrightValidDescribeCallback`. - Removed outdated tests related to `noPlaywrightSkippedTest` and `noPlaywrightMissingAwait`. - Added new test cases for `noPlaywrightWaitForTimeout` and other related rules. - Regenerated snapshots for updated tests and rules. --- .changeset/common-lizards-sniff.md | 9 +- .../migrate/eslint_any_rule_to_biome.rs | 14 +- .../src/analyzer/linter/rules.rs | 231 ++++++++---------- .../src/generated/domain_selector.rs | 3 +- .../src/categories.rs | 3 +- crates/biome_js_analyze/src/lint/nursery.rs | 5 +- .../nursery/no_playwright_missing_await.rs | 45 ++-- .../nursery/no_playwright_skipped_test.rs | 129 ---------- ...use_playwright_valid_describe_callback.rs} | 6 +- .../invalid/bracket-notation.js | 12 - .../invalid/bracket-notation.js.snap | 20 -- .../invalid/dollar-dollar.js | 1 + .../invalid/dollar-dollar.js.snap | 9 +- .../invalid/dollar.js | 1 + .../invalid/dollar.js.snap | 9 +- .../invalid/frame.js | 1 + .../invalid/frame.js.snap | 9 +- .../invalid/nested-calls.js | 1 + .../invalid/nested-calls.js.snap | 44 ++-- .../valid/locator.js | 1 + .../valid/locator.js.snap | 2 +- .../invalid/bracket-notation.js | 12 - .../invalid/bracket-notation.js.snap | 20 -- .../noPlaywrightEval/invalid/eval-all.js | 1 + .../noPlaywrightEval/invalid/eval-all.js.snap | 9 +- .../nursery/noPlaywrightEval/invalid/eval.js | 1 + .../noPlaywrightEval/invalid/eval.js.snap | 9 +- .../invalid/multiple-arguments.js | 1 + .../invalid/multiple-arguments.js.snap | 48 ++-- .../valid/locator-evaluate.js | 1 + .../valid/locator-evaluate.js.snap | 2 +- .../invalid/bracket-notation-methods.js | 12 - .../invalid/bracket-notation-methods.js.snap | 20 -- .../noPlaywrightForceOption/invalid/click.js | 1 + .../invalid/click.js.snap | 9 +- .../noPlaywrightForceOption/invalid/fill.js | 1 + .../invalid/fill.js.snap | 9 +- .../invalid/force-string-literal.js | 1 + .../invalid/force-string-literal.js.snap | 22 +- .../invalid/select-option-force.js | 1 + .../invalid/select-option-force.js.snap | 34 +-- .../noPlaywrightForceOption/valid/click.js | 1 + .../valid/click.js.snap | 2 +- .../invalid/bracket-notation.js | 28 --- .../invalid/bracket-notation.js.snap | 36 --- .../invalid/expect-async-matcher.js | 1 + .../invalid/expect-async-matcher.js.snap | 23 +- .../invalid/expect-poll-sync-matchers.js | 1 + .../invalid/expect-poll-sync-matchers.js.snap | 101 +++----- .../invalid/expect-poll.js | 1 + .../invalid/expect-poll.js.snap | 25 +- .../invalid/module-level.js | 1 + .../invalid/module-level.js.snap | 19 +- .../invalid/nested-expects.js | 1 + .../invalid/nested-expects.js.snap | 71 ++---- .../invalid/non-async-context.js | 1 + .../invalid/non-async-context.js.snap | 12 +- .../invalid/promise-all-not-awaited.js | 1 + .../invalid/promise-all-not-awaited.js.snap | 47 ++-- .../invalid/promise-combinators.js | 1 + .../invalid/promise-combinators.js.snap | 143 +++++------ .../invalid/test-step.js | 1 + .../invalid/test-step.js.snap | 29 +-- .../noPlaywrightMissingAwait/valid/awaited.js | 1 + .../valid/awaited.js.snap | 1 + .../valid/bracket-notation-valid.js | 2 +- .../valid/bracket-notation-valid.js.snap | 2 +- .../valid/promise-all.js | 1 + .../valid/promise-all.js.snap | 2 +- .../valid/promise-combinators-valid.js | 2 +- .../valid/promise-combinators-valid.js.snap | 2 +- .../valid/returned.js | 1 + .../valid/returned.js.snap | 1 + .../invalid/all-methods.js | 1 + .../invalid/all-methods.js.snap | 82 ++++--- .../invalid/bracket-notation.js | 10 - .../invalid/bracket-notation.js.snap | 18 -- .../noPlaywrightNetworkidle/invalid/goto.js | 1 + .../invalid/goto.js.snap | 9 +- .../invalid/wait-for-load-state.js | 1 + .../invalid/wait-for-load-state.js.snap | 9 +- .../noPlaywrightNetworkidle/valid/load.js | 1 + .../valid/load.js.snap | 2 +- .../valid/other-wait-options.js | 1 + .../valid/other-wait-options.js.snap | 1 + .../invalid/bracket-notation.js | 8 - .../invalid/bracket-notation.js.snap | 16 -- .../noPlaywrightPagePause/invalid/frame.js | 1 + .../invalid/frame.js.snap | 10 +- .../noPlaywrightPagePause/invalid/in-test.js | 1 + .../invalid/in-test.js.snap | 14 +- .../noPlaywrightPagePause/invalid/simple.js | 1 + .../invalid/simple.js.snap | 9 +- .../noPlaywrightPagePause/valid/click.js | 1 + .../noPlaywrightPagePause/valid/click.js.snap | 2 +- .../valid/other-methods.js | 1 + .../valid/other-methods.js.snap | 2 +- .../valid/pause-function.js | 1 + .../valid/pause-function.js.snap | 2 +- .../invalid/describe-skip.js | 5 - .../invalid/describe-skip.js.snap | 35 --- .../invalid/test-skip.js | 2 - .../invalid/test-skip.js.snap | 28 --- .../invalid/expect-mixed-chains.js | 1 + .../invalid/expect-mixed-chains.js.snap | 68 +++--- .../invalid/expect-sync.js | 1 + .../invalid/expect-sync.js.snap | 11 +- .../invalid/getByRole.js | 1 + .../invalid/getByRole.js.snap | 11 +- .../invalid/locator.js | 1 + .../invalid/locator.js.snap | 11 +- .../invalid/page-frame.js | 1 + .../invalid/page-frame.js.snap | 38 +-- .../valid/async-methods.js | 1 + .../valid/async-methods.js.snap | 2 +- .../valid/expect-with-async-modifiers.js | 1 + .../valid/expect-with-async-modifiers.js.snap | 1 + .../valid/sync-no-await.js | 1 + .../valid/sync-no-await.js.snap | 2 +- .../invalid/simple.js | 1 + .../invalid/simple.js.snap | 9 +- .../invalid/with-options.js | 1 + .../invalid/with-options.js.snap | 11 +- .../valid/alternatives.js | 1 + .../valid/alternatives.js.snap | 1 + .../invalid/simple.js | 1 + .../invalid/simple.js.snap | 9 +- .../invalid/with-state.js | 1 + .../invalid/with-state.js.snap | 11 +- .../valid/locators.js | 1 + .../valid/locators.js.snap | 2 +- .../invalid/in-test.js | 1 + .../invalid/in-test.js.snap | 13 +- .../invalid/simple.js | 1 + .../invalid/simple.js.snap | 8 +- .../valid/other-methods.js | 1 + .../valid/other-methods.js.snap | 2 +- .../invalid/async.js | 1 + .../invalid/async.js.snap | 12 +- .../invalid/has-params.js | 1 + .../invalid/has-params.js.snap | 12 +- .../invalid/missing-callback.js | 1 + .../invalid/missing-callback.js.snap | 22 +- .../invalid/not-function.js | 1 + .../invalid/not-function.js.snap | 70 +++--- .../valid/correct.js | 1 + .../valid/correct.js.snap | 2 +- .../suspicious/noFocusedTests/invalid.js | 4 + .../suspicious/noFocusedTests/invalid.js.snap | 49 ++++ .../specs/suspicious/noFocusedTests/valid.js | 4 +- .../suspicious/noFocusedTests/valid.js.snap | 2 + crates/biome_rule_options/src/lib.rs | 3 +- .../no_playwright_valid_describe_callback.rs | 6 - ...use_playwright_valid_describe_callback.rs} | 2 +- .../@biomejs/backend-jsonrpc/src/workspace.ts | 42 ++-- .../@biomejs/biome/configuration_schema.json | 86 +++---- 156 files changed, 869 insertions(+), 1284 deletions(-) delete mode 100644 crates/biome_js_analyze/src/lint/nursery/no_playwright_skipped_test.rs rename crates/biome_js_analyze/src/lint/nursery/{no_playwright_valid_describe_callback.rs => use_playwright_valid_describe_callback.rs} (97%) delete mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/bracket-notation.js delete mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/bracket-notation.js.snap delete mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/bracket-notation.js delete mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/bracket-notation.js.snap delete mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/bracket-notation-methods.js delete mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/bracket-notation-methods.js.snap delete mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/bracket-notation.js delete mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/bracket-notation.js.snap delete mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/bracket-notation.js delete mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/bracket-notation.js.snap delete mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/bracket-notation.js delete mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/bracket-notation.js.snap delete mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightSkippedTest/invalid/describe-skip.js delete mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightSkippedTest/invalid/describe-skip.js.snap delete mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightSkippedTest/invalid/test-skip.js delete mode 100644 crates/biome_js_analyze/tests/specs/nursery/noPlaywrightSkippedTest/invalid/test-skip.js.snap rename crates/biome_js_analyze/tests/specs/nursery/{noPlaywrightValidDescribeCallback => usePlaywrightValidDescribeCallback}/invalid/async.js (71%) rename crates/biome_js_analyze/tests/specs/nursery/{noPlaywrightValidDescribeCallback => usePlaywrightValidDescribeCallback}/invalid/async.js.snap (58%) rename crates/biome_js_analyze/tests/specs/nursery/{noPlaywrightValidDescribeCallback => usePlaywrightValidDescribeCallback}/invalid/has-params.js (70%) rename crates/biome_js_analyze/tests/specs/nursery/{noPlaywrightValidDescribeCallback => usePlaywrightValidDescribeCallback}/invalid/has-params.js.snap (55%) rename crates/biome_js_analyze/tests/specs/nursery/{noPlaywrightValidDescribeCallback => usePlaywrightValidDescribeCallback}/invalid/missing-callback.js (65%) rename crates/biome_js_analyze/tests/specs/nursery/{noPlaywrightValidDescribeCallback => usePlaywrightValidDescribeCallback}/invalid/missing-callback.js.snap (54%) rename crates/biome_js_analyze/tests/specs/nursery/{noPlaywrightValidDescribeCallback => usePlaywrightValidDescribeCallback}/invalid/not-function.js (86%) rename crates/biome_js_analyze/tests/specs/nursery/{noPlaywrightValidDescribeCallback => usePlaywrightValidDescribeCallback}/invalid/not-function.js.snap (56%) rename crates/biome_js_analyze/tests/specs/nursery/{noPlaywrightValidDescribeCallback => usePlaywrightValidDescribeCallback}/valid/correct.js (83%) rename crates/biome_js_analyze/tests/specs/nursery/{noPlaywrightValidDescribeCallback => usePlaywrightValidDescribeCallback}/valid/correct.js.snap (88%) delete mode 100644 crates/biome_rule_options/src/no_playwright_valid_describe_callback.rs rename crates/biome_rule_options/src/{no_playwright_skipped_test.rs => use_playwright_valid_describe_callback.rs} (84%) diff --git a/.changeset/common-lizards-sniff.md b/.changeset/common-lizards-sniff.md index dabf3f9905f0..9db534e8ca09 100644 --- a/.changeset/common-lizards-sniff.md +++ b/.changeset/common-lizards-sniff.md @@ -2,21 +2,20 @@ "@biomejs/biome": patch --- -Added 13 new Playwright lint rules to the nursery (from eslint-plugin-playwright). +Added 11 new Playwright lint rules to the nursery (from eslint-plugin-playwright). The following rules are now available: -- [`missingPlaywrightAwait`](https://biomejs.dev/linter/rules/missing-playwright-await/): Enforce awaiting async Playwright APIs. +- [`noPlaywrightMissingAwait`](https://biomejs.dev/linter/rules/no-playwright-missing-await/): Enforce awaiting async Playwright APIs. - [`noPlaywrightElementHandle`](https://biomejs.dev/linter/rules/no-playwright-element-handle/): Prefer locators over element handles (`page.$()` and `page.$$()`). - [`noPlaywrightEval`](https://biomejs.dev/linter/rules/no-playwright-eval/): Disallow `page.$eval()` and `page.$$eval()` methods. - [`noPlaywrightForceOption`](https://biomejs.dev/linter/rules/no-playwright-force-option/): Disallow the `force` option on user interactions. - [`noPlaywrightNetworkidle`](https://biomejs.dev/linter/rules/no-playwright-networkidle/): Disallow deprecated `networkidle` wait option. - [`noPlaywrightPagePause`](https://biomejs.dev/linter/rules/no-playwright-page-pause/): Disallow `page.pause()` debugging calls in committed code. -- [`noPlaywrightSkippedTest`](https://biomejs.dev/linter/rules/no-playwright-skipped-test/): Disallow skipped tests with `test.skip()`. - [`noPlaywrightUselessAwait`](https://biomejs.dev/linter/rules/no-playwright-useless-await/): Disallow unnecessary `await` on synchronous Playwright methods. -- [`noPlaywrightValidDescribeCallback`](https://biomejs.dev/linter/rules/no-playwright-valid-describe-callback/): Validate describe callback signatures are not async. +- [`usePlaywrightValidDescribeCallback`](https://biomejs.dev/linter/rules/use-playwright-valid-describe-callback/): Validate describe callback signatures are not async. - [`noPlaywrightWaitForNavigation`](https://biomejs.dev/linter/rules/no-playwright-wait-for-navigation/): Prefer modern navigation APIs over deprecated `waitForNavigation()`. - [`noPlaywrightWaitForSelector`](https://biomejs.dev/linter/rules/no-playwright-wait-for-selector/): Prefer locators over deprecated `waitForSelector()`. - [`noPlaywrightWaitForTimeout`](https://biomejs.dev/linter/rules/no-playwright-wait-for-timeout/): Disallow hard-coded timeouts with `waitForTimeout()`. -Additionally, the existing `noFocusedTests` rule now detects Playwright's `test.only()` pattern. +Additionally, the existing `noFocusedTests` rule now detects Playwright's `test.only()` pattern, and `noSkippedTests` already handles Playwright's `test.skip()` pattern. diff --git a/crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs b/crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs index 3e5be704d924..6e754133b85a 100644 --- a/crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs +++ b/crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs @@ -2305,18 +2305,6 @@ pub(crate) fn migrate_eslint_any_rule( .get_or_insert(Default::default()); rule.set_level(rule.level().max(rule_severity.into())); } - "playwright/no-skipped-test" => { - if !options.include_nursery { - results.add(eslint_name, eslint_to_biome::RuleMigrationResult::Nursery); - return false; - } - let group = rules.nursery.get_or_insert_with(Default::default); - let rule = group - .unwrap_group_as_mut() - .no_playwright_skipped_test - .get_or_insert(Default::default()); - rule.set_level(rule.level().max(rule_severity.into())); - } "playwright/no-useless-await" => { if !options.include_nursery { results.add(eslint_name, eslint_to_biome::RuleMigrationResult::Nursery); @@ -2373,7 +2361,7 @@ pub(crate) fn migrate_eslint_any_rule( let group = rules.nursery.get_or_insert_with(Default::default); let rule = group .unwrap_group_as_mut() - .no_playwright_valid_describe_callback + .use_playwright_valid_describe_callback .get_or_insert(Default::default()); rule.set_level(rule.level().max(rule_severity.into())); } diff --git a/crates/biome_configuration/src/analyzer/linter/rules.rs b/crates/biome_configuration/src/analyzer/linter/rules.rs index 4cbb92bb9696..d409b86c64fc 100644 --- a/crates/biome_configuration/src/analyzer/linter/rules.rs +++ b/crates/biome_configuration/src/analyzer/linter/rules.rs @@ -241,9 +241,7 @@ pub enum RuleName { NoPlaywrightMissingAwait, NoPlaywrightNetworkidle, NoPlaywrightPagePause, - NoPlaywrightSkippedTest, NoPlaywrightUselessAwait, - NoPlaywrightValidDescribeCallback, NoPlaywrightWaitForNavigation, NoPlaywrightWaitForSelector, NoPlaywrightWaitForTimeout, @@ -441,6 +439,7 @@ pub enum RuleName { UseObjectSpread, UseOptionalChain, UseParseIntRadix, + UsePlaywrightValidDescribeCallback, UseQwikClasslist, UseQwikMethodUsage, UseQwikValidLexicalScope, @@ -651,9 +650,7 @@ impl RuleName { Self::NoPlaywrightMissingAwait => "noPlaywrightMissingAwait", Self::NoPlaywrightNetworkidle => "noPlaywrightNetworkidle", Self::NoPlaywrightPagePause => "noPlaywrightPagePause", - Self::NoPlaywrightSkippedTest => "noPlaywrightSkippedTest", Self::NoPlaywrightUselessAwait => "noPlaywrightUselessAwait", - Self::NoPlaywrightValidDescribeCallback => "noPlaywrightValidDescribeCallback", Self::NoPlaywrightWaitForNavigation => "noPlaywrightWaitForNavigation", Self::NoPlaywrightWaitForSelector => "noPlaywrightWaitForSelector", Self::NoPlaywrightWaitForTimeout => "noPlaywrightWaitForTimeout", @@ -851,6 +848,7 @@ impl RuleName { Self::UseObjectSpread => "useObjectSpread", Self::UseOptionalChain => "useOptionalChain", Self::UseParseIntRadix => "useParseIntRadix", + Self::UsePlaywrightValidDescribeCallback => "usePlaywrightValidDescribeCallback", Self::UseQwikClasslist => "useQwikClasslist", Self::UseQwikMethodUsage => "useQwikMethodUsage", Self::UseQwikValidLexicalScope => "useQwikValidLexicalScope", @@ -1057,9 +1055,7 @@ impl RuleName { Self::NoPlaywrightMissingAwait => RuleGroup::Nursery, Self::NoPlaywrightNetworkidle => RuleGroup::Nursery, Self::NoPlaywrightPagePause => RuleGroup::Nursery, - Self::NoPlaywrightSkippedTest => RuleGroup::Nursery, Self::NoPlaywrightUselessAwait => RuleGroup::Nursery, - Self::NoPlaywrightValidDescribeCallback => RuleGroup::Nursery, Self::NoPlaywrightWaitForNavigation => RuleGroup::Nursery, Self::NoPlaywrightWaitForSelector => RuleGroup::Nursery, Self::NoPlaywrightWaitForTimeout => RuleGroup::Nursery, @@ -1257,6 +1253,7 @@ impl RuleName { Self::UseObjectSpread => RuleGroup::Style, Self::UseOptionalChain => RuleGroup::Complexity, Self::UseParseIntRadix => RuleGroup::Correctness, + Self::UsePlaywrightValidDescribeCallback => RuleGroup::Nursery, Self::UseQwikClasslist => RuleGroup::Correctness, Self::UseQwikMethodUsage => RuleGroup::Nursery, Self::UseQwikValidLexicalScope => RuleGroup::Nursery, @@ -1472,9 +1469,7 @@ impl std::str::FromStr for RuleName { "noPlaywrightMissingAwait" => Ok(Self::NoPlaywrightMissingAwait), "noPlaywrightNetworkidle" => Ok(Self::NoPlaywrightNetworkidle), "noPlaywrightPagePause" => Ok(Self::NoPlaywrightPagePause), - "noPlaywrightSkippedTest" => Ok(Self::NoPlaywrightSkippedTest), "noPlaywrightUselessAwait" => Ok(Self::NoPlaywrightUselessAwait), - "noPlaywrightValidDescribeCallback" => Ok(Self::NoPlaywrightValidDescribeCallback), "noPlaywrightWaitForNavigation" => Ok(Self::NoPlaywrightWaitForNavigation), "noPlaywrightWaitForSelector" => Ok(Self::NoPlaywrightWaitForSelector), "noPlaywrightWaitForTimeout" => Ok(Self::NoPlaywrightWaitForTimeout), @@ -1672,6 +1667,7 @@ impl std::str::FromStr for RuleName { "useObjectSpread" => Ok(Self::UseObjectSpread), "useOptionalChain" => Ok(Self::UseOptionalChain), "useParseIntRadix" => Ok(Self::UseParseIntRadix), + "usePlaywrightValidDescribeCallback" => Ok(Self::UsePlaywrightValidDescribeCallback), "useQwikClasslist" => Ok(Self::UseQwikClasslist), "useQwikMethodUsage" => Ok(Self::UseQwikMethodUsage), "useQwikValidLexicalScope" => Ok(Self::UseQwikValidLexicalScope), @@ -4856,7 +4852,7 @@ impl From for Correctness { #[cfg_attr(feature = "schema", derive(JsonSchema))] #[serde(rename_all = "camelCase", default, deny_unknown_fields)] #[doc = r" A list of rules that belong to this group"] -pub struct Nursery { # [doc = r" Enables the recommended rules for this group"] # [serde (skip_serializing_if = "Option::is_none")] pub recommended : Option < bool > , # [doc = "Disallow continue statements.\nSee https://biomejs.dev/linter/rules/no-continue"] # [serde (skip_serializing_if = "Option::is_none")] pub no_continue : Option < RuleConfiguration < biome_rule_options :: no_continue :: NoContinueOptions >> , # [doc = "Restrict imports of deprecated exports.\nSee https://biomejs.dev/linter/rules/no-deprecated-imports"] # [serde (skip_serializing_if = "Option::is_none")] pub no_deprecated_imports : Option < RuleConfiguration < biome_rule_options :: no_deprecated_imports :: NoDeprecatedImportsOptions >> , # [doc = "Prevent the listing of duplicate dependencies. The rule supports the following dependency groups: \"bundledDependencies\", \"bundleDependencies\", \"dependencies\", \"devDependencies\", \"overrides\", \"optionalDependencies\", and \"peerDependencies\".\nSee https://biomejs.dev/linter/rules/no-duplicate-dependencies"] # [serde (skip_serializing_if = "Option::is_none")] pub no_duplicate_dependencies : Option < RuleConfiguration < biome_rule_options :: no_duplicate_dependencies :: NoDuplicateDependenciesOptions >> , # [doc = "Disallow JSX prop spreading the same identifier multiple times.\nSee https://biomejs.dev/linter/rules/no-duplicated-spread-props"] # [serde (skip_serializing_if = "Option::is_none")] pub no_duplicated_spread_props : Option < RuleConfiguration < biome_rule_options :: no_duplicated_spread_props :: NoDuplicatedSpreadPropsOptions >> , # [doc = "Disallow empty sources.\nSee https://biomejs.dev/linter/rules/no-empty-source"] # [serde (skip_serializing_if = "Option::is_none")] pub no_empty_source : Option < RuleConfiguration < biome_rule_options :: no_empty_source :: NoEmptySourceOptions >> , # [doc = "Require the use of === or !== for comparison with null.\nSee https://biomejs.dev/linter/rules/no-equals-to-null"] # [serde (skip_serializing_if = "Option::is_none")] pub no_equals_to_null : Option < RuleFixConfiguration < biome_rule_options :: no_equals_to_null :: NoEqualsToNullOptions >> , # [doc = "Require Promise-like statements to be handled appropriately.\nSee https://biomejs.dev/linter/rules/no-floating-promises"] # [serde (skip_serializing_if = "Option::is_none")] pub no_floating_promises : Option < RuleFixConfiguration < biome_rule_options :: no_floating_promises :: NoFloatingPromisesOptions >> , # [doc = "Disallow iterating using a for-in loop.\nSee https://biomejs.dev/linter/rules/no-for-in"] # [serde (skip_serializing_if = "Option::is_none")] pub no_for_in : Option < RuleConfiguration < biome_rule_options :: no_for_in :: NoForInOptions >> , # [doc = "Prevent import cycles.\nSee https://biomejs.dev/linter/rules/no-import-cycles"] # [serde (skip_serializing_if = "Option::is_none")] pub no_import_cycles : Option < RuleConfiguration < biome_rule_options :: no_import_cycles :: NoImportCyclesOptions >> , # [doc = "Disallows the usage of the unary operators ++ and --.\nSee https://biomejs.dev/linter/rules/no-increment-decrement"] # [serde (skip_serializing_if = "Option::is_none")] pub no_increment_decrement : Option < RuleConfiguration < biome_rule_options :: no_increment_decrement :: NoIncrementDecrementOptions >> , # [doc = "Disallow string literals inside JSX elements.\nSee https://biomejs.dev/linter/rules/no-jsx-literals"] # [serde (skip_serializing_if = "Option::is_none")] pub no_jsx_literals : Option < RuleConfiguration < biome_rule_options :: no_jsx_literals :: NoJsxLiteralsOptions >> , # [doc = "Prevent problematic leaked values from being rendered.\nSee https://biomejs.dev/linter/rules/no-leaked-render"] # [serde (skip_serializing_if = "Option::is_none")] pub no_leaked_render : Option < RuleConfiguration < biome_rule_options :: no_leaked_render :: NoLeakedRenderOptions >> , # [doc = "Disallow Promises to be used in places where they are almost certainly a mistake.\nSee https://biomejs.dev/linter/rules/no-misused-promises"] # [serde (skip_serializing_if = "Option::is_none")] pub no_misused_promises : Option < RuleFixConfiguration < biome_rule_options :: no_misused_promises :: NoMisusedPromisesOptions >> , # [doc = "Disallow creating multiline strings by escaping newlines.\nSee https://biomejs.dev/linter/rules/no-multi-str"] # [serde (skip_serializing_if = "Option::is_none")] pub no_multi_str : Option < RuleConfiguration < biome_rule_options :: no_multi_str :: NoMultiStrOptions >> , # [doc = "Prevent client components from being async functions.\nSee https://biomejs.dev/linter/rules/no-next-async-client-component"] # [serde (skip_serializing_if = "Option::is_none")] pub no_next_async_client_component : Option < RuleConfiguration < biome_rule_options :: no_next_async_client_component :: NoNextAsyncClientComponentOptions >> , # [doc = "Disallow function parameters that are only used in recursive calls.\nSee https://biomejs.dev/linter/rules/no-parameters-only-used-in-recursion"] # [serde (skip_serializing_if = "Option::is_none")] pub no_parameters_only_used_in_recursion : Option < RuleFixConfiguration < biome_rule_options :: no_parameters_only_used_in_recursion :: NoParametersOnlyUsedInRecursionOptions >> , # [doc = "Disallow usage of element handles (page.$() and page.$$()).\nSee https://biomejs.dev/linter/rules/no-playwright-element-handle"] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_element_handle : Option < RuleConfiguration < biome_rule_options :: no_playwright_element_handle :: NoPlaywrightElementHandleOptions >> , # [doc = "Disallow usage of page.$eval() and page.$$eval().\nSee https://biomejs.dev/linter/rules/no-playwright-eval"] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_eval : Option < RuleConfiguration < biome_rule_options :: no_playwright_eval :: NoPlaywrightEvalOptions >> , # [doc = "Disallow usage of the { force: true } option.\nSee https://biomejs.dev/linter/rules/no-playwright-force-option"] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_force_option : Option < RuleConfiguration < biome_rule_options :: no_playwright_force_option :: NoPlaywrightForceOptionOptions >> , # [doc = "Enforce Playwright async APIs to be awaited or returned.\nSee https://biomejs.dev/linter/rules/no-playwright-missing-await"] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_missing_await : Option < RuleFixConfiguration < biome_rule_options :: no_playwright_missing_await :: NoPlaywrightMissingAwaitOptions >> , # [doc = "Disallow usage of the networkidle option.\nSee https://biomejs.dev/linter/rules/no-playwright-networkidle"] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_networkidle : Option < RuleConfiguration < biome_rule_options :: no_playwright_networkidle :: NoPlaywrightNetworkidleOptions >> , # [doc = "Disallow using page.pause().\nSee https://biomejs.dev/linter/rules/no-playwright-page-pause"] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_page_pause : Option < RuleConfiguration < biome_rule_options :: no_playwright_page_pause :: NoPlaywrightPagePauseOptions >> , # [doc = "Disallow usage of .skip annotation in Playwright tests.\nSee https://biomejs.dev/linter/rules/no-playwright-skipped-test"] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_skipped_test : Option < RuleConfiguration < biome_rule_options :: no_playwright_skipped_test :: NoPlaywrightSkippedTestOptions >> , # [doc = "Disallow unnecessary await for Playwright methods that don't return promises.\nSee https://biomejs.dev/linter/rules/no-playwright-useless-await"] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_useless_await : Option < RuleFixConfiguration < biome_rule_options :: no_playwright_useless_await :: NoPlaywrightUselessAwaitOptions >> , # [doc = "Enforce valid describe() callback.\nSee https://biomejs.dev/linter/rules/no-playwright-valid-describe-callback"] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_valid_describe_callback : Option < RuleConfiguration < biome_rule_options :: no_playwright_valid_describe_callback :: NoPlaywrightValidDescribeCallbackOptions >> , # [doc = "Disallow using page.waitForNavigation().\nSee https://biomejs.dev/linter/rules/no-playwright-wait-for-navigation"] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_wait_for_navigation : Option < RuleConfiguration < biome_rule_options :: no_playwright_wait_for_navigation :: NoPlaywrightWaitForNavigationOptions >> , # [doc = "Disallow using page.waitForSelector().\nSee https://biomejs.dev/linter/rules/no-playwright-wait-for-selector"] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_wait_for_selector : Option < RuleConfiguration < biome_rule_options :: no_playwright_wait_for_selector :: NoPlaywrightWaitForSelectorOptions >> , # [doc = "Disallow using page.waitForTimeout().\nSee https://biomejs.dev/linter/rules/no-playwright-wait-for-timeout"] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_wait_for_timeout : Option < RuleConfiguration < biome_rule_options :: no_playwright_wait_for_timeout :: NoPlaywrightWaitForTimeoutOptions >> , # [doc = "Disallow the use of the __proto__ property.\nSee https://biomejs.dev/linter/rules/no-proto"] # [serde (skip_serializing_if = "Option::is_none")] pub no_proto : Option < RuleConfiguration < biome_rule_options :: no_proto :: NoProtoOptions >> , # [doc = "Replaces usages of forwardRef with passing ref as a prop.\nSee https://biomejs.dev/linter/rules/no-react-forward-ref"] # [serde (skip_serializing_if = "Option::is_none")] pub no_react_forward_ref : Option < RuleFixConfiguration < biome_rule_options :: no_react_forward_ref :: NoReactForwardRefOptions >> , # [doc = "Disallow variable declarations from shadowing variables declared in the outer scope.\nSee https://biomejs.dev/linter/rules/no-shadow"] # [serde (skip_serializing_if = "Option::is_none")] pub no_shadow : Option < RuleConfiguration < biome_rule_options :: no_shadow :: NoShadowOptions >> , # [doc = "Prevent the usage of synchronous scripts.\nSee https://biomejs.dev/linter/rules/no-sync-scripts"] # [serde (skip_serializing_if = "Option::is_none")] pub no_sync_scripts : Option < RuleConfiguration < biome_rule_options :: no_sync_scripts :: NoSyncScriptsOptions >> , # [doc = "Disallow ternary operators.\nSee https://biomejs.dev/linter/rules/no-ternary"] # [serde (skip_serializing_if = "Option::is_none")] pub no_ternary : Option < RuleConfiguration < biome_rule_options :: no_ternary :: NoTernaryOptions >> , # [doc = "Disallow unknown DOM properties.\nSee https://biomejs.dev/linter/rules/no-unknown-attribute"] # [serde (skip_serializing_if = "Option::is_none")] pub no_unknown_attribute : Option < RuleConfiguration < biome_rule_options :: no_unknown_attribute :: NoUnknownAttributeOptions >> , # [doc = "Disallow unnecessary type-based conditions that can be statically determined as redundant.\nSee https://biomejs.dev/linter/rules/no-unnecessary-conditions"] # [serde (skip_serializing_if = "Option::is_none")] pub no_unnecessary_conditions : Option < RuleConfiguration < biome_rule_options :: no_unnecessary_conditions :: NoUnnecessaryConditionsOptions >> , # [doc = "Warn when importing non-existing exports.\nSee https://biomejs.dev/linter/rules/no-unresolved-imports"] # [serde (skip_serializing_if = "Option::is_none")] pub no_unresolved_imports : Option < RuleConfiguration < biome_rule_options :: no_unresolved_imports :: NoUnresolvedImportsOptions >> , # [doc = "Disallow expression statements that are neither a function call nor an assignment.\nSee https://biomejs.dev/linter/rules/no-unused-expressions"] # [serde (skip_serializing_if = "Option::is_none")] pub no_unused_expressions : Option < RuleConfiguration < biome_rule_options :: no_unused_expressions :: NoUnusedExpressionsOptions >> , # [doc = "Disallow unused catch bindings.\nSee https://biomejs.dev/linter/rules/no-useless-catch-binding"] # [serde (skip_serializing_if = "Option::is_none")] pub no_useless_catch_binding : Option < RuleFixConfiguration < biome_rule_options :: no_useless_catch_binding :: NoUselessCatchBindingOptions >> , # [doc = "Disallow the use of useless undefined.\nSee https://biomejs.dev/linter/rules/no-useless-undefined"] # [serde (skip_serializing_if = "Option::is_none")] pub no_useless_undefined : Option < RuleFixConfiguration < biome_rule_options :: no_useless_undefined :: NoUselessUndefinedOptions >> , # [doc = "Enforce that Vue component data options are declared as functions.\nSee https://biomejs.dev/linter/rules/no-vue-data-object-declaration"] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_data_object_declaration : Option < RuleFixConfiguration < biome_rule_options :: no_vue_data_object_declaration :: NoVueDataObjectDeclarationOptions >> , # [doc = "Disallow duplicate keys in Vue component data, methods, computed properties, and other options.\nSee https://biomejs.dev/linter/rules/no-vue-duplicate-keys"] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_duplicate_keys : Option < RuleConfiguration < biome_rule_options :: no_vue_duplicate_keys :: NoVueDuplicateKeysOptions >> , # [doc = "Disallow reserved keys in Vue component data and computed properties.\nSee https://biomejs.dev/linter/rules/no-vue-reserved-keys"] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_reserved_keys : Option < RuleConfiguration < biome_rule_options :: no_vue_reserved_keys :: NoVueReservedKeysOptions >> , # [doc = "Disallow reserved names to be used as props.\nSee https://biomejs.dev/linter/rules/no-vue-reserved-props"] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_reserved_props : Option < RuleConfiguration < biome_rule_options :: no_vue_reserved_props :: NoVueReservedPropsOptions >> , # [doc = "Disallow using v-if and v-for directives on the same element.\nSee https://biomejs.dev/linter/rules/no-vue-v-if-with-v-for"] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_v_if_with_v_for : Option < RuleConfiguration < biome_rule_options :: no_vue_v_if_with_v_for :: NoVueVIfWithVForOptions >> , # [doc = "Require Array#sort and Array#toSorted calls to always provide a compareFunction.\nSee https://biomejs.dev/linter/rules/use-array-sort-compare"] # [serde (skip_serializing_if = "Option::is_none")] pub use_array_sort_compare : Option < RuleConfiguration < biome_rule_options :: use_array_sort_compare :: UseArraySortCompareOptions >> , # [doc = "Enforce consistent arrow function bodies.\nSee https://biomejs.dev/linter/rules/use-consistent-arrow-return"] # [serde (skip_serializing_if = "Option::is_none")] pub use_consistent_arrow_return : Option < RuleFixConfiguration < biome_rule_options :: use_consistent_arrow_return :: UseConsistentArrowReturnOptions >> , # [doc = "Require all descriptions to follow the same style (either block or inline) to maintain consistency and improve readability across the schema.\nSee https://biomejs.dev/linter/rules/use-consistent-graphql-descriptions"] # [serde (skip_serializing_if = "Option::is_none")] pub use_consistent_graphql_descriptions : Option < RuleConfiguration < biome_rule_options :: use_consistent_graphql_descriptions :: UseConsistentGraphqlDescriptionsOptions >> , # [doc = "Require the @deprecated directive to specify a deletion date.\nSee https://biomejs.dev/linter/rules/use-deprecated-date"] # [serde (skip_serializing_if = "Option::is_none")] pub use_deprecated_date : Option < RuleConfiguration < biome_rule_options :: use_deprecated_date :: UseDeprecatedDateOptions >> , # [doc = "Require switch-case statements to be exhaustive.\nSee https://biomejs.dev/linter/rules/use-exhaustive-switch-cases"] # [serde (skip_serializing_if = "Option::is_none")] pub use_exhaustive_switch_cases : Option < RuleFixConfiguration < biome_rule_options :: use_exhaustive_switch_cases :: UseExhaustiveSwitchCasesOptions >> , # [doc = "Enforce types in functions, methods, variables, and parameters.\nSee https://biomejs.dev/linter/rules/use-explicit-type"] # [serde (skip_serializing_if = "Option::is_none")] pub use_explicit_type : Option < RuleConfiguration < biome_rule_options :: use_explicit_type :: UseExplicitTypeOptions >> , # [doc = "Enforce the use of Array.prototype.find() over Array.prototype.filter() followed by [0] when looking for a single result.\nSee https://biomejs.dev/linter/rules/use-find"] # [serde (skip_serializing_if = "Option::is_none")] pub use_find : Option < RuleConfiguration < biome_rule_options :: use_find :: UseFindOptions >> , # [doc = "Enforce a maximum number of parameters in function definitions.\nSee https://biomejs.dev/linter/rules/use-max-params"] # [serde (skip_serializing_if = "Option::is_none")] pub use_max_params : Option < RuleConfiguration < biome_rule_options :: use_max_params :: UseMaxParamsOptions >> , # [doc = "Disallow use* hooks outside of component$ or other use* hooks in Qwik applications.\nSee https://biomejs.dev/linter/rules/use-qwik-method-usage"] # [serde (skip_serializing_if = "Option::is_none")] pub use_qwik_method_usage : Option < RuleConfiguration < biome_rule_options :: use_qwik_method_usage :: UseQwikMethodUsageOptions >> , # [doc = "Disallow unserializable expressions in Qwik dollar ($) scopes.\nSee https://biomejs.dev/linter/rules/use-qwik-valid-lexical-scope"] # [serde (skip_serializing_if = "Option::is_none")] pub use_qwik_valid_lexical_scope : Option < RuleConfiguration < biome_rule_options :: use_qwik_valid_lexical_scope :: UseQwikValidLexicalScopeOptions >> , # [doc = "Enforce RegExp#exec over String#match if no global flag is provided.\nSee https://biomejs.dev/linter/rules/use-regexp-exec"] # [serde (skip_serializing_if = "Option::is_none")] pub use_regexp_exec : Option < RuleConfiguration < biome_rule_options :: use_regexp_exec :: UseRegexpExecOptions >> , # [doc = "Enforce the presence of required scripts in package.json.\nSee https://biomejs.dev/linter/rules/use-required-scripts"] # [serde (skip_serializing_if = "Option::is_none")] pub use_required_scripts : Option < RuleConfiguration < biome_rule_options :: use_required_scripts :: UseRequiredScriptsOptions >> , # [doc = "Enforce the sorting of CSS utility classes.\nSee https://biomejs.dev/linter/rules/use-sorted-classes"] # [serde (skip_serializing_if = "Option::is_none")] pub use_sorted_classes : Option < RuleFixConfiguration < biome_rule_options :: use_sorted_classes :: UseSortedClassesOptions >> , # [doc = "Enforce the use of the spread operator over .apply().\nSee https://biomejs.dev/linter/rules/use-spread"] # [serde (skip_serializing_if = "Option::is_none")] pub use_spread : Option < RuleFixConfiguration < biome_rule_options :: use_spread :: UseSpreadOptions >> , # [doc = "Enforce unique operation names across a GraphQL document.\nSee https://biomejs.dev/linter/rules/use-unique-graphql-operation-name"] # [serde (skip_serializing_if = "Option::is_none")] pub use_unique_graphql_operation_name : Option < RuleConfiguration < biome_rule_options :: use_unique_graphql_operation_name :: UseUniqueGraphqlOperationNameOptions >> , # [doc = "Enforce specific order of Vue compiler macros.\nSee https://biomejs.dev/linter/rules/use-vue-define-macros-order"] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_define_macros_order : Option < RuleFixConfiguration < biome_rule_options :: use_vue_define_macros_order :: UseVueDefineMacrosOrderOptions >> , # [doc = "Enforce hyphenated (kebab-case) attribute names in Vue templates.\nSee https://biomejs.dev/linter/rules/use-vue-hyphenated-attributes"] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_hyphenated_attributes : Option < RuleFixConfiguration < biome_rule_options :: use_vue_hyphenated_attributes :: UseVueHyphenatedAttributesOptions >> , # [doc = "Enforce multi-word component names in Vue components.\nSee https://biomejs.dev/linter/rules/use-vue-multi-word-component-names"] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_multi_word_component_names : Option < RuleConfiguration < biome_rule_options :: use_vue_multi_word_component_names :: UseVueMultiWordComponentNamesOptions >> , # [doc = "Forbids v-bind directives with missing arguments or invalid modifiers.\nSee https://biomejs.dev/linter/rules/use-vue-valid-v-bind"] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_valid_v_bind : Option < RuleConfiguration < biome_rule_options :: use_vue_valid_v_bind :: UseVueValidVBindOptions >> , # [doc = "Enforce valid usage of v-else.\nSee https://biomejs.dev/linter/rules/use-vue-valid-v-else"] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_valid_v_else : Option < RuleConfiguration < biome_rule_options :: use_vue_valid_v_else :: UseVueValidVElseOptions >> , # [doc = "Enforce valid v-else-if directives.\nSee https://biomejs.dev/linter/rules/use-vue-valid-v-else-if"] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_valid_v_else_if : Option < RuleConfiguration < biome_rule_options :: use_vue_valid_v_else_if :: UseVueValidVElseIfOptions >> , # [doc = "Enforce valid v-html directives.\nSee https://biomejs.dev/linter/rules/use-vue-valid-v-html"] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_valid_v_html : Option < RuleConfiguration < biome_rule_options :: use_vue_valid_v_html :: UseVueValidVHtmlOptions >> , # [doc = "Enforces valid v-if usage for Vue templates.\nSee https://biomejs.dev/linter/rules/use-vue-valid-v-if"] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_valid_v_if : Option < RuleConfiguration < biome_rule_options :: use_vue_valid_v_if :: UseVueValidVIfOptions >> , # [doc = "Enforce valid v-on directives with proper arguments, modifiers, and handlers.\nSee https://biomejs.dev/linter/rules/use-vue-valid-v-on"] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_valid_v_on : Option < RuleConfiguration < biome_rule_options :: use_vue_valid_v_on :: UseVueValidVOnOptions >> , # [doc = "Enforce valid v-text Vue directives.\nSee https://biomejs.dev/linter/rules/use-vue-valid-v-text"] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_valid_v_text : Option < RuleConfiguration < biome_rule_options :: use_vue_valid_v_text :: UseVueValidVTextOptions >> } +pub struct Nursery { # [doc = r" Enables the recommended rules for this group"] # [serde (skip_serializing_if = "Option::is_none")] pub recommended : Option < bool > , # [doc = "Disallow continue statements.\nSee https://biomejs.dev/linter/rules/no-continue"] # [serde (skip_serializing_if = "Option::is_none")] pub no_continue : Option < RuleConfiguration < biome_rule_options :: no_continue :: NoContinueOptions >> , # [doc = "Restrict imports of deprecated exports.\nSee https://biomejs.dev/linter/rules/no-deprecated-imports"] # [serde (skip_serializing_if = "Option::is_none")] pub no_deprecated_imports : Option < RuleConfiguration < biome_rule_options :: no_deprecated_imports :: NoDeprecatedImportsOptions >> , # [doc = "Prevent the listing of duplicate dependencies. The rule supports the following dependency groups: \"bundledDependencies\", \"bundleDependencies\", \"dependencies\", \"devDependencies\", \"overrides\", \"optionalDependencies\", and \"peerDependencies\".\nSee https://biomejs.dev/linter/rules/no-duplicate-dependencies"] # [serde (skip_serializing_if = "Option::is_none")] pub no_duplicate_dependencies : Option < RuleConfiguration < biome_rule_options :: no_duplicate_dependencies :: NoDuplicateDependenciesOptions >> , # [doc = "Disallow JSX prop spreading the same identifier multiple times.\nSee https://biomejs.dev/linter/rules/no-duplicated-spread-props"] # [serde (skip_serializing_if = "Option::is_none")] pub no_duplicated_spread_props : Option < RuleConfiguration < biome_rule_options :: no_duplicated_spread_props :: NoDuplicatedSpreadPropsOptions >> , # [doc = "Disallow empty sources.\nSee https://biomejs.dev/linter/rules/no-empty-source"] # [serde (skip_serializing_if = "Option::is_none")] pub no_empty_source : Option < RuleConfiguration < biome_rule_options :: no_empty_source :: NoEmptySourceOptions >> , # [doc = "Require the use of === or !== for comparison with null.\nSee https://biomejs.dev/linter/rules/no-equals-to-null"] # [serde (skip_serializing_if = "Option::is_none")] pub no_equals_to_null : Option < RuleFixConfiguration < biome_rule_options :: no_equals_to_null :: NoEqualsToNullOptions >> , # [doc = "Require Promise-like statements to be handled appropriately.\nSee https://biomejs.dev/linter/rules/no-floating-promises"] # [serde (skip_serializing_if = "Option::is_none")] pub no_floating_promises : Option < RuleFixConfiguration < biome_rule_options :: no_floating_promises :: NoFloatingPromisesOptions >> , # [doc = "Disallow iterating using a for-in loop.\nSee https://biomejs.dev/linter/rules/no-for-in"] # [serde (skip_serializing_if = "Option::is_none")] pub no_for_in : Option < RuleConfiguration < biome_rule_options :: no_for_in :: NoForInOptions >> , # [doc = "Prevent import cycles.\nSee https://biomejs.dev/linter/rules/no-import-cycles"] # [serde (skip_serializing_if = "Option::is_none")] pub no_import_cycles : Option < RuleConfiguration < biome_rule_options :: no_import_cycles :: NoImportCyclesOptions >> , # [doc = "Disallows the usage of the unary operators ++ and --.\nSee https://biomejs.dev/linter/rules/no-increment-decrement"] # [serde (skip_serializing_if = "Option::is_none")] pub no_increment_decrement : Option < RuleConfiguration < biome_rule_options :: no_increment_decrement :: NoIncrementDecrementOptions >> , # [doc = "Disallow string literals inside JSX elements.\nSee https://biomejs.dev/linter/rules/no-jsx-literals"] # [serde (skip_serializing_if = "Option::is_none")] pub no_jsx_literals : Option < RuleConfiguration < biome_rule_options :: no_jsx_literals :: NoJsxLiteralsOptions >> , # [doc = "Prevent problematic leaked values from being rendered.\nSee https://biomejs.dev/linter/rules/no-leaked-render"] # [serde (skip_serializing_if = "Option::is_none")] pub no_leaked_render : Option < RuleConfiguration < biome_rule_options :: no_leaked_render :: NoLeakedRenderOptions >> , # [doc = "Disallow Promises to be used in places where they are almost certainly a mistake.\nSee https://biomejs.dev/linter/rules/no-misused-promises"] # [serde (skip_serializing_if = "Option::is_none")] pub no_misused_promises : Option < RuleFixConfiguration < biome_rule_options :: no_misused_promises :: NoMisusedPromisesOptions >> , # [doc = "Disallow creating multiline strings by escaping newlines.\nSee https://biomejs.dev/linter/rules/no-multi-str"] # [serde (skip_serializing_if = "Option::is_none")] pub no_multi_str : Option < RuleConfiguration < biome_rule_options :: no_multi_str :: NoMultiStrOptions >> , # [doc = "Prevent client components from being async functions.\nSee https://biomejs.dev/linter/rules/no-next-async-client-component"] # [serde (skip_serializing_if = "Option::is_none")] pub no_next_async_client_component : Option < RuleConfiguration < biome_rule_options :: no_next_async_client_component :: NoNextAsyncClientComponentOptions >> , # [doc = "Disallow function parameters that are only used in recursive calls.\nSee https://biomejs.dev/linter/rules/no-parameters-only-used-in-recursion"] # [serde (skip_serializing_if = "Option::is_none")] pub no_parameters_only_used_in_recursion : Option < RuleFixConfiguration < biome_rule_options :: no_parameters_only_used_in_recursion :: NoParametersOnlyUsedInRecursionOptions >> , # [doc = "Disallow usage of element handles (page.$() and page.$$()).\nSee https://biomejs.dev/linter/rules/no-playwright-element-handle"] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_element_handle : Option < RuleConfiguration < biome_rule_options :: no_playwright_element_handle :: NoPlaywrightElementHandleOptions >> , # [doc = "Disallow usage of page.$eval() and page.$$eval().\nSee https://biomejs.dev/linter/rules/no-playwright-eval"] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_eval : Option < RuleConfiguration < biome_rule_options :: no_playwright_eval :: NoPlaywrightEvalOptions >> , # [doc = "Disallow usage of the { force: true } option.\nSee https://biomejs.dev/linter/rules/no-playwright-force-option"] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_force_option : Option < RuleConfiguration < biome_rule_options :: no_playwright_force_option :: NoPlaywrightForceOptionOptions >> , # [doc = "Enforce Playwright async APIs to be awaited or returned.\nSee https://biomejs.dev/linter/rules/no-playwright-missing-await"] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_missing_await : Option < RuleFixConfiguration < biome_rule_options :: no_playwright_missing_await :: NoPlaywrightMissingAwaitOptions >> , # [doc = "Disallow usage of the networkidle option.\nSee https://biomejs.dev/linter/rules/no-playwright-networkidle"] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_networkidle : Option < RuleConfiguration < biome_rule_options :: no_playwright_networkidle :: NoPlaywrightNetworkidleOptions >> , # [doc = "Disallow using page.pause().\nSee https://biomejs.dev/linter/rules/no-playwright-page-pause"] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_page_pause : Option < RuleConfiguration < biome_rule_options :: no_playwright_page_pause :: NoPlaywrightPagePauseOptions >> , # [doc = "Disallow unnecessary await for Playwright methods that don't return promises.\nSee https://biomejs.dev/linter/rules/no-playwright-useless-await"] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_useless_await : Option < RuleFixConfiguration < biome_rule_options :: no_playwright_useless_await :: NoPlaywrightUselessAwaitOptions >> , # [doc = "Disallow using page.waitForNavigation().\nSee https://biomejs.dev/linter/rules/no-playwright-wait-for-navigation"] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_wait_for_navigation : Option < RuleConfiguration < biome_rule_options :: no_playwright_wait_for_navigation :: NoPlaywrightWaitForNavigationOptions >> , # [doc = "Disallow using page.waitForSelector().\nSee https://biomejs.dev/linter/rules/no-playwright-wait-for-selector"] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_wait_for_selector : Option < RuleConfiguration < biome_rule_options :: no_playwright_wait_for_selector :: NoPlaywrightWaitForSelectorOptions >> , # [doc = "Disallow using page.waitForTimeout().\nSee https://biomejs.dev/linter/rules/no-playwright-wait-for-timeout"] # [serde (skip_serializing_if = "Option::is_none")] pub no_playwright_wait_for_timeout : Option < RuleConfiguration < biome_rule_options :: no_playwright_wait_for_timeout :: NoPlaywrightWaitForTimeoutOptions >> , # [doc = "Disallow the use of the __proto__ property.\nSee https://biomejs.dev/linter/rules/no-proto"] # [serde (skip_serializing_if = "Option::is_none")] pub no_proto : Option < RuleConfiguration < biome_rule_options :: no_proto :: NoProtoOptions >> , # [doc = "Replaces usages of forwardRef with passing ref as a prop.\nSee https://biomejs.dev/linter/rules/no-react-forward-ref"] # [serde (skip_serializing_if = "Option::is_none")] pub no_react_forward_ref : Option < RuleFixConfiguration < biome_rule_options :: no_react_forward_ref :: NoReactForwardRefOptions >> , # [doc = "Disallow variable declarations from shadowing variables declared in the outer scope.\nSee https://biomejs.dev/linter/rules/no-shadow"] # [serde (skip_serializing_if = "Option::is_none")] pub no_shadow : Option < RuleConfiguration < biome_rule_options :: no_shadow :: NoShadowOptions >> , # [doc = "Prevent the usage of synchronous scripts.\nSee https://biomejs.dev/linter/rules/no-sync-scripts"] # [serde (skip_serializing_if = "Option::is_none")] pub no_sync_scripts : Option < RuleConfiguration < biome_rule_options :: no_sync_scripts :: NoSyncScriptsOptions >> , # [doc = "Disallow ternary operators.\nSee https://biomejs.dev/linter/rules/no-ternary"] # [serde (skip_serializing_if = "Option::is_none")] pub no_ternary : Option < RuleConfiguration < biome_rule_options :: no_ternary :: NoTernaryOptions >> , # [doc = "Disallow unknown DOM properties.\nSee https://biomejs.dev/linter/rules/no-unknown-attribute"] # [serde (skip_serializing_if = "Option::is_none")] pub no_unknown_attribute : Option < RuleConfiguration < biome_rule_options :: no_unknown_attribute :: NoUnknownAttributeOptions >> , # [doc = "Disallow unnecessary type-based conditions that can be statically determined as redundant.\nSee https://biomejs.dev/linter/rules/no-unnecessary-conditions"] # [serde (skip_serializing_if = "Option::is_none")] pub no_unnecessary_conditions : Option < RuleConfiguration < biome_rule_options :: no_unnecessary_conditions :: NoUnnecessaryConditionsOptions >> , # [doc = "Warn when importing non-existing exports.\nSee https://biomejs.dev/linter/rules/no-unresolved-imports"] # [serde (skip_serializing_if = "Option::is_none")] pub no_unresolved_imports : Option < RuleConfiguration < biome_rule_options :: no_unresolved_imports :: NoUnresolvedImportsOptions >> , # [doc = "Disallow expression statements that are neither a function call nor an assignment.\nSee https://biomejs.dev/linter/rules/no-unused-expressions"] # [serde (skip_serializing_if = "Option::is_none")] pub no_unused_expressions : Option < RuleConfiguration < biome_rule_options :: no_unused_expressions :: NoUnusedExpressionsOptions >> , # [doc = "Disallow unused catch bindings.\nSee https://biomejs.dev/linter/rules/no-useless-catch-binding"] # [serde (skip_serializing_if = "Option::is_none")] pub no_useless_catch_binding : Option < RuleFixConfiguration < biome_rule_options :: no_useless_catch_binding :: NoUselessCatchBindingOptions >> , # [doc = "Disallow the use of useless undefined.\nSee https://biomejs.dev/linter/rules/no-useless-undefined"] # [serde (skip_serializing_if = "Option::is_none")] pub no_useless_undefined : Option < RuleFixConfiguration < biome_rule_options :: no_useless_undefined :: NoUselessUndefinedOptions >> , # [doc = "Enforce that Vue component data options are declared as functions.\nSee https://biomejs.dev/linter/rules/no-vue-data-object-declaration"] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_data_object_declaration : Option < RuleFixConfiguration < biome_rule_options :: no_vue_data_object_declaration :: NoVueDataObjectDeclarationOptions >> , # [doc = "Disallow duplicate keys in Vue component data, methods, computed properties, and other options.\nSee https://biomejs.dev/linter/rules/no-vue-duplicate-keys"] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_duplicate_keys : Option < RuleConfiguration < biome_rule_options :: no_vue_duplicate_keys :: NoVueDuplicateKeysOptions >> , # [doc = "Disallow reserved keys in Vue component data and computed properties.\nSee https://biomejs.dev/linter/rules/no-vue-reserved-keys"] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_reserved_keys : Option < RuleConfiguration < biome_rule_options :: no_vue_reserved_keys :: NoVueReservedKeysOptions >> , # [doc = "Disallow reserved names to be used as props.\nSee https://biomejs.dev/linter/rules/no-vue-reserved-props"] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_reserved_props : Option < RuleConfiguration < biome_rule_options :: no_vue_reserved_props :: NoVueReservedPropsOptions >> , # [doc = "Disallow using v-if and v-for directives on the same element.\nSee https://biomejs.dev/linter/rules/no-vue-v-if-with-v-for"] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_v_if_with_v_for : Option < RuleConfiguration < biome_rule_options :: no_vue_v_if_with_v_for :: NoVueVIfWithVForOptions >> , # [doc = "Require Array#sort and Array#toSorted calls to always provide a compareFunction.\nSee https://biomejs.dev/linter/rules/use-array-sort-compare"] # [serde (skip_serializing_if = "Option::is_none")] pub use_array_sort_compare : Option < RuleConfiguration < biome_rule_options :: use_array_sort_compare :: UseArraySortCompareOptions >> , # [doc = "Enforce consistent arrow function bodies.\nSee https://biomejs.dev/linter/rules/use-consistent-arrow-return"] # [serde (skip_serializing_if = "Option::is_none")] pub use_consistent_arrow_return : Option < RuleFixConfiguration < biome_rule_options :: use_consistent_arrow_return :: UseConsistentArrowReturnOptions >> , # [doc = "Require all descriptions to follow the same style (either block or inline) to maintain consistency and improve readability across the schema.\nSee https://biomejs.dev/linter/rules/use-consistent-graphql-descriptions"] # [serde (skip_serializing_if = "Option::is_none")] pub use_consistent_graphql_descriptions : Option < RuleConfiguration < biome_rule_options :: use_consistent_graphql_descriptions :: UseConsistentGraphqlDescriptionsOptions >> , # [doc = "Require the @deprecated directive to specify a deletion date.\nSee https://biomejs.dev/linter/rules/use-deprecated-date"] # [serde (skip_serializing_if = "Option::is_none")] pub use_deprecated_date : Option < RuleConfiguration < biome_rule_options :: use_deprecated_date :: UseDeprecatedDateOptions >> , # [doc = "Require switch-case statements to be exhaustive.\nSee https://biomejs.dev/linter/rules/use-exhaustive-switch-cases"] # [serde (skip_serializing_if = "Option::is_none")] pub use_exhaustive_switch_cases : Option < RuleFixConfiguration < biome_rule_options :: use_exhaustive_switch_cases :: UseExhaustiveSwitchCasesOptions >> , # [doc = "Enforce types in functions, methods, variables, and parameters.\nSee https://biomejs.dev/linter/rules/use-explicit-type"] # [serde (skip_serializing_if = "Option::is_none")] pub use_explicit_type : Option < RuleConfiguration < biome_rule_options :: use_explicit_type :: UseExplicitTypeOptions >> , # [doc = "Enforce the use of Array.prototype.find() over Array.prototype.filter() followed by [0] when looking for a single result.\nSee https://biomejs.dev/linter/rules/use-find"] # [serde (skip_serializing_if = "Option::is_none")] pub use_find : Option < RuleConfiguration < biome_rule_options :: use_find :: UseFindOptions >> , # [doc = "Enforce a maximum number of parameters in function definitions.\nSee https://biomejs.dev/linter/rules/use-max-params"] # [serde (skip_serializing_if = "Option::is_none")] pub use_max_params : Option < RuleConfiguration < biome_rule_options :: use_max_params :: UseMaxParamsOptions >> , # [doc = "Enforce valid describe() callback.\nSee https://biomejs.dev/linter/rules/use-playwright-valid-describe-callback"] # [serde (skip_serializing_if = "Option::is_none")] pub use_playwright_valid_describe_callback : Option < RuleConfiguration < biome_rule_options :: use_playwright_valid_describe_callback :: UsePlaywrightValidDescribeCallbackOptions >> , # [doc = "Disallow use* hooks outside of component$ or other use* hooks in Qwik applications.\nSee https://biomejs.dev/linter/rules/use-qwik-method-usage"] # [serde (skip_serializing_if = "Option::is_none")] pub use_qwik_method_usage : Option < RuleConfiguration < biome_rule_options :: use_qwik_method_usage :: UseQwikMethodUsageOptions >> , # [doc = "Disallow unserializable expressions in Qwik dollar ($) scopes.\nSee https://biomejs.dev/linter/rules/use-qwik-valid-lexical-scope"] # [serde (skip_serializing_if = "Option::is_none")] pub use_qwik_valid_lexical_scope : Option < RuleConfiguration < biome_rule_options :: use_qwik_valid_lexical_scope :: UseQwikValidLexicalScopeOptions >> , # [doc = "Enforce RegExp#exec over String#match if no global flag is provided.\nSee https://biomejs.dev/linter/rules/use-regexp-exec"] # [serde (skip_serializing_if = "Option::is_none")] pub use_regexp_exec : Option < RuleConfiguration < biome_rule_options :: use_regexp_exec :: UseRegexpExecOptions >> , # [doc = "Enforce the presence of required scripts in package.json.\nSee https://biomejs.dev/linter/rules/use-required-scripts"] # [serde (skip_serializing_if = "Option::is_none")] pub use_required_scripts : Option < RuleConfiguration < biome_rule_options :: use_required_scripts :: UseRequiredScriptsOptions >> , # [doc = "Enforce the sorting of CSS utility classes.\nSee https://biomejs.dev/linter/rules/use-sorted-classes"] # [serde (skip_serializing_if = "Option::is_none")] pub use_sorted_classes : Option < RuleFixConfiguration < biome_rule_options :: use_sorted_classes :: UseSortedClassesOptions >> , # [doc = "Enforce the use of the spread operator over .apply().\nSee https://biomejs.dev/linter/rules/use-spread"] # [serde (skip_serializing_if = "Option::is_none")] pub use_spread : Option < RuleFixConfiguration < biome_rule_options :: use_spread :: UseSpreadOptions >> , # [doc = "Enforce unique operation names across a GraphQL document.\nSee https://biomejs.dev/linter/rules/use-unique-graphql-operation-name"] # [serde (skip_serializing_if = "Option::is_none")] pub use_unique_graphql_operation_name : Option < RuleConfiguration < biome_rule_options :: use_unique_graphql_operation_name :: UseUniqueGraphqlOperationNameOptions >> , # [doc = "Enforce specific order of Vue compiler macros.\nSee https://biomejs.dev/linter/rules/use-vue-define-macros-order"] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_define_macros_order : Option < RuleFixConfiguration < biome_rule_options :: use_vue_define_macros_order :: UseVueDefineMacrosOrderOptions >> , # [doc = "Enforce hyphenated (kebab-case) attribute names in Vue templates.\nSee https://biomejs.dev/linter/rules/use-vue-hyphenated-attributes"] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_hyphenated_attributes : Option < RuleFixConfiguration < biome_rule_options :: use_vue_hyphenated_attributes :: UseVueHyphenatedAttributesOptions >> , # [doc = "Enforce multi-word component names in Vue components.\nSee https://biomejs.dev/linter/rules/use-vue-multi-word-component-names"] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_multi_word_component_names : Option < RuleConfiguration < biome_rule_options :: use_vue_multi_word_component_names :: UseVueMultiWordComponentNamesOptions >> , # [doc = "Forbids v-bind directives with missing arguments or invalid modifiers.\nSee https://biomejs.dev/linter/rules/use-vue-valid-v-bind"] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_valid_v_bind : Option < RuleConfiguration < biome_rule_options :: use_vue_valid_v_bind :: UseVueValidVBindOptions >> , # [doc = "Enforce valid usage of v-else.\nSee https://biomejs.dev/linter/rules/use-vue-valid-v-else"] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_valid_v_else : Option < RuleConfiguration < biome_rule_options :: use_vue_valid_v_else :: UseVueValidVElseOptions >> , # [doc = "Enforce valid v-else-if directives.\nSee https://biomejs.dev/linter/rules/use-vue-valid-v-else-if"] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_valid_v_else_if : Option < RuleConfiguration < biome_rule_options :: use_vue_valid_v_else_if :: UseVueValidVElseIfOptions >> , # [doc = "Enforce valid v-html directives.\nSee https://biomejs.dev/linter/rules/use-vue-valid-v-html"] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_valid_v_html : Option < RuleConfiguration < biome_rule_options :: use_vue_valid_v_html :: UseVueValidVHtmlOptions >> , # [doc = "Enforces valid v-if usage for Vue templates.\nSee https://biomejs.dev/linter/rules/use-vue-valid-v-if"] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_valid_v_if : Option < RuleConfiguration < biome_rule_options :: use_vue_valid_v_if :: UseVueValidVIfOptions >> , # [doc = "Enforce valid v-on directives with proper arguments, modifiers, and handlers.\nSee https://biomejs.dev/linter/rules/use-vue-valid-v-on"] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_valid_v_on : Option < RuleConfiguration < biome_rule_options :: use_vue_valid_v_on :: UseVueValidVOnOptions >> , # [doc = "Enforce valid v-text Vue directives.\nSee https://biomejs.dev/linter/rules/use-vue-valid-v-text"] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_valid_v_text : Option < RuleConfiguration < biome_rule_options :: use_vue_valid_v_text :: UseVueValidVTextOptions >> } impl Nursery { const GROUP_NAME: &'static str = "nursery"; pub(crate) const GROUP_RULES: &'static [&'static str] = &[ @@ -4882,9 +4878,7 @@ impl Nursery { "noPlaywrightMissingAwait", "noPlaywrightNetworkidle", "noPlaywrightPagePause", - "noPlaywrightSkippedTest", "noPlaywrightUselessAwait", - "noPlaywrightValidDescribeCallback", "noPlaywrightWaitForNavigation", "noPlaywrightWaitForSelector", "noPlaywrightWaitForTimeout", @@ -4912,6 +4906,7 @@ impl Nursery { "useExplicitType", "useFind", "useMaxParams", + "usePlaywrightValidDescribeCallback", "useQwikMethodUsage", "useQwikValidLexicalScope", "useRegexpExec", @@ -4931,8 +4926,8 @@ impl Nursery { "useVueValidVText", ]; const RECOMMENDED_RULES_AS_FILTERS: &'static [RuleFilter<'static>] = &[ - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[28]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[57]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[26]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[56]), ]; const ALL_RULES_AS_FILTERS: &'static [RuleFilter<'static>] = &[ RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[0]), @@ -5003,7 +4998,6 @@ impl Nursery { RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[65]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[66]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[67]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[68]), ]; } impl RuleGroupExt for Nursery { @@ -5125,240 +5119,235 @@ impl RuleGroupExt for Nursery { { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21])); } - if let Some(rule) = self.no_playwright_skipped_test.as_ref() + if let Some(rule) = self.no_playwright_useless_await.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22])); } - if let Some(rule) = self.no_playwright_useless_await.as_ref() + if let Some(rule) = self.no_playwright_wait_for_navigation.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23])); } - if let Some(rule) = self.no_playwright_valid_describe_callback.as_ref() + if let Some(rule) = self.no_playwright_wait_for_selector.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24])); } - if let Some(rule) = self.no_playwright_wait_for_navigation.as_ref() + if let Some(rule) = self.no_playwright_wait_for_timeout.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[25])); } - if let Some(rule) = self.no_playwright_wait_for_selector.as_ref() + if let Some(rule) = self.no_proto.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[26])); } - if let Some(rule) = self.no_playwright_wait_for_timeout.as_ref() + if let Some(rule) = self.no_react_forward_ref.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[27])); } - if let Some(rule) = self.no_proto.as_ref() + if let Some(rule) = self.no_shadow.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[28])); } - if let Some(rule) = self.no_react_forward_ref.as_ref() + if let Some(rule) = self.no_sync_scripts.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[29])); } - if let Some(rule) = self.no_shadow.as_ref() + if let Some(rule) = self.no_ternary.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[30])); } - if let Some(rule) = self.no_sync_scripts.as_ref() + if let Some(rule) = self.no_unknown_attribute.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[31])); } - if let Some(rule) = self.no_ternary.as_ref() + if let Some(rule) = self.no_unnecessary_conditions.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[32])); } - if let Some(rule) = self.no_unknown_attribute.as_ref() + if let Some(rule) = self.no_unresolved_imports.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[33])); } - if let Some(rule) = self.no_unnecessary_conditions.as_ref() + if let Some(rule) = self.no_unused_expressions.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[34])); } - if let Some(rule) = self.no_unresolved_imports.as_ref() + if let Some(rule) = self.no_useless_catch_binding.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[35])); } - if let Some(rule) = self.no_unused_expressions.as_ref() + if let Some(rule) = self.no_useless_undefined.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[36])); } - if let Some(rule) = self.no_useless_catch_binding.as_ref() + if let Some(rule) = self.no_vue_data_object_declaration.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[37])); } - if let Some(rule) = self.no_useless_undefined.as_ref() + if let Some(rule) = self.no_vue_duplicate_keys.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[38])); } - if let Some(rule) = self.no_vue_data_object_declaration.as_ref() + if let Some(rule) = self.no_vue_reserved_keys.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[39])); } - if let Some(rule) = self.no_vue_duplicate_keys.as_ref() + if let Some(rule) = self.no_vue_reserved_props.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[40])); } - if let Some(rule) = self.no_vue_reserved_keys.as_ref() + if let Some(rule) = self.no_vue_v_if_with_v_for.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[41])); } - if let Some(rule) = self.no_vue_reserved_props.as_ref() + if let Some(rule) = self.use_array_sort_compare.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[42])); } - if let Some(rule) = self.no_vue_v_if_with_v_for.as_ref() + if let Some(rule) = self.use_consistent_arrow_return.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[43])); } - if let Some(rule) = self.use_array_sort_compare.as_ref() + if let Some(rule) = self.use_consistent_graphql_descriptions.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[44])); } - if let Some(rule) = self.use_consistent_arrow_return.as_ref() + if let Some(rule) = self.use_deprecated_date.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[45])); } - if let Some(rule) = self.use_consistent_graphql_descriptions.as_ref() + if let Some(rule) = self.use_exhaustive_switch_cases.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[46])); } - if let Some(rule) = self.use_deprecated_date.as_ref() + if let Some(rule) = self.use_explicit_type.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[47])); } - if let Some(rule) = self.use_exhaustive_switch_cases.as_ref() + if let Some(rule) = self.use_find.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[48])); } - if let Some(rule) = self.use_explicit_type.as_ref() + if let Some(rule) = self.use_max_params.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[49])); } - if let Some(rule) = self.use_find.as_ref() + if let Some(rule) = self.use_playwright_valid_describe_callback.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[50])); } - if let Some(rule) = self.use_max_params.as_ref() - && rule.is_enabled() - { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[51])); - } if let Some(rule) = self.use_qwik_method_usage.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[52])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[51])); } if let Some(rule) = self.use_qwik_valid_lexical_scope.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[53])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[52])); } if let Some(rule) = self.use_regexp_exec.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[54])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[53])); } if let Some(rule) = self.use_required_scripts.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[55])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[54])); } if let Some(rule) = self.use_sorted_classes.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[56])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[55])); } if let Some(rule) = self.use_spread.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[57])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[56])); } if let Some(rule) = self.use_unique_graphql_operation_name.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[58])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[57])); } if let Some(rule) = self.use_vue_define_macros_order.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[59])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[58])); } if let Some(rule) = self.use_vue_hyphenated_attributes.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[60])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[59])); } if let Some(rule) = self.use_vue_multi_word_component_names.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[61])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[60])); } if let Some(rule) = self.use_vue_valid_v_bind.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[62])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[61])); } if let Some(rule) = self.use_vue_valid_v_else.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[63])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[62])); } if let Some(rule) = self.use_vue_valid_v_else_if.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[64])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[63])); } if let Some(rule) = self.use_vue_valid_v_html.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[65])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[64])); } if let Some(rule) = self.use_vue_valid_v_if.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[66])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[65])); } if let Some(rule) = self.use_vue_valid_v_on.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[67])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[66])); } if let Some(rule) = self.use_vue_valid_v_text.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[68])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[67])); } index_set } @@ -5474,240 +5463,235 @@ impl RuleGroupExt for Nursery { { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21])); } - if let Some(rule) = self.no_playwright_skipped_test.as_ref() + if let Some(rule) = self.no_playwright_useless_await.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22])); } - if let Some(rule) = self.no_playwright_useless_await.as_ref() + if let Some(rule) = self.no_playwright_wait_for_navigation.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23])); } - if let Some(rule) = self.no_playwright_valid_describe_callback.as_ref() + if let Some(rule) = self.no_playwright_wait_for_selector.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24])); } - if let Some(rule) = self.no_playwright_wait_for_navigation.as_ref() + if let Some(rule) = self.no_playwright_wait_for_timeout.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[25])); } - if let Some(rule) = self.no_playwright_wait_for_selector.as_ref() + if let Some(rule) = self.no_proto.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[26])); } - if let Some(rule) = self.no_playwright_wait_for_timeout.as_ref() + if let Some(rule) = self.no_react_forward_ref.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[27])); } - if let Some(rule) = self.no_proto.as_ref() + if let Some(rule) = self.no_shadow.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[28])); } - if let Some(rule) = self.no_react_forward_ref.as_ref() + if let Some(rule) = self.no_sync_scripts.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[29])); } - if let Some(rule) = self.no_shadow.as_ref() + if let Some(rule) = self.no_ternary.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[30])); } - if let Some(rule) = self.no_sync_scripts.as_ref() + if let Some(rule) = self.no_unknown_attribute.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[31])); } - if let Some(rule) = self.no_ternary.as_ref() + if let Some(rule) = self.no_unnecessary_conditions.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[32])); } - if let Some(rule) = self.no_unknown_attribute.as_ref() + if let Some(rule) = self.no_unresolved_imports.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[33])); } - if let Some(rule) = self.no_unnecessary_conditions.as_ref() + if let Some(rule) = self.no_unused_expressions.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[34])); } - if let Some(rule) = self.no_unresolved_imports.as_ref() + if let Some(rule) = self.no_useless_catch_binding.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[35])); } - if let Some(rule) = self.no_unused_expressions.as_ref() + if let Some(rule) = self.no_useless_undefined.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[36])); } - if let Some(rule) = self.no_useless_catch_binding.as_ref() + if let Some(rule) = self.no_vue_data_object_declaration.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[37])); } - if let Some(rule) = self.no_useless_undefined.as_ref() + if let Some(rule) = self.no_vue_duplicate_keys.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[38])); } - if let Some(rule) = self.no_vue_data_object_declaration.as_ref() + if let Some(rule) = self.no_vue_reserved_keys.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[39])); } - if let Some(rule) = self.no_vue_duplicate_keys.as_ref() + if let Some(rule) = self.no_vue_reserved_props.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[40])); } - if let Some(rule) = self.no_vue_reserved_keys.as_ref() + if let Some(rule) = self.no_vue_v_if_with_v_for.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[41])); } - if let Some(rule) = self.no_vue_reserved_props.as_ref() + if let Some(rule) = self.use_array_sort_compare.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[42])); } - if let Some(rule) = self.no_vue_v_if_with_v_for.as_ref() + if let Some(rule) = self.use_consistent_arrow_return.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[43])); } - if let Some(rule) = self.use_array_sort_compare.as_ref() + if let Some(rule) = self.use_consistent_graphql_descriptions.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[44])); } - if let Some(rule) = self.use_consistent_arrow_return.as_ref() + if let Some(rule) = self.use_deprecated_date.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[45])); } - if let Some(rule) = self.use_consistent_graphql_descriptions.as_ref() + if let Some(rule) = self.use_exhaustive_switch_cases.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[46])); } - if let Some(rule) = self.use_deprecated_date.as_ref() + if let Some(rule) = self.use_explicit_type.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[47])); } - if let Some(rule) = self.use_exhaustive_switch_cases.as_ref() + if let Some(rule) = self.use_find.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[48])); } - if let Some(rule) = self.use_explicit_type.as_ref() + if let Some(rule) = self.use_max_params.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[49])); } - if let Some(rule) = self.use_find.as_ref() + if let Some(rule) = self.use_playwright_valid_describe_callback.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[50])); } - if let Some(rule) = self.use_max_params.as_ref() - && rule.is_disabled() - { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[51])); - } if let Some(rule) = self.use_qwik_method_usage.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[52])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[51])); } if let Some(rule) = self.use_qwik_valid_lexical_scope.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[53])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[52])); } if let Some(rule) = self.use_regexp_exec.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[54])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[53])); } if let Some(rule) = self.use_required_scripts.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[55])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[54])); } if let Some(rule) = self.use_sorted_classes.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[56])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[55])); } if let Some(rule) = self.use_spread.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[57])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[56])); } if let Some(rule) = self.use_unique_graphql_operation_name.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[58])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[57])); } if let Some(rule) = self.use_vue_define_macros_order.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[59])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[58])); } if let Some(rule) = self.use_vue_hyphenated_attributes.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[60])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[59])); } if let Some(rule) = self.use_vue_multi_word_component_names.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[61])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[60])); } if let Some(rule) = self.use_vue_valid_v_bind.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[62])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[61])); } if let Some(rule) = self.use_vue_valid_v_else.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[63])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[62])); } if let Some(rule) = self.use_vue_valid_v_else_if.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[64])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[63])); } if let Some(rule) = self.use_vue_valid_v_html.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[65])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[64])); } if let Some(rule) = self.use_vue_valid_v_if.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[66])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[65])); } if let Some(rule) = self.use_vue_valid_v_on.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[67])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[66])); } if let Some(rule) = self.use_vue_valid_v_text.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[68])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[67])); } index_set } @@ -5827,18 +5811,10 @@ impl RuleGroupExt for Nursery { .no_playwright_page_pause .as_ref() .map(|conf| (conf.level(), conf.get_options())), - "noPlaywrightSkippedTest" => self - .no_playwright_skipped_test - .as_ref() - .map(|conf| (conf.level(), conf.get_options())), "noPlaywrightUselessAwait" => self .no_playwright_useless_await .as_ref() .map(|conf| (conf.level(), conf.get_options())), - "noPlaywrightValidDescribeCallback" => self - .no_playwright_valid_describe_callback - .as_ref() - .map(|conf| (conf.level(), conf.get_options())), "noPlaywrightWaitForNavigation" => self .no_playwright_wait_for_navigation .as_ref() @@ -5947,6 +5923,10 @@ impl RuleGroupExt for Nursery { .use_max_params .as_ref() .map(|conf| (conf.level(), conf.get_options())), + "usePlaywrightValidDescribeCallback" => self + .use_playwright_valid_describe_callback + .as_ref() + .map(|conf| (conf.level(), conf.get_options())), "useQwikMethodUsage" => self .use_qwik_method_usage .as_ref() @@ -6045,9 +6025,7 @@ impl From for Nursery { no_playwright_missing_await: Some(value.into()), no_playwright_networkidle: Some(value.into()), no_playwright_page_pause: Some(value.into()), - no_playwright_skipped_test: Some(value.into()), no_playwright_useless_await: Some(value.into()), - no_playwright_valid_describe_callback: Some(value.into()), no_playwright_wait_for_navigation: Some(value.into()), no_playwright_wait_for_selector: Some(value.into()), no_playwright_wait_for_timeout: Some(value.into()), @@ -6075,6 +6053,7 @@ impl From for Nursery { use_explicit_type: Some(value.into()), use_find: Some(value.into()), use_max_params: Some(value.into()), + use_playwright_valid_describe_callback: Some(value.into()), use_qwik_method_usage: Some(value.into()), use_qwik_valid_lexical_scope: Some(value.into()), use_regexp_exec: Some(value.into()), diff --git a/crates/biome_configuration/src/generated/domain_selector.rs b/crates/biome_configuration/src/generated/domain_selector.rs index 8aab445fea44..64b8f4f1a465 100644 --- a/crates/biome_configuration/src/generated/domain_selector.rs +++ b/crates/biome_configuration/src/generated/domain_selector.rs @@ -25,12 +25,11 @@ static PLAYWRIGHT_FILTERS: LazyLock>> = LazyLock::new(|| RuleFilter::Rule("nursery", "noPlaywrightMissingAwait"), RuleFilter::Rule("nursery", "noPlaywrightNetworkidle"), RuleFilter::Rule("nursery", "noPlaywrightPagePause"), - RuleFilter::Rule("nursery", "noPlaywrightSkippedTest"), RuleFilter::Rule("nursery", "noPlaywrightUselessAwait"), - RuleFilter::Rule("nursery", "noPlaywrightValidDescribeCallback"), RuleFilter::Rule("nursery", "noPlaywrightWaitForNavigation"), RuleFilter::Rule("nursery", "noPlaywrightWaitForSelector"), RuleFilter::Rule("nursery", "noPlaywrightWaitForTimeout"), + RuleFilter::Rule("nursery", "usePlaywrightValidDescribeCallback"), ] }); static PROJECT_FILTERS: LazyLock>> = LazyLock::new(|| { diff --git a/crates/biome_diagnostics_categories/src/categories.rs b/crates/biome_diagnostics_categories/src/categories.rs index 33d57e54e874..b2e69d8ab9b1 100644 --- a/crates/biome_diagnostics_categories/src/categories.rs +++ b/crates/biome_diagnostics_categories/src/categories.rs @@ -188,9 +188,8 @@ define_categories! { "lint/nursery/noPlaywrightMissingAwait": "https://biomejs.dev/linter/rules/no-playwright-missing-await", "lint/nursery/noPlaywrightNetworkidle": "https://biomejs.dev/linter/rules/no-playwright-networkidle", "lint/nursery/noPlaywrightPagePause": "https://biomejs.dev/linter/rules/no-playwright-page-pause", - "lint/nursery/noPlaywrightSkippedTest": "https://biomejs.dev/linter/rules/no-playwright-skipped-test", "lint/nursery/noPlaywrightUselessAwait": "https://biomejs.dev/linter/rules/no-playwright-useless-await", - "lint/nursery/noPlaywrightValidDescribeCallback": "https://biomejs.dev/linter/rules/no-playwright-valid-describe-callback", + "lint/nursery/usePlaywrightValidDescribeCallback": "https://biomejs.dev/linter/rules/use-playwright-valid-describe-callback", "lint/nursery/noPlaywrightWaitForNavigation": "https://biomejs.dev/linter/rules/no-playwright-wait-for-navigation", "lint/nursery/noPlaywrightWaitForSelector": "https://biomejs.dev/linter/rules/no-playwright-wait-for-selector", "lint/nursery/noPlaywrightWaitForTimeout": "https://biomejs.dev/linter/rules/no-playwright-wait-for-timeout", diff --git a/crates/biome_js_analyze/src/lint/nursery.rs b/crates/biome_js_analyze/src/lint/nursery.rs index ce8858877807..f397024ee449 100644 --- a/crates/biome_js_analyze/src/lint/nursery.rs +++ b/crates/biome_js_analyze/src/lint/nursery.rs @@ -24,9 +24,7 @@ pub mod no_playwright_force_option; pub mod no_playwright_missing_await; pub mod no_playwright_networkidle; pub mod no_playwright_page_pause; -pub mod no_playwright_skipped_test; pub mod no_playwright_useless_await; -pub mod no_playwright_valid_describe_callback; pub mod no_playwright_wait_for_navigation; pub mod no_playwright_wait_for_selector; pub mod no_playwright_wait_for_timeout; @@ -51,6 +49,7 @@ pub mod use_exhaustive_switch_cases; pub mod use_explicit_type; pub mod use_find; pub mod use_max_params; +pub mod use_playwright_valid_describe_callback; pub mod use_qwik_method_usage; pub mod use_qwik_valid_lexical_scope; pub mod use_regexp_exec; @@ -58,4 +57,4 @@ pub mod use_sorted_classes; pub mod use_spread; pub mod use_vue_define_macros_order; pub mod use_vue_multi_word_component_names; -declare_lint_group! { pub Nursery { name : "nursery" , rules : [self :: no_continue :: NoContinue , self :: no_deprecated_imports :: NoDeprecatedImports , self :: no_duplicated_spread_props :: NoDuplicatedSpreadProps , self :: no_empty_source :: NoEmptySource , self :: no_equals_to_null :: NoEqualsToNull , self :: no_floating_promises :: NoFloatingPromises , self :: no_for_in :: NoForIn , self :: no_import_cycles :: NoImportCycles , self :: no_increment_decrement :: NoIncrementDecrement , self :: no_jsx_literals :: NoJsxLiterals , self :: no_leaked_render :: NoLeakedRender , self :: no_misused_promises :: NoMisusedPromises , self :: no_multi_str :: NoMultiStr , self :: no_next_async_client_component :: NoNextAsyncClientComponent , self :: no_parameters_only_used_in_recursion :: NoParametersOnlyUsedInRecursion , self :: no_playwright_element_handle :: NoPlaywrightElementHandle , self :: no_playwright_eval :: NoPlaywrightEval , self :: no_playwright_force_option :: NoPlaywrightForceOption , self :: no_playwright_missing_await :: NoPlaywrightMissingAwait , self :: no_playwright_networkidle :: NoPlaywrightNetworkidle , self :: no_playwright_page_pause :: NoPlaywrightPagePause , self :: no_playwright_skipped_test :: NoPlaywrightSkippedTest , self :: no_playwright_useless_await :: NoPlaywrightUselessAwait , self :: no_playwright_valid_describe_callback :: NoPlaywrightValidDescribeCallback , self :: no_playwright_wait_for_navigation :: NoPlaywrightWaitForNavigation , self :: no_playwright_wait_for_selector :: NoPlaywrightWaitForSelector , self :: no_playwright_wait_for_timeout :: NoPlaywrightWaitForTimeout , self :: no_proto :: NoProto , self :: no_react_forward_ref :: NoReactForwardRef , self :: no_shadow :: NoShadow , self :: no_sync_scripts :: NoSyncScripts , self :: no_ternary :: NoTernary , self :: no_unknown_attribute :: NoUnknownAttribute , self :: no_unnecessary_conditions :: NoUnnecessaryConditions , self :: no_unresolved_imports :: NoUnresolvedImports , self :: no_unused_expressions :: NoUnusedExpressions , self :: no_useless_catch_binding :: NoUselessCatchBinding , self :: no_useless_undefined :: NoUselessUndefined , self :: no_vue_data_object_declaration :: NoVueDataObjectDeclaration , self :: no_vue_duplicate_keys :: NoVueDuplicateKeys , self :: no_vue_reserved_keys :: NoVueReservedKeys , self :: no_vue_reserved_props :: NoVueReservedProps , self :: use_array_sort_compare :: UseArraySortCompare , self :: use_consistent_arrow_return :: UseConsistentArrowReturn , self :: use_exhaustive_switch_cases :: UseExhaustiveSwitchCases , self :: use_explicit_type :: UseExplicitType , self :: use_find :: UseFind , self :: use_max_params :: UseMaxParams , self :: use_qwik_method_usage :: UseQwikMethodUsage , self :: use_qwik_valid_lexical_scope :: UseQwikValidLexicalScope , self :: use_regexp_exec :: UseRegexpExec , self :: use_sorted_classes :: UseSortedClasses , self :: use_spread :: UseSpread , self :: use_vue_define_macros_order :: UseVueDefineMacrosOrder , self :: use_vue_multi_word_component_names :: UseVueMultiWordComponentNames ,] } } +declare_lint_group! { pub Nursery { name : "nursery" , rules : [self :: no_continue :: NoContinue , self :: no_deprecated_imports :: NoDeprecatedImports , self :: no_duplicated_spread_props :: NoDuplicatedSpreadProps , self :: no_empty_source :: NoEmptySource , self :: no_equals_to_null :: NoEqualsToNull , self :: no_floating_promises :: NoFloatingPromises , self :: no_for_in :: NoForIn , self :: no_import_cycles :: NoImportCycles , self :: no_increment_decrement :: NoIncrementDecrement , self :: no_jsx_literals :: NoJsxLiterals , self :: no_leaked_render :: NoLeakedRender , self :: no_misused_promises :: NoMisusedPromises , self :: no_multi_str :: NoMultiStr , self :: no_next_async_client_component :: NoNextAsyncClientComponent , self :: no_parameters_only_used_in_recursion :: NoParametersOnlyUsedInRecursion , self :: no_playwright_element_handle :: NoPlaywrightElementHandle , self :: no_playwright_eval :: NoPlaywrightEval , self :: no_playwright_force_option :: NoPlaywrightForceOption , self :: no_playwright_missing_await :: NoPlaywrightMissingAwait , self :: no_playwright_networkidle :: NoPlaywrightNetworkidle , self :: no_playwright_page_pause :: NoPlaywrightPagePause , self :: no_playwright_useless_await :: NoPlaywrightUselessAwait , self :: no_playwright_wait_for_navigation :: NoPlaywrightWaitForNavigation , self :: no_playwright_wait_for_selector :: NoPlaywrightWaitForSelector , self :: no_playwright_wait_for_timeout :: NoPlaywrightWaitForTimeout , self :: no_proto :: NoProto , self :: no_react_forward_ref :: NoReactForwardRef , self :: no_shadow :: NoShadow , self :: no_sync_scripts :: NoSyncScripts , self :: no_ternary :: NoTernary , self :: no_unknown_attribute :: NoUnknownAttribute , self :: no_unnecessary_conditions :: NoUnnecessaryConditions , self :: no_unresolved_imports :: NoUnresolvedImports , self :: no_unused_expressions :: NoUnusedExpressions , self :: no_useless_catch_binding :: NoUselessCatchBinding , self :: no_useless_undefined :: NoUselessUndefined , self :: no_vue_data_object_declaration :: NoVueDataObjectDeclaration , self :: no_vue_duplicate_keys :: NoVueDuplicateKeys , self :: no_vue_reserved_keys :: NoVueReservedKeys , self :: no_vue_reserved_props :: NoVueReservedProps , self :: use_array_sort_compare :: UseArraySortCompare , self :: use_consistent_arrow_return :: UseConsistentArrowReturn , self :: use_exhaustive_switch_cases :: UseExhaustiveSwitchCases , self :: use_explicit_type :: UseExplicitType , self :: use_find :: UseFind , self :: use_max_params :: UseMaxParams , self :: use_playwright_valid_describe_callback :: UsePlaywrightValidDescribeCallback , self :: use_qwik_method_usage :: UseQwikMethodUsage , self :: use_qwik_valid_lexical_scope :: UseQwikValidLexicalScope , self :: use_regexp_exec :: UseRegexpExec , self :: use_sorted_classes :: UseSortedClasses , self :: use_spread :: UseSpread , self :: use_vue_define_macros_order :: UseVueDefineMacrosOrder , self :: use_vue_multi_word_component_names :: UseVueMultiWordComponentNames ,] } } diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_missing_await.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_missing_await.rs index a4ca2aa809cd..bb501781af23 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_playwright_missing_await.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_playwright_missing_await.rs @@ -5,8 +5,10 @@ use biome_analyze::{ use biome_console::markup; use biome_diagnostics::Applicability; use biome_js_factory::make; -use biome_js_syntax::{AnyJsExpression, JsArrowFunctionExpression, JsCallExpression, JsModule, T}; -use biome_rowan::{AstNode, BatchMutationExt, TokenText}; +use biome_js_syntax::{ + AnyJsExpression, JsArrowFunctionExpression, JsCallExpression, JsModule, JsSyntaxKind, +}; +use biome_rowan::{AstNode, BatchMutationExt, TokenText, TriviaPieceKind}; use crate::{JsRuleAction, ast_utils::is_in_async_function}; @@ -158,7 +160,7 @@ impl Rule for NoPlaywrightMissingAwait { }, ) .note(markup! { - "Add ""await"" before the expect call or return the promise." + "The ""expect.poll"" method converts any synchronous expect to an asynchronous polling one. Add ""await"" before the call or return the promise." }), ), MissingAwaitType::TestStep => Some( @@ -170,7 +172,7 @@ impl Rule for NoPlaywrightMissingAwait { }, ) .note(markup! { - "Add ""await"" before the test.step call or return the promise." + "Test steps are asynchronous. Add ""await"" before ""test.step()"" or return the promise." }), ), } @@ -190,38 +192,41 @@ impl Rule for NoPlaywrightMissingAwait { && !is_call_awaited_or_returned(&promise_combinator) { let mut mutation = ctx.root().begin(); - let await_expr = make::js_await_expression( - make::token(T![await]), - promise_combinator.clone().into(), - ); + let expression: AnyJsExpression = promise_combinator.clone().into(); + let trimmed_expression = expression.clone().trim_comments_and_trivia()?; + let await_expr = AnyJsExpression::JsAwaitExpression(make::js_await_expression( + make::token(JsSyntaxKind::AWAIT_KW) + .with_trailing_trivia([(TriviaPieceKind::Whitespace, " ")]), + trimmed_expression, + )); - mutation.replace_element( - promise_combinator.into_syntax().into(), - await_expr.into_syntax().into(), - ); + mutation.replace_node_transfer_trivia(expression, await_expr)?; return Some(JsRuleAction::new( ctx.metadata().action_category(ctx.category(), ctx.group()), Applicability::MaybeIncorrect, - markup! { "Add await to Promise combinator" }.to_owned(), + markup! { "Add ""await"" before the Promise combinator." } + .to_owned(), mutation, )); } // Normal case: fix the call directly let mut mutation = ctx.root().begin(); - let await_expr = - make::js_await_expression(make::token(T![await]), call_expr.clone().into()); + let expression: AnyJsExpression = call_expr.clone().into(); + let trimmed_expression = expression.clone().trim_comments_and_trivia()?; + let await_expr = AnyJsExpression::JsAwaitExpression(make::js_await_expression( + make::token(JsSyntaxKind::AWAIT_KW) + .with_trailing_trivia([(TriviaPieceKind::Whitespace, " ")]), + trimmed_expression, + )); - mutation.replace_element( - call_expr.clone().into_syntax().into(), - await_expr.into_syntax().into(), - ); + mutation.replace_node_transfer_trivia(expression, await_expr)?; Some(JsRuleAction::new( ctx.metadata().action_category(ctx.category(), ctx.group()), Applicability::MaybeIncorrect, - markup! { "Add await" }.to_owned(), + markup! { "Add ""await"" before the call." }.to_owned(), mutation, )) } diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_skipped_test.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_skipped_test.rs deleted file mode 100644 index 963c8653eafd..000000000000 --- a/crates/biome_js_analyze/src/lint/nursery/no_playwright_skipped_test.rs +++ /dev/null @@ -1,129 +0,0 @@ -use biome_analyze::{ - Ast, Rule, RuleDiagnostic, RuleDomain, RuleSource, context::RuleContext, declare_lint_rule, -}; -use biome_console::markup; -use biome_js_syntax::{JsCallExpression, JsStaticMemberExpression}; -use biome_rowan::AstNode; - -declare_lint_rule! { - /// Disallow usage of `.skip` annotation in Playwright tests. - /// - /// Skipped tests using `.skip` should typically not be committed to version control. - /// They indicate incomplete work that should either be completed or removed. - /// - /// ## Examples - /// - /// ### Invalid - /// - /// ```js,expect_diagnostic - /// test.skip('skip this test', async ({ page }) => {}); - /// ``` - /// - /// ```js,expect_diagnostic - /// test.describe.skip('skip suite', () => { - /// test('one', async ({ page }) => {}); - /// }); - /// ``` - /// - /// ### Valid - /// - /// ```js - /// test('this test', async ({ page }) => {}); - /// ``` - /// - /// ```js - /// test.describe('test suite', () => { - /// test('one', async ({ page }) => {}); - /// }); - /// ``` - /// - pub NoPlaywrightSkippedTest { - version: "next", - name: "noPlaywrightSkippedTest", - language: "js", - sources: &[RuleSource::EslintPlaywright("no-skipped-test").same()], - recommended: false, - domains: &[RuleDomain::Playwright], - } -} - -impl Rule for NoPlaywrightSkippedTest { - type Query = Ast; - type State = (); - type Signals = Option; - type Options = (); - - fn run(ctx: &RuleContext) -> Self::Signals { - let call_expr = ctx.query(); - let callee = call_expr.callee().ok()?; - - // Check if this is a member expression like test.skip() or describe.skip() - let member_expr = JsStaticMemberExpression::cast_ref(callee.syntax())?; - - // Check if the member being accessed is "skip" - let member_name = member_expr.member().ok()?; - let member_text = member_name.as_js_name()?.value_token().ok()?; - - if member_text.text_trimmed() != "skip" { - return None; - } - - // Check if the object is test/describe or a chain like test.describe - let object = member_expr.object().ok()?; - - fn is_test_or_describe_object(expr: &biome_js_syntax::AnyJsExpression) -> bool { - match expr { - biome_js_syntax::AnyJsExpression::JsIdentifierExpression(id) => { - if let Ok(name) = id.name() - && let Ok(token) = name.value_token() - { - let text = token.text_trimmed(); - return text == "test" || text == "describe"; - } - false - } - biome_js_syntax::AnyJsExpression::JsStaticMemberExpression(member) => { - // Handle test.describe.skip or similar chains - if let Ok(member_name) = member.member() - && let Some(name) = member_name.as_js_name() - && let Ok(token) = name.value_token() - { - let text = token.text_trimmed(); - if (text == "describe" || text == "serial" || text == "parallel") - && let Ok(obj) = member.object() - { - return is_test_or_describe_object(&obj); - } - } - false - } - _ => false, - } - } - - if is_test_or_describe_object(&object) { - Some(()) - } else { - None - } - } - - fn diagnostic(ctx: &RuleContext, _: &Self::State) -> Option { - let node = ctx.query(); - Some( - RuleDiagnostic::new( - rule_category!(), - node.range(), - markup! { - "Unexpected skipped test using "".skip"" annotation." - }, - ) - .note(markup! { - "Skipped tests should not be committed to version control." - }) - .note(markup! { - "Either remove the test or complete the implementation and remove the "".skip"" annotation." - }), - ) - } -} diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_valid_describe_callback.rs b/crates/biome_js_analyze/src/lint/nursery/use_playwright_valid_describe_callback.rs similarity index 97% rename from crates/biome_js_analyze/src/lint/nursery/no_playwright_valid_describe_callback.rs rename to crates/biome_js_analyze/src/lint/nursery/use_playwright_valid_describe_callback.rs index d70fb15923fd..8d711f65c1fe 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_playwright_valid_describe_callback.rs +++ b/crates/biome_js_analyze/src/lint/nursery/use_playwright_valid_describe_callback.rs @@ -42,9 +42,9 @@ declare_lint_rule! { /// }); /// ``` /// - pub NoPlaywrightValidDescribeCallback { + pub UsePlaywrightValidDescribeCallback { version: "next", - name: "noPlaywrightValidDescribeCallback", + name: "usePlaywrightValidDescribeCallback", language: "js", sources: &[RuleSource::EslintPlaywright("valid-describe-callback").same()], recommended: false, @@ -59,7 +59,7 @@ pub enum InvalidReason { NotFunction, } -impl Rule for NoPlaywrightValidDescribeCallback { +impl Rule for UsePlaywrightValidDescribeCallback { type Query = Ast; type State = InvalidReason; type Signals = Option; diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/bracket-notation.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/bracket-notation.js deleted file mode 100644 index 9d8c2fed33b6..000000000000 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/bracket-notation.js +++ /dev/null @@ -1,12 +0,0 @@ -// Test bracket notation for element handles -const handle1 = await page["$"]("button"); - -const handle2 = await page[`$`]("button"); - -const handles1 = await page["$$"]("a"); - -const handles2 = await page[`$$`]("a"); - -await this.page["$"]("input"); - -await this.page[`$$`]("div"); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/bracket-notation.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/bracket-notation.js.snap deleted file mode 100644 index cdf1a0688733..000000000000 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/bracket-notation.js.snap +++ /dev/null @@ -1,20 +0,0 @@ ---- -source: crates/biome_js_analyze/tests/spec_tests.rs -expression: bracket-notation.js ---- -# Input -```js -// Test bracket notation for element handles -const handle1 = await page["$"]("button"); - -const handle2 = await page[`$`]("button"); - -const handles1 = await page["$$"]("a"); - -const handles2 = await page[`$$`]("a"); - -await this.page["$"]("input"); - -await this.page[`$$`]("div"); - -``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/dollar-dollar.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/dollar-dollar.js index f39696719c28..fb5ca5362429 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/dollar-dollar.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/dollar-dollar.js @@ -1,2 +1,3 @@ +/* should generate diagnostics */ const buttons = await page.$$('.btn'); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/dollar-dollar.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/dollar-dollar.js.snap index cc6723b2d3e7..ad86721ccc6d 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/dollar-dollar.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/dollar-dollar.js.snap @@ -1,10 +1,10 @@ --- source: crates/biome_js_analyze/tests/spec_tests.rs -assertion_line: 152 expression: dollar-dollar.js --- # Input ```js +/* should generate diagnostics */ const buttons = await page.$$('.btn'); @@ -12,13 +12,14 @@ const buttons = await page.$$('.btn'); # Diagnostics ``` -dollar-dollar.js:1:23 lint/nursery/noPlaywrightElementHandle ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +dollar-dollar.js:2:23 lint/nursery/noPlaywrightElementHandle ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Unexpected use of element handles. - > 1 │ const buttons = await page.$$('.btn'); + 1 │ /* should generate diagnostics */ + > 2 │ const buttons = await page.$$('.btn'); │ ^^^^^^^^^^^^^^^ - 2 │ + 3 │ i Element handles like page.$$() are discouraged. diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/dollar.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/dollar.js index 37678605f932..3399a4d689f6 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/dollar.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/dollar.js @@ -1,2 +1,3 @@ +/* should generate diagnostics */ const button = await page.$('button'); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/dollar.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/dollar.js.snap index 5e75193b979f..8ce798707db8 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/dollar.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/dollar.js.snap @@ -1,10 +1,10 @@ --- source: crates/biome_js_analyze/tests/spec_tests.rs -assertion_line: 152 expression: dollar.js --- # Input ```js +/* should generate diagnostics */ const button = await page.$('button'); @@ -12,13 +12,14 @@ const button = await page.$('button'); # Diagnostics ``` -dollar.js:1:22 lint/nursery/noPlaywrightElementHandle ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +dollar.js:2:22 lint/nursery/noPlaywrightElementHandle ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Unexpected use of element handles. - > 1 │ const button = await page.$('button'); + 1 │ /* should generate diagnostics */ + > 2 │ const button = await page.$('button'); │ ^^^^^^^^^^^^^^^^ - 2 │ + 3 │ i Element handles like page.$() are discouraged. diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/frame.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/frame.js index fd5a901bf9c2..b30b9aa757c3 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/frame.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/frame.js @@ -1,2 +1,3 @@ +/* should generate diagnostics */ const element = await frame.$('#element'); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/frame.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/frame.js.snap index 0e49347117db..fcb0bbdff878 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/frame.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/frame.js.snap @@ -1,10 +1,10 @@ --- source: crates/biome_js_analyze/tests/spec_tests.rs -assertion_line: 152 expression: frame.js --- # Input ```js +/* should generate diagnostics */ const element = await frame.$('#element'); @@ -12,13 +12,14 @@ const element = await frame.$('#element'); # Diagnostics ``` -frame.js:1:23 lint/nursery/noPlaywrightElementHandle ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +frame.js:2:23 lint/nursery/noPlaywrightElementHandle ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Unexpected use of element handles. - > 1 │ const element = await frame.$('#element'); + 1 │ /* should generate diagnostics */ + > 2 │ const element = await frame.$('#element'); │ ^^^^^^^^^^^^^^^^^^^ - 2 │ + 3 │ i Element handles like page.$() are discouraged. diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/nested-calls.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/nested-calls.js index fe7269fbf94b..7037440bdd02 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/nested-calls.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/nested-calls.js @@ -1,3 +1,4 @@ +/* should generate diagnostics */ // Test nested element handle calls const button1 = await (await page.$("button")); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/nested-calls.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/nested-calls.js.snap index bfe09b2ceeaf..82377467053e 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/nested-calls.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/nested-calls.js.snap @@ -4,6 +4,7 @@ expression: nested-calls.js --- # Input ```js +/* should generate diagnostics */ // Test nested element handle calls const button1 = await (await page.$("button")); @@ -18,15 +19,16 @@ handle = await page.$("input"); # Diagnostics ``` -nested-calls.js:2:30 lint/nursery/noPlaywrightElementHandle ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +nested-calls.js:3:30 lint/nursery/noPlaywrightElementHandle ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Unexpected use of element handles. - 1 │ // Test nested element handle calls - > 2 │ const button1 = await (await page.$("button")); + 1 │ /* should generate diagnostics */ + 2 │ // Test nested element handle calls + > 3 │ const button1 = await (await page.$("button")); │ ^^^^^^^^^^^^^^^^ - 3 │ - 4 │ const button2 = await page.$("#foo"); + 4 │ + 5 │ const button2 = await page.$("#foo"); i Element handles like page.$() are discouraged. @@ -38,16 +40,16 @@ nested-calls.js:2:30 lint/nursery/noPlaywrightElementHandle ━━━━━━ ``` ``` -nested-calls.js:4:23 lint/nursery/noPlaywrightElementHandle ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +nested-calls.js:5:23 lint/nursery/noPlaywrightElementHandle ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Unexpected use of element handles. - 2 │ const button1 = await (await page.$("button")); - 3 │ - > 4 │ const button2 = await page.$("#foo"); + 3 │ const button1 = await (await page.$("button")); + 4 │ + > 5 │ const button2 = await page.$("#foo"); │ ^^^^^^^^^^^^^^ - 5 │ - 6 │ await (await page.$$("div")); + 6 │ + 7 │ await (await page.$$("div")); i Element handles like page.$() are discouraged. @@ -59,16 +61,16 @@ nested-calls.js:4:23 lint/nursery/noPlaywrightElementHandle ━━━━━━ ``` ``` -nested-calls.js:6:14 lint/nursery/noPlaywrightElementHandle ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +nested-calls.js:7:14 lint/nursery/noPlaywrightElementHandle ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Unexpected use of element handles. - 4 │ const button2 = await page.$("#foo"); - 5 │ - > 6 │ await (await page.$$("div")); + 5 │ const button2 = await page.$("#foo"); + 6 │ + > 7 │ await (await page.$$("div")); │ ^^^^^^^^^^^^^^ - 7 │ - 8 │ let handle; + 8 │ + 9 │ let handle; i Element handles like page.$$() are discouraged. @@ -80,14 +82,14 @@ nested-calls.js:6:14 lint/nursery/noPlaywrightElementHandle ━━━━━━ ``` ``` -nested-calls.js:9:16 lint/nursery/noPlaywrightElementHandle ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +nested-calls.js:10:16 lint/nursery/noPlaywrightElementHandle ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Unexpected use of element handles. - 8 │ let handle; - > 9 │ handle = await page.$("input"); + 9 │ let handle; + > 10 │ handle = await page.$("input"); │ ^^^^^^^^^^^^^^^ - 10 │ + 11 │ i Element handles like page.$() are discouraged. diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/valid/locator.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/valid/locator.js index 2d844638047c..7da7c5cc9818 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/valid/locator.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/valid/locator.js @@ -1,3 +1,4 @@ +/* should not generate diagnostics */ const button = page.locator('button'); await button.click(); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/valid/locator.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/valid/locator.js.snap index 82584ecb9a28..5b0e059198e8 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/valid/locator.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/valid/locator.js.snap @@ -1,10 +1,10 @@ --- source: crates/biome_js_analyze/tests/spec_tests.rs -assertion_line: 152 expression: locator.js --- # Input ```js +/* should not generate diagnostics */ const button = page.locator('button'); await button.click(); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/bracket-notation.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/bracket-notation.js deleted file mode 100644 index 92b934f91d31..000000000000 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/bracket-notation.js +++ /dev/null @@ -1,12 +0,0 @@ -// Test bracket notation for $eval and $$eval -await page["$eval"]("#search", el => el.value); - -await page[`$eval`]("#search", el => el.value); - -await page["$$eval"]("div", els => els.length); - -await page[`$$eval`]("div", els => els.length); - -await this.page["$eval"]("#input", el => el.checked); - -await this.page[`$$eval`]("span", els => els.map(e => e.textContent)); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/bracket-notation.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/bracket-notation.js.snap deleted file mode 100644 index 380dc872aa47..000000000000 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/bracket-notation.js.snap +++ /dev/null @@ -1,20 +0,0 @@ ---- -source: crates/biome_js_analyze/tests/spec_tests.rs -expression: bracket-notation.js ---- -# Input -```js -// Test bracket notation for $eval and $$eval -await page["$eval"]("#search", el => el.value); - -await page[`$eval`]("#search", el => el.value); - -await page["$$eval"]("div", els => els.length); - -await page[`$$eval`]("div", els => els.length); - -await this.page["$eval"]("#input", el => el.checked); - -await this.page[`$$eval`]("span", els => els.map(e => e.textContent)); - -``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/eval-all.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/eval-all.js index 3c05cd63d19b..3bd708576f1e 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/eval-all.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/eval-all.js @@ -1,2 +1,3 @@ +/* should generate diagnostics */ const texts = await page.$$eval('.foo', els => els.map(el => el.textContent)); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/eval-all.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/eval-all.js.snap index 421ffd7924e3..f5b19d741cf1 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/eval-all.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/eval-all.js.snap @@ -1,10 +1,10 @@ --- source: crates/biome_js_analyze/tests/spec_tests.rs -assertion_line: 152 expression: eval-all.js --- # Input ```js +/* should generate diagnostics */ const texts = await page.$$eval('.foo', els => els.map(el => el.textContent)); @@ -12,13 +12,14 @@ const texts = await page.$$eval('.foo', els => els.map(el => el.textContent)); # Diagnostics ``` -eval-all.js:1:21 lint/nursery/noPlaywrightEval ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +eval-all.js:2:21 lint/nursery/noPlaywrightEval ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Unexpected use of page.$$eval(). - > 1 │ const texts = await page.$$eval('.foo', els => els.map(el => el.textContent)); + 1 │ /* should generate diagnostics */ + > 2 │ const texts = await page.$$eval('.foo', els => els.map(el => el.textContent)); │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 2 │ + 3 │ i Use locator.evaluateAll() instead. diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/eval.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/eval.js index 702d6df01aea..6e8b9dbaf854 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/eval.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/eval.js @@ -1,2 +1,3 @@ +/* should generate diagnostics */ await page.$eval('.foo', el => el.textContent); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/eval.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/eval.js.snap index 9046a2c123d6..4c8b2a43c7ae 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/eval.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/eval.js.snap @@ -1,10 +1,10 @@ --- source: crates/biome_js_analyze/tests/spec_tests.rs -assertion_line: 152 expression: eval.js --- # Input ```js +/* should generate diagnostics */ await page.$eval('.foo', el => el.textContent); @@ -12,13 +12,14 @@ await page.$eval('.foo', el => el.textContent); # Diagnostics ``` -eval.js:1:7 lint/nursery/noPlaywrightEval ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +eval.js:2:7 lint/nursery/noPlaywrightEval ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Unexpected use of page.$eval(). - > 1 │ await page.$eval('.foo', el => el.textContent); + 1 │ /* should generate diagnostics */ + > 2 │ await page.$eval('.foo', el => el.textContent); │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 2 │ + 3 │ i Use locator.evaluate() instead. diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/multiple-arguments.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/multiple-arguments.js index e724511ec5e6..7bd6751492ba 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/multiple-arguments.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/multiple-arguments.js @@ -1,3 +1,4 @@ +/* should generate diagnostics */ // Test $eval and $$eval with multiple arguments await page.$eval(".main-container", (e, suffix) => e.outerHTML + suffix, "hello"); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/multiple-arguments.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/multiple-arguments.js.snap index b8a48fbecf6a..9ff4e0d3ca7e 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/multiple-arguments.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/invalid/multiple-arguments.js.snap @@ -4,6 +4,7 @@ expression: multiple-arguments.js --- # Input ```js +/* should generate diagnostics */ // Test $eval and $$eval with multiple arguments await page.$eval(".main-container", (e, suffix) => e.outerHTML + suffix, "hello"); @@ -17,15 +18,16 @@ await this.page.$$eval("span", (els, className) => els.filter(e => e.className = # Diagnostics ``` -multiple-arguments.js:2:7 lint/nursery/noPlaywrightEval ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +multiple-arguments.js:3:7 lint/nursery/noPlaywrightEval ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Unexpected use of page.$eval(). - 1 │ // Test $eval and $$eval with multiple arguments - > 2 │ await page.$eval(".main-container", (e, suffix) => e.outerHTML + suffix, "hello"); + 1 │ /* should generate diagnostics */ + 2 │ // Test $eval and $$eval with multiple arguments + > 3 │ await page.$eval(".main-container", (e, suffix) => e.outerHTML + suffix, "hello"); │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 3 │ - 4 │ await page.$$eval("div", (divs, min) => divs.length >= min, 10); + 4 │ + 5 │ await page.$$eval("div", (divs, min) => divs.length >= min, 10); i Use locator.evaluate() instead. @@ -35,16 +37,16 @@ multiple-arguments.js:2:7 lint/nursery/noPlaywrightEval ━━━━━━━━ ``` ``` -multiple-arguments.js:4:7 lint/nursery/noPlaywrightEval ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +multiple-arguments.js:5:7 lint/nursery/noPlaywrightEval ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Unexpected use of page.$$eval(). - 2 │ await page.$eval(".main-container", (e, suffix) => e.outerHTML + suffix, "hello"); - 3 │ - > 4 │ await page.$$eval("div", (divs, min) => divs.length >= min, 10); + 3 │ await page.$eval(".main-container", (e, suffix) => e.outerHTML + suffix, "hello"); + 4 │ + > 5 │ await page.$$eval("div", (divs, min) => divs.length >= min, 10); │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 5 │ - 6 │ await page.$eval("#search", (el, prop) => el[prop], "value"); + 6 │ + 7 │ await page.$eval("#search", (el, prop) => el[prop], "value"); i Use locator.evaluateAll() instead. @@ -54,16 +56,16 @@ multiple-arguments.js:4:7 lint/nursery/noPlaywrightEval ━━━━━━━━ ``` ``` -multiple-arguments.js:6:7 lint/nursery/noPlaywrightEval ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +multiple-arguments.js:7:7 lint/nursery/noPlaywrightEval ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Unexpected use of page.$eval(). - 4 │ await page.$$eval("div", (divs, min) => divs.length >= min, 10); - 5 │ - > 6 │ await page.$eval("#search", (el, prop) => el[prop], "value"); + 5 │ await page.$$eval("div", (divs, min) => divs.length >= min, 10); + 6 │ + > 7 │ await page.$eval("#search", (el, prop) => el[prop], "value"); │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 7 │ - 8 │ await this.page.$$eval("span", (els, className) => els.filter(e => e.className === className), "active"); + 8 │ + 9 │ await this.page.$$eval("span", (els, className) => els.filter(e => e.className === className), "active"); i Use locator.evaluate() instead. @@ -73,15 +75,15 @@ multiple-arguments.js:6:7 lint/nursery/noPlaywrightEval ━━━━━━━━ ``` ``` -multiple-arguments.js:8:7 lint/nursery/noPlaywrightEval ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +multiple-arguments.js:9:7 lint/nursery/noPlaywrightEval ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Unexpected use of page.$$eval(). - 6 │ await page.$eval("#search", (el, prop) => el[prop], "value"); - 7 │ - > 8 │ await this.page.$$eval("span", (els, className) => els.filter(e => e.className === className), "active"); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 9 │ + 7 │ await page.$eval("#search", (el, prop) => el[prop], "value"); + 8 │ + > 9 │ await this.page.$$eval("span", (els, className) => els.filter(e => e.className === className), "active"); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 10 │ i Use locator.evaluateAll() instead. diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/valid/locator-evaluate.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/valid/locator-evaluate.js index 434d29c63576..e589133bf25c 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/valid/locator-evaluate.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/valid/locator-evaluate.js @@ -1,3 +1,4 @@ +/* should not generate diagnostics */ const text = await page.locator('.foo').evaluate(el => el.textContent); const texts = await page.locator('.foo').evaluateAll(els => els.map(el => el.textContent)); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/valid/locator-evaluate.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/valid/locator-evaluate.js.snap index 4805d424a45c..d1861079fea5 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/valid/locator-evaluate.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightEval/valid/locator-evaluate.js.snap @@ -1,10 +1,10 @@ --- source: crates/biome_js_analyze/tests/spec_tests.rs -assertion_line: 152 expression: locator-evaluate.js --- # Input ```js +/* should not generate diagnostics */ const text = await page.locator('.foo').evaluate(el => el.textContent); const texts = await page.locator('.foo').evaluateAll(els => els.map(el => el.textContent)); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/bracket-notation-methods.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/bracket-notation-methods.js deleted file mode 100644 index 462953fb6af6..000000000000 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/bracket-notation-methods.js +++ /dev/null @@ -1,12 +0,0 @@ -// Test bracket notation for methods with force option -await page.locator("button")["click"]({ force: true }); - -await page.locator("button")[`click`]({ force: true }); - -await page.locator("input")["fill"]("text", { force: true }); - -await page.locator("input")[`fill`]("text", { force: true }); - -await page.locator("checkbox")["check"]({ force: true }); - -await page.locator("checkbox")[`check`]({ force: true }); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/bracket-notation-methods.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/bracket-notation-methods.js.snap deleted file mode 100644 index 21a6216032e8..000000000000 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/bracket-notation-methods.js.snap +++ /dev/null @@ -1,20 +0,0 @@ ---- -source: crates/biome_js_analyze/tests/spec_tests.rs -expression: bracket-notation-methods.js ---- -# Input -```js -// Test bracket notation for methods with force option -await page.locator("button")["click"]({ force: true }); - -await page.locator("button")[`click`]({ force: true }); - -await page.locator("input")["fill"]("text", { force: true }); - -await page.locator("input")[`fill`]("text", { force: true }); - -await page.locator("checkbox")["check"]({ force: true }); - -await page.locator("checkbox")[`check`]({ force: true }); - -``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/click.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/click.js index 161bf72fbcd3..435d4e6fe0c3 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/click.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/click.js @@ -1,3 +1,4 @@ +/* should generate diagnostics */ await page.locator('button').click({ force: true }); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/click.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/click.js.snap index 65ae29187720..4f8ea5cf79d4 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/click.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/click.js.snap @@ -1,10 +1,10 @@ --- source: crates/biome_js_analyze/tests/spec_tests.rs -assertion_line: 152 expression: click.js --- # Input ```js +/* should generate diagnostics */ await page.locator('button').click({ force: true }); @@ -13,13 +13,14 @@ await page.locator('button').click({ force: true }); # Diagnostics ``` -click.js:1:7 lint/nursery/noPlaywrightForceOption ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +click.js:2:7 lint/nursery/noPlaywrightForceOption ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Unexpected use of { force: true } option. - > 1 │ await page.locator('button').click({ force: true }); + 1 │ /* should generate diagnostics */ + > 2 │ await page.locator('button').click({ force: true }); │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 2 │ + 3 │ i The force option bypasses actionability checks and can lead to unreliable tests. diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/fill.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/fill.js index 12c6deca2821..5f2c54e814eb 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/fill.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/fill.js @@ -1,3 +1,4 @@ +/* should generate diagnostics */ await page.locator('input').fill('text', { force: true }); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/fill.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/fill.js.snap index e5bff191ebbc..016f15bcdfe6 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/fill.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/fill.js.snap @@ -1,10 +1,10 @@ --- source: crates/biome_js_analyze/tests/spec_tests.rs -assertion_line: 152 expression: fill.js --- # Input ```js +/* should generate diagnostics */ await page.locator('input').fill('text', { force: true }); @@ -13,13 +13,14 @@ await page.locator('input').fill('text', { force: true }); # Diagnostics ``` -fill.js:1:7 lint/nursery/noPlaywrightForceOption ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +fill.js:2:7 lint/nursery/noPlaywrightForceOption ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Unexpected use of { force: true } option. - > 1 │ await page.locator('input').fill('text', { force: true }); + 1 │ /* should generate diagnostics */ + > 2 │ await page.locator('input').fill('text', { force: true }); │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 2 │ + 3 │ i The force option bypasses actionability checks and can lead to unreliable tests. diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/force-string-literal.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/force-string-literal.js index ade21bf11b79..6b978797dfbc 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/force-string-literal.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/force-string-literal.js @@ -1,3 +1,4 @@ +/* should generate diagnostics */ // Test string literal syntax for force option await page.locator('button').click({ "force": true }); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/force-string-literal.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/force-string-literal.js.snap index 8e9978d46fc8..2338aacbd819 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/force-string-literal.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/force-string-literal.js.snap @@ -4,6 +4,7 @@ expression: force-string-literal.js --- # Input ```js +/* should generate diagnostics */ // Test string literal syntax for force option await page.locator('button').click({ "force": true }); @@ -13,15 +14,16 @@ await page.locator('input').fill('text', { 'force': true }); # Diagnostics ``` -force-string-literal.js:2:7 lint/nursery/noPlaywrightForceOption ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +force-string-literal.js:3:7 lint/nursery/noPlaywrightForceOption ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Unexpected use of { force: true } option. - 1 │ // Test string literal syntax for force option - > 2 │ await page.locator('button').click({ "force": true }); + 1 │ /* should generate diagnostics */ + 2 │ // Test string literal syntax for force option + > 3 │ await page.locator('button').click({ "force": true }); │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 3 │ - 4 │ await page.locator('input').fill('text', { 'force': true }); + 4 │ + 5 │ await page.locator('input').fill('text', { 'force': true }); i The force option bypasses actionability checks and can lead to unreliable tests. @@ -31,15 +33,15 @@ force-string-literal.js:2:7 lint/nursery/noPlaywrightForceOption ━━━━━ ``` ``` -force-string-literal.js:4:7 lint/nursery/noPlaywrightForceOption ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +force-string-literal.js:5:7 lint/nursery/noPlaywrightForceOption ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Unexpected use of { force: true } option. - 2 │ await page.locator('button').click({ "force": true }); - 3 │ - > 4 │ await page.locator('input').fill('text', { 'force': true }); + 3 │ await page.locator('button').click({ "force": true }); + 4 │ + > 5 │ await page.locator('input').fill('text', { 'force': true }); │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 5 │ + 6 │ i The force option bypasses actionability checks and can lead to unreliable tests. diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/select-option-force.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/select-option-force.js index dd100e200c49..889212fbdb41 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/select-option-force.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/select-option-force.js @@ -1,3 +1,4 @@ +/* should generate diagnostics */ // Test selectOption with force option (it has different signature with first options argument) await page.locator("select").selectOption({ label: "Blue" }, { force: true }); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/select-option-force.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/select-option-force.js.snap index b43e8a851897..4a56a748aee4 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/select-option-force.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/invalid/select-option-force.js.snap @@ -4,6 +4,7 @@ expression: select-option-force.js --- # Input ```js +/* should generate diagnostics */ // Test selectOption with force option (it has different signature with first options argument) await page.locator("select").selectOption({ label: "Blue" }, { force: true }); @@ -15,15 +16,16 @@ await page.locator("select").selectOption(["option1", "option2"], { force: true # Diagnostics ``` -select-option-force.js:2:7 lint/nursery/noPlaywrightForceOption ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +select-option-force.js:3:7 lint/nursery/noPlaywrightForceOption ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Unexpected use of { force: true } option. - 1 │ // Test selectOption with force option (it has different signature with first options argument) - > 2 │ await page.locator("select").selectOption({ label: "Blue" }, { force: true }); + 1 │ /* should generate diagnostics */ + 2 │ // Test selectOption with force option (it has different signature with first options argument) + > 3 │ await page.locator("select").selectOption({ label: "Blue" }, { force: true }); │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 3 │ - 4 │ await page.locator("select").selectOption("value", { force: true }); + 4 │ + 5 │ await page.locator("select").selectOption("value", { force: true }); i The force option bypasses actionability checks and can lead to unreliable tests. @@ -33,16 +35,16 @@ select-option-force.js:2:7 lint/nursery/noPlaywrightForceOption ━━━━━ ``` ``` -select-option-force.js:4:7 lint/nursery/noPlaywrightForceOption ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +select-option-force.js:5:7 lint/nursery/noPlaywrightForceOption ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Unexpected use of { force: true } option. - 2 │ await page.locator("select").selectOption({ label: "Blue" }, { force: true }); - 3 │ - > 4 │ await page.locator("select").selectOption("value", { force: true }); + 3 │ await page.locator("select").selectOption({ label: "Blue" }, { force: true }); + 4 │ + > 5 │ await page.locator("select").selectOption("value", { force: true }); │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 5 │ - 6 │ await page.locator("select").selectOption(["option1", "option2"], { force: true }); + 6 │ + 7 │ await page.locator("select").selectOption(["option1", "option2"], { force: true }); i The force option bypasses actionability checks and can lead to unreliable tests. @@ -52,15 +54,15 @@ select-option-force.js:4:7 lint/nursery/noPlaywrightForceOption ━━━━━ ``` ``` -select-option-force.js:6:7 lint/nursery/noPlaywrightForceOption ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +select-option-force.js:7:7 lint/nursery/noPlaywrightForceOption ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Unexpected use of { force: true } option. - 4 │ await page.locator("select").selectOption("value", { force: true }); - 5 │ - > 6 │ await page.locator("select").selectOption(["option1", "option2"], { force: true }); + 5 │ await page.locator("select").selectOption("value", { force: true }); + 6 │ + > 7 │ await page.locator("select").selectOption(["option1", "option2"], { force: true }); │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 7 │ + 8 │ i The force option bypasses actionability checks and can lead to unreliable tests. diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/valid/click.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/valid/click.js index cb2e6186037a..4bea388b8595 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/valid/click.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/valid/click.js @@ -1,3 +1,4 @@ +/* should not generate diagnostics */ await page.locator('button').click(); await page.locator('check').check(); await page.locator('input').fill('text'); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/valid/click.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/valid/click.js.snap index bf9f482c2fc2..0ec9c8d85879 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/valid/click.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightForceOption/valid/click.js.snap @@ -1,10 +1,10 @@ --- source: crates/biome_js_analyze/tests/spec_tests.rs -assertion_line: 152 expression: click.js --- # Input ```js +/* should not generate diagnostics */ await page.locator('button').click(); await page.locator('check').check(); await page.locator('input').fill('text'); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/bracket-notation.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/bracket-notation.js deleted file mode 100644 index 26382aac44c6..000000000000 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/bracket-notation.js +++ /dev/null @@ -1,28 +0,0 @@ -// Test bracket notation for various Playwright async APIs -test('bracket notation for test.step', async ({ page }) => { - test["step"]('do something', async () => { - await page.click('button'); - }); -}); - -test('template literal bracket notation for test.step', async ({ page }) => { - test[`step`]('do something', async () => { - await page.click('button'); - }); -}); - -test('bracket notation for expect.soft', async ({ page }) => { - expect["soft"](page).toBeVisible(); -}); - -test('template literal for expect.soft', async ({ page }) => { - expect[`soft`](page).toBeVisible(); -}); - -test('bracket notation for matcher', async ({ page }) => { - expect(page)["toBeVisible"](); -}); - -test('template literal for matcher', async ({ page }) => { - expect(page)[`toBeVisible`](); -}); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/bracket-notation.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/bracket-notation.js.snap deleted file mode 100644 index 8802d63dbbc6..000000000000 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/bracket-notation.js.snap +++ /dev/null @@ -1,36 +0,0 @@ ---- -source: crates/biome_js_analyze/tests/spec_tests.rs -expression: bracket-notation.js ---- -# Input -```js -// Test bracket notation for various Playwright async APIs -test('bracket notation for test.step', async ({ page }) => { - test["step"]('do something', async () => { - await page.click('button'); - }); -}); - -test('template literal bracket notation for test.step', async ({ page }) => { - test[`step`]('do something', async () => { - await page.click('button'); - }); -}); - -test('bracket notation for expect.soft', async ({ page }) => { - expect["soft"](page).toBeVisible(); -}); - -test('template literal for expect.soft', async ({ page }) => { - expect[`soft`](page).toBeVisible(); -}); - -test('bracket notation for matcher', async ({ page }) => { - expect(page)["toBeVisible"](); -}); - -test('template literal for matcher', async ({ page }) => { - expect(page)[`toBeVisible`](); -}); - -``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/expect-async-matcher.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/expect-async-matcher.js index 8a9748fc274b..e1200de4f8ba 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/expect-async-matcher.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/expect-async-matcher.js @@ -1,3 +1,4 @@ +/* should generate diagnostics */ test('example', async ({ page }) => { expect(page.locator('body')).toBeVisible(); }); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/expect-async-matcher.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/expect-async-matcher.js.snap index 68db8d115b73..08133193f8f6 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/expect-async-matcher.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/expect-async-matcher.js.snap @@ -4,6 +4,7 @@ expression: expect-async-matcher.js --- # Input ```js +/* should generate diagnostics */ test('example', async ({ page }) => { expect(page.locator('body')).toBeVisible(); }); @@ -13,26 +14,22 @@ test('example', async ({ page }) => { # Diagnostics ``` -expect-async-matcher.js:2:5 lint/nursery/noPlaywrightMissingAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━ +expect-async-matcher.js:3:5 lint/nursery/noPlaywrightMissingAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━ i Async matcher toBeVisible must be awaited or returned. - 1 │ test('example', async ({ page }) => { - > 2 │ expect(page.locator('body')).toBeVisible(); + 1 │ /* should generate diagnostics */ + 2 │ test('example', async ({ page }) => { + > 3 │ expect(page.locator('body')).toBeVisible(); │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 3 │ }); - 4 │ + 4 │ }); + 5 │ i Add await before the expect call or return the promise. - i Unsafe fix: Add await - - 1 1 │ test('example', async ({ page }) => { - 2 │ - ····expect(page.locator('body')).toBeVisible(); - 2 │ + ····await - 3 │ + ····expect(page.locator('body')).toBeVisible(); - 3 4 │ }); - 4 5 │ + i Unsafe fix: Add await before the call. + 3 │ ····await·expect(page.locator('body')).toBeVisible(); + │ ++++++ ``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/expect-poll-sync-matchers.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/expect-poll-sync-matchers.js index f7158c8c765c..e970ca324175 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/expect-poll-sync-matchers.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/expect-poll-sync-matchers.js @@ -1,3 +1,4 @@ +/* should generate diagnostics */ // expect.poll with synchronous matchers should still require await test('toBe matcher', async () => { expect.poll(() => getValue()).toBe(42); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/expect-poll-sync-matchers.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/expect-poll-sync-matchers.js.snap index 7f7e9c543ab9..0ec4d83b9a05 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/expect-poll-sync-matchers.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/expect-poll-sync-matchers.js.snap @@ -4,6 +4,7 @@ expression: expect-poll-sync-matchers.js --- # Input ```js +/* should generate diagnostics */ // expect.poll with synchronous matchers should still require await test('toBe matcher', async () => { expect.poll(() => getValue()).toBe(42); @@ -25,106 +26,82 @@ test('toStrictEqual matcher', async () => { # Diagnostics ``` -expect-poll-sync-matchers.js:3:5 lint/nursery/noPlaywrightMissingAwait FIXABLE ━━━━━━━━━━━━━━━━━━━ +expect-poll-sync-matchers.js:4:5 lint/nursery/noPlaywrightMissingAwait FIXABLE ━━━━━━━━━━━━━━━━━━━ i expect.poll must be awaited or returned. - 1 │ // expect.poll with synchronous matchers should still require await - 2 │ test('toBe matcher', async () => { - > 3 │ expect.poll(() => getValue()).toBe(42); + 2 │ // expect.poll with synchronous matchers should still require await + 3 │ test('toBe matcher', async () => { + > 4 │ expect.poll(() => getValue()).toBe(42); │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 4 │ }); - 5 │ + 5 │ }); + 6 │ - i Add await before the expect call or return the promise. + i The expect.poll method converts any synchronous expect to an asynchronous polling one. Add await before the call or return the promise. - i Unsafe fix: Add await - - 1 1 │ // expect.poll with synchronous matchers should still require await - 2 2 │ test('toBe matcher', async () => { - 3 │ - ····expect.poll(()·=>·getValue()).toBe(42); - 3 │ + ····await - 4 │ + ····expect.poll(()·=>·getValue()).toBe(42); - 4 5 │ }); - 5 6 │ + i Unsafe fix: Add await before the call. + 4 │ ····await·expect.poll(()·=>·getValue()).toBe(42); + │ ++++++ ``` ``` -expect-poll-sync-matchers.js:7:5 lint/nursery/noPlaywrightMissingAwait FIXABLE ━━━━━━━━━━━━━━━━━━━ +expect-poll-sync-matchers.js:8:5 lint/nursery/noPlaywrightMissingAwait FIXABLE ━━━━━━━━━━━━━━━━━━━ i expect.poll must be awaited or returned. - 6 │ test('toEqual matcher', async () => { - > 7 │ expect.poll(() => getObject()).toEqual({ foo: 'bar' }); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 8 │ }); - 9 │ - - i Add await before the expect call or return the promise. + 7 │ test('toEqual matcher', async () => { + > 8 │ expect.poll(() => getObject()).toEqual({ foo: 'bar' }); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 9 │ }); + 10 │ - i Unsafe fix: Add await + i The expect.poll method converts any synchronous expect to an asynchronous polling one. Add await before the call or return the promise. - 5 5 │ - 6 6 │ test('toEqual matcher', async () => { - 7 │ - ····expect.poll(()·=>·getObject()).toEqual({·foo:·'bar'·}); - 7 │ + ····await - 8 │ + ····expect.poll(()·=>·getObject()).toEqual({·foo:·'bar'·}); - 8 9 │ }); - 9 10 │ + i Unsafe fix: Add await before the call. + 8 │ ····await·expect.poll(()·=>·getObject()).toEqual({·foo:·'bar'·}); + │ ++++++ ``` ``` -expect-poll-sync-matchers.js:11:5 lint/nursery/noPlaywrightMissingAwait FIXABLE ━━━━━━━━━━━━━━━━━━ +expect-poll-sync-matchers.js:12:5 lint/nursery/noPlaywrightMissingAwait FIXABLE ━━━━━━━━━━━━━━━━━━ i expect.poll must be awaited or returned. - 10 │ test('toMatch matcher', async () => { - > 11 │ expect.poll(() => getString()).toMatch(/pattern/); + 11 │ test('toMatch matcher', async () => { + > 12 │ expect.poll(() => getString()).toMatch(/pattern/); │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 12 │ }); - 13 │ + 13 │ }); + 14 │ - i Add await before the expect call or return the promise. + i The expect.poll method converts any synchronous expect to an asynchronous polling one. Add await before the call or return the promise. - i Unsafe fix: Add await - - 9 9 │ - 10 10 │ test('toMatch matcher', async () => { - 11 │ - ····expect.poll(()·=>·getString()).toMatch(/pattern/); - 11 │ + ····await - 12 │ + ····expect.poll(()·=>·getString()).toMatch(/pattern/); - 12 13 │ }); - 13 14 │ + i Unsafe fix: Add await before the call. + 12 │ ····await·expect.poll(()·=>·getString()).toMatch(/pattern/); + │ ++++++ ``` ``` -expect-poll-sync-matchers.js:15:5 lint/nursery/noPlaywrightMissingAwait FIXABLE ━━━━━━━━━━━━━━━━━━ +expect-poll-sync-matchers.js:16:5 lint/nursery/noPlaywrightMissingAwait FIXABLE ━━━━━━━━━━━━━━━━━━ i expect.poll must be awaited or returned. - 14 │ test('toStrictEqual matcher', async () => { - > 15 │ expect.poll(() => getData()).toStrictEqual({ a: 1 }); + 15 │ test('toStrictEqual matcher', async () => { + > 16 │ expect.poll(() => getData()).toStrictEqual({ a: 1 }); │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 16 │ }); - 17 │ - - i Add await before the expect call or return the promise. + 17 │ }); + 18 │ - i Unsafe fix: Add await + i The expect.poll method converts any synchronous expect to an asynchronous polling one. Add await before the call or return the promise. - 13 13 │ - 14 14 │ test('toStrictEqual matcher', async () => { - 15 │ - ····expect.poll(()·=>·getData()).toStrictEqual({·a:·1·}); - 15 │ + ····await - 16 │ + ····expect.poll(()·=>·getData()).toStrictEqual({·a:·1·}); - 16 17 │ }); - 17 18 │ + i Unsafe fix: Add await before the call. + 16 │ ····await·expect.poll(()·=>·getData()).toStrictEqual({·a:·1·}); + │ ++++++ ``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/expect-poll.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/expect-poll.js index d3c6a39982fe..26c12274345f 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/expect-poll.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/expect-poll.js @@ -1,3 +1,4 @@ +/* should generate diagnostics */ test('example', async () => { expect.poll(() => foo).toBe(true); }); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/expect-poll.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/expect-poll.js.snap index 0082f4b70242..10c9b46c19bb 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/expect-poll.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/expect-poll.js.snap @@ -4,6 +4,7 @@ expression: expect-poll.js --- # Input ```js +/* should generate diagnostics */ test('example', async () => { expect.poll(() => foo).toBe(true); }); @@ -13,26 +14,22 @@ test('example', async () => { # Diagnostics ``` -expect-poll.js:2:5 lint/nursery/noPlaywrightMissingAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +expect-poll.js:3:5 lint/nursery/noPlaywrightMissingAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i expect.poll must be awaited or returned. - 1 │ test('example', async () => { - > 2 │ expect.poll(() => foo).toBe(true); + 1 │ /* should generate diagnostics */ + 2 │ test('example', async () => { + > 3 │ expect.poll(() => foo).toBe(true); │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 3 │ }); - 4 │ + 4 │ }); + 5 │ - i Add await before the expect call or return the promise. + i The expect.poll method converts any synchronous expect to an asynchronous polling one. Add await before the call or return the promise. - i Unsafe fix: Add await - - 1 1 │ test('example', async () => { - 2 │ - ····expect.poll(()·=>·foo).toBe(true); - 2 │ + ····await - 3 │ + ····expect.poll(()·=>·foo).toBe(true); - 3 4 │ }); - 4 5 │ + i Unsafe fix: Add await before the call. + 3 │ ····await·expect.poll(()·=>·foo).toBe(true); + │ ++++++ ``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/module-level.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/module-level.js index cf8445f21c06..a14d7c50c949 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/module-level.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/module-level.js @@ -1,3 +1,4 @@ +/* should generate diagnostics */ // Module-level code (top-level await is supported) expect(page.locator('body')).toBeVisible(); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/module-level.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/module-level.js.snap index bb4f58d7d153..91e40901d63a 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/module-level.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/module-level.js.snap @@ -4,6 +4,7 @@ expression: module-level.js --- # Input ```js +/* should generate diagnostics */ // Module-level code (top-level await is supported) expect(page.locator('body')).toBeVisible(); @@ -13,23 +14,21 @@ expect(page.locator('body')).toBeVisible(); # Diagnostics ``` -module-level.js:2:1 lint/nursery/noPlaywrightMissingAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +module-level.js:3:1 lint/nursery/noPlaywrightMissingAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Async matcher toBeVisible must be awaited or returned. - 1 │ // Module-level code (top-level await is supported) - > 2 │ expect(page.locator('body')).toBeVisible(); + 1 │ /* should generate diagnostics */ + 2 │ // Module-level code (top-level await is supported) + > 3 │ expect(page.locator('body')).toBeVisible(); │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 3 │ + 4 │ i Add await before the expect call or return the promise. - i Unsafe fix: Add await - - 1 1 │ // Module-level code (top-level await is supported) - 2 │ + await//·Module-level·code·(top-level·await·is·supported) - 2 3 │ expect(page.locator('body')).toBeVisible(); - 3 4 │ + i Unsafe fix: Add await before the call. + 3 │ await·expect(page.locator('body')).toBeVisible(); + │ ++++++ ``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/nested-expects.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/nested-expects.js index fd83bbf38005..834d4bdce85f 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/nested-expects.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/nested-expects.js @@ -1,3 +1,4 @@ +/* should generate diagnostics */ // Test nested expect calls and chaining test('nested not operator', async ({ page }) => { expect(page).not.toBeVisible(); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/nested-expects.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/nested-expects.js.snap index 51f7f6f508b1..cb9d8daeabbb 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/nested-expects.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/nested-expects.js.snap @@ -4,6 +4,7 @@ expression: nested-expects.js --- # Input ```js +/* should generate diagnostics */ // Test nested expect calls and chaining test('nested not operator', async ({ page }) => { expect(page).not.toBeVisible(); @@ -21,80 +22,62 @@ test('multiple not calls', async ({ page }) => { # Diagnostics ``` -nested-expects.js:3:5 lint/nursery/noPlaywrightMissingAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +nested-expects.js:4:5 lint/nursery/noPlaywrightMissingAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Async matcher toBeVisible must be awaited or returned. - 1 │ // Test nested expect calls and chaining - 2 │ test('nested not operator', async ({ page }) => { - > 3 │ expect(page).not.toBeVisible(); + 2 │ // Test nested expect calls and chaining + 3 │ test('nested not operator', async ({ page }) => { + > 4 │ expect(page).not.toBeVisible(); │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 4 │ }); - 5 │ + 5 │ }); + 6 │ i Add await before the expect call or return the promise. - i Unsafe fix: Add await - - 1 1 │ // Test nested expect calls and chaining - 2 2 │ test('nested not operator', async ({ page }) => { - 3 │ - ····expect(page).not.toBeVisible(); - 3 │ + ····await - 4 │ + ····expect(page).not.toBeVisible(); - 4 5 │ }); - 5 6 │ + i Unsafe fix: Add await before the call. + 4 │ ····await·expect(page).not.toBeVisible(); + │ ++++++ ``` ``` -nested-expects.js:7:5 lint/nursery/noPlaywrightMissingAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +nested-expects.js:8:5 lint/nursery/noPlaywrightMissingAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Async matcher toHaveText must be awaited or returned. - 6 │ test('chained not and soft', async ({ page }) => { - > 7 │ expect.soft(page).not.toHaveText("foo"); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 8 │ }); - 9 │ + 7 │ test('chained not and soft', async ({ page }) => { + > 8 │ expect.soft(page).not.toHaveText("foo"); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 9 │ }); + 10 │ i Add await before the expect call or return the promise. - i Unsafe fix: Add await - - 5 5 │ - 6 6 │ test('chained not and soft', async ({ page }) => { - 7 │ - ····expect.soft(page).not.toHaveText("foo"); - 7 │ + ····await - 8 │ + ····expect.soft(page).not.toHaveText("foo"); - 8 9 │ }); - 9 10 │ + i Unsafe fix: Add await before the call. + 8 │ ····await·expect.soft(page).not.toHaveText("foo"); + │ ++++++ ``` ``` -nested-expects.js:11:5 lint/nursery/noPlaywrightMissingAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +nested-expects.js:12:5 lint/nursery/noPlaywrightMissingAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Async matcher toBeEnabled must be awaited or returned. - 10 │ test('multiple not calls', async ({ page }) => { - > 11 │ expect(page).not.not.toBeEnabled(); + 11 │ test('multiple not calls', async ({ page }) => { + > 12 │ expect(page).not.not.toBeEnabled(); │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 12 │ }); - 13 │ + 13 │ }); + 14 │ i Add await before the expect call or return the promise. - i Unsafe fix: Add await - - 9 9 │ - 10 10 │ test('multiple not calls', async ({ page }) => { - 11 │ - ····expect(page).not.not.toBeEnabled(); - 11 │ + ····await - 12 │ + ····expect(page).not.not.toBeEnabled(); - 12 13 │ }); - 13 14 │ + i Unsafe fix: Add await before the call. + 12 │ ····await·expect(page).not.not.toBeEnabled(); + │ ++++++ ``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/non-async-context.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/non-async-context.js index afc46d8d37d3..3f6a7fff7007 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/non-async-context.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/non-async-context.js @@ -1,3 +1,4 @@ +/* should generate diagnostics */ test('example', ({ page }) => { expect(page.locator('body')).toBeVisible(); }); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/non-async-context.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/non-async-context.js.snap index d0021e79b540..46f35f9b9e2d 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/non-async-context.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/non-async-context.js.snap @@ -4,6 +4,7 @@ expression: non-async-context.js --- # Input ```js +/* should generate diagnostics */ test('example', ({ page }) => { expect(page.locator('body')).toBeVisible(); }); @@ -14,15 +15,16 @@ test('example', ({ page }) => { # Diagnostics ``` -non-async-context.js:2:5 lint/nursery/noPlaywrightMissingAwait ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +non-async-context.js:3:5 lint/nursery/noPlaywrightMissingAwait ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Async matcher toBeVisible must be awaited or returned. - 1 │ test('example', ({ page }) => { - > 2 │ expect(page.locator('body')).toBeVisible(); + 1 │ /* should generate diagnostics */ + 2 │ test('example', ({ page }) => { + > 3 │ expect(page.locator('body')).toBeVisible(); │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 3 │ }); - 4 │ + 4 │ }); + 5 │ i Add await before the expect call or return the promise. diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/promise-all-not-awaited.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/promise-all-not-awaited.js index 44b609d054f1..f461cc9c5e30 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/promise-all-not-awaited.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/promise-all-not-awaited.js @@ -1,3 +1,4 @@ +/* should generate diagnostics */ test('example', async ({ page }) => { Promise.all([ expect(page.locator('.one')).toBeVisible(), diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/promise-all-not-awaited.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/promise-all-not-awaited.js.snap index 14f6bc0f2ed4..aa7e693b5f9b 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/promise-all-not-awaited.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/promise-all-not-awaited.js.snap @@ -4,6 +4,7 @@ expression: promise-all-not-awaited.js --- # Input ```js +/* should generate diagnostics */ test('example', async ({ page }) => { Promise.all([ expect(page.locator('.one')).toBeVisible(), @@ -16,53 +17,43 @@ test('example', async ({ page }) => { # Diagnostics ``` -promise-all-not-awaited.js:3:9 lint/nursery/noPlaywrightMissingAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━ +promise-all-not-awaited.js:4:9 lint/nursery/noPlaywrightMissingAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━ i Async matcher toBeVisible must be awaited or returned. - 1 │ test('example', async ({ page }) => { - 2 │ Promise.all([ - > 3 │ expect(page.locator('.one')).toBeVisible(), + 2 │ test('example', async ({ page }) => { + 3 │ Promise.all([ + > 4 │ expect(page.locator('.one')).toBeVisible(), │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 4 │ expect(page.locator('.two')).toBeVisible() - 5 │ ]); + 5 │ expect(page.locator('.two')).toBeVisible() + 6 │ ]); i Add await before the expect call or return the promise. - i Unsafe fix: Add await to Promise combinator - - 1 1 │ test('example', async ({ page }) => { - 2 │ - ····Promise.all([ - 2 │ + ····await - 3 │ + ····Promise.all([ - 3 4 │ expect(page.locator('.one')).toBeVisible(), - 4 5 │ expect(page.locator('.two')).toBeVisible() + i Unsafe fix: Add await before the Promise combinator. + 3 │ ····await·Promise.all([ + │ ++++++ ``` ``` -promise-all-not-awaited.js:4:9 lint/nursery/noPlaywrightMissingAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━ +promise-all-not-awaited.js:5:9 lint/nursery/noPlaywrightMissingAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━ i Async matcher toBeVisible must be awaited or returned. - 2 │ Promise.all([ - 3 │ expect(page.locator('.one')).toBeVisible(), - > 4 │ expect(page.locator('.two')).toBeVisible() + 3 │ Promise.all([ + 4 │ expect(page.locator('.one')).toBeVisible(), + > 5 │ expect(page.locator('.two')).toBeVisible() │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 5 │ ]); - 6 │ }); + 6 │ ]); + 7 │ }); i Add await before the expect call or return the promise. - i Unsafe fix: Add await to Promise combinator - - 1 1 │ test('example', async ({ page }) => { - 2 │ - ····Promise.all([ - 2 │ + ····await - 3 │ + ····Promise.all([ - 3 4 │ expect(page.locator('.one')).toBeVisible(), - 4 5 │ expect(page.locator('.two')).toBeVisible() + i Unsafe fix: Add await before the Promise combinator. + 3 │ ····await·Promise.all([ + │ ++++++ ``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/promise-combinators.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/promise-combinators.js index 3e9e422870e5..dbd57c6d2165 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/promise-combinators.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/promise-combinators.js @@ -1,3 +1,4 @@ +/* should generate diagnostics */ test('Promise.allSettled', async ({ page }) => { Promise.allSettled([ expect(page).toBeVisible(), diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/promise-combinators.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/promise-combinators.js.snap index 2e0b59bcccd1..58b389121da0 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/promise-combinators.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/promise-combinators.js.snap @@ -4,6 +4,7 @@ expression: promise-combinators.js --- # Input ```js +/* should generate diagnostics */ test('Promise.allSettled', async ({ page }) => { Promise.allSettled([ expect(page).toBeVisible(), @@ -29,161 +30,127 @@ test('Promise.any', async ({ page }) => { # Diagnostics ``` -promise-combinators.js:3:9 lint/nursery/noPlaywrightMissingAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━ +promise-combinators.js:4:9 lint/nursery/noPlaywrightMissingAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━ i Async matcher toBeVisible must be awaited or returned. - 1 │ test('Promise.allSettled', async ({ page }) => { - 2 │ Promise.allSettled([ - > 3 │ expect(page).toBeVisible(), + 2 │ test('Promise.allSettled', async ({ page }) => { + 3 │ Promise.allSettled([ + > 4 │ expect(page).toBeVisible(), │ ^^^^^^^^^^^^^^^^^^^^^^^^^^ - 4 │ expect(page).toHaveText('foo') - 5 │ ]); + 5 │ expect(page).toHaveText('foo') + 6 │ ]); i Add await before the expect call or return the promise. - i Unsafe fix: Add await to Promise combinator - - 1 1 │ test('Promise.allSettled', async ({ page }) => { - 2 │ - ····Promise.allSettled([ - 2 │ + ····await - 3 │ + ····Promise.allSettled([ - 3 4 │ expect(page).toBeVisible(), - 4 5 │ expect(page).toHaveText('foo') + i Unsafe fix: Add await before the Promise combinator. + 3 │ ····await·Promise.allSettled([ + │ ++++++ ``` ``` -promise-combinators.js:4:9 lint/nursery/noPlaywrightMissingAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━ +promise-combinators.js:5:9 lint/nursery/noPlaywrightMissingAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━ i Async matcher toHaveText must be awaited or returned. - 2 │ Promise.allSettled([ - 3 │ expect(page).toBeVisible(), - > 4 │ expect(page).toHaveText('foo') + 3 │ Promise.allSettled([ + 4 │ expect(page).toBeVisible(), + > 5 │ expect(page).toHaveText('foo') │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 5 │ ]); - 6 │ }); + 6 │ ]); + 7 │ }); i Add await before the expect call or return the promise. - i Unsafe fix: Add await to Promise combinator - - 1 1 │ test('Promise.allSettled', async ({ page }) => { - 2 │ - ····Promise.allSettled([ - 2 │ + ····await - 3 │ + ····Promise.allSettled([ - 3 4 │ expect(page).toBeVisible(), - 4 5 │ expect(page).toHaveText('foo') + i Unsafe fix: Add await before the Promise combinator. + 3 │ ····await·Promise.allSettled([ + │ ++++++ ``` ``` -promise-combinators.js:10:9 lint/nursery/noPlaywrightMissingAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━ +promise-combinators.js:11:9 lint/nursery/noPlaywrightMissingAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━ i Async matcher toBeVisible must be awaited or returned. - 8 │ test('Promise.race', async ({ page }) => { - 9 │ Promise.race([ - > 10 │ expect(page).toBeVisible(), + 9 │ test('Promise.race', async ({ page }) => { + 10 │ Promise.race([ + > 11 │ expect(page).toBeVisible(), │ ^^^^^^^^^^^^^^^^^^^^^^^^^^ - 11 │ expect(page).toHaveText('foo') - 12 │ ]); + 12 │ expect(page).toHaveText('foo') + 13 │ ]); i Add await before the expect call or return the promise. - i Unsafe fix: Add await to Promise combinator - - 7 7 │ - 8 8 │ test('Promise.race', async ({ page }) => { - 9 │ - ····Promise.race([ - 9 │ + ····await - 10 │ + ····Promise.race([ - 10 11 │ expect(page).toBeVisible(), - 11 12 │ expect(page).toHaveText('foo') + i Unsafe fix: Add await before the Promise combinator. + 10 │ ····await·Promise.race([ + │ ++++++ ``` ``` -promise-combinators.js:11:9 lint/nursery/noPlaywrightMissingAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━ +promise-combinators.js:12:9 lint/nursery/noPlaywrightMissingAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━ i Async matcher toHaveText must be awaited or returned. - 9 │ Promise.race([ - 10 │ expect(page).toBeVisible(), - > 11 │ expect(page).toHaveText('foo') + 10 │ Promise.race([ + 11 │ expect(page).toBeVisible(), + > 12 │ expect(page).toHaveText('foo') │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 12 │ ]); - 13 │ }); + 13 │ ]); + 14 │ }); i Add await before the expect call or return the promise. - i Unsafe fix: Add await to Promise combinator - - 7 7 │ - 8 8 │ test('Promise.race', async ({ page }) => { - 9 │ - ····Promise.race([ - 9 │ + ····await - 10 │ + ····Promise.race([ - 10 11 │ expect(page).toBeVisible(), - 11 12 │ expect(page).toHaveText('foo') + i Unsafe fix: Add await before the Promise combinator. + 10 │ ····await·Promise.race([ + │ ++++++ ``` ``` -promise-combinators.js:17:9 lint/nursery/noPlaywrightMissingAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━ +promise-combinators.js:18:9 lint/nursery/noPlaywrightMissingAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━ i Async matcher toBeVisible must be awaited or returned. - 15 │ test('Promise.any', async ({ page }) => { - 16 │ Promise.any([ - > 17 │ expect(page).toBeVisible(), + 16 │ test('Promise.any', async ({ page }) => { + 17 │ Promise.any([ + > 18 │ expect(page).toBeVisible(), │ ^^^^^^^^^^^^^^^^^^^^^^^^^^ - 18 │ expect(page).toHaveText('foo') - 19 │ ]); + 19 │ expect(page).toHaveText('foo') + 20 │ ]); i Add await before the expect call or return the promise. - i Unsafe fix: Add await to Promise combinator - - 14 14 │ - 15 15 │ test('Promise.any', async ({ page }) => { - 16 │ - ····Promise.any([ - 16 │ + ····await - 17 │ + ····Promise.any([ - 17 18 │ expect(page).toBeVisible(), - 18 19 │ expect(page).toHaveText('foo') + i Unsafe fix: Add await before the Promise combinator. + 17 │ ····await·Promise.any([ + │ ++++++ ``` ``` -promise-combinators.js:18:9 lint/nursery/noPlaywrightMissingAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━ +promise-combinators.js:19:9 lint/nursery/noPlaywrightMissingAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━ i Async matcher toHaveText must be awaited or returned. - 16 │ Promise.any([ - 17 │ expect(page).toBeVisible(), - > 18 │ expect(page).toHaveText('foo') + 17 │ Promise.any([ + 18 │ expect(page).toBeVisible(), + > 19 │ expect(page).toHaveText('foo') │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 19 │ ]); - 20 │ }); + 20 │ ]); + 21 │ }); i Add await before the expect call or return the promise. - i Unsafe fix: Add await to Promise combinator - - 14 14 │ - 15 15 │ test('Promise.any', async ({ page }) => { - 16 │ - ····Promise.any([ - 16 │ + ····await - 17 │ + ····Promise.any([ - 17 18 │ expect(page).toBeVisible(), - 18 19 │ expect(page).toHaveText('foo') + i Unsafe fix: Add await before the Promise combinator. + 17 │ ····await·Promise.any([ + │ ++++++ ``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/test-step.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/test-step.js index 4b9cc8b33d18..0e26c35a24ba 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/test-step.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/test-step.js @@ -1,3 +1,4 @@ +/* should generate diagnostics */ test('example', async ({ page }) => { test.step('clicks button', async () => { await page.click('button'); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/test-step.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/test-step.js.snap index 229f03558479..ab85de9bbe17 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/test-step.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/test-step.js.snap @@ -4,6 +4,7 @@ expression: test-step.js --- # Input ```js +/* should generate diagnostics */ test('example', async ({ page }) => { test.step('clicks button', async () => { await page.click('button'); @@ -15,29 +16,25 @@ test('example', async ({ page }) => { # Diagnostics ``` -test-step.js:2:5 lint/nursery/noPlaywrightMissingAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +test-step.js:3:5 lint/nursery/noPlaywrightMissingAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i test.step must be awaited or returned. - 1 │ test('example', async ({ page }) => { - > 2 │ test.step('clicks button', async () => { + 1 │ /* should generate diagnostics */ + 2 │ test('example', async ({ page }) => { + > 3 │ test.step('clicks button', async () => { │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - > 3 │ await page.click('button'); - > 4 │ }); + > 4 │ await page.click('button'); + > 5 │ }); │ ^^ - 5 │ }); - 6 │ + 6 │ }); + 7 │ - i Add await before the test.step call or return the promise. + i Test steps are asynchronous. Add await before test.step() or return the promise. - i Unsafe fix: Add await - - 1 1 │ test('example', async ({ page }) => { - 2 │ - ····test.step('clicks·button',·async·()·=>·{ - 2 │ + ····await - 3 │ + ····test.step('clicks·button',·async·()·=>·{ - 3 4 │ await page.click('button'); - 4 5 │ }); + i Unsafe fix: Add await before the call. + 3 │ ····await·test.step('clicks·button',·async·()·=>·{ + │ ++++++ ``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/valid/awaited.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/valid/awaited.js index 0483bc0be360..f27bb2252197 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/valid/awaited.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/valid/awaited.js @@ -1,3 +1,4 @@ +/* should not generate diagnostics */ test('example', async ({ page }) => { await expect(page.locator('body')).toBeVisible(); await test.step('step', async () => {}); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/valid/awaited.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/valid/awaited.js.snap index 7760f7433dd0..7c08c28220a5 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/valid/awaited.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/valid/awaited.js.snap @@ -4,6 +4,7 @@ expression: awaited.js --- # Input ```js +/* should not generate diagnostics */ test('example', async ({ page }) => { await expect(page.locator('body')).toBeVisible(); await test.step('step', async () => {}); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/valid/bracket-notation-valid.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/valid/bracket-notation-valid.js index cfc3d5b44d57..ff2fcd73642a 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/valid/bracket-notation-valid.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/valid/bracket-notation-valid.js @@ -1,4 +1,4 @@ -// should not generate diagnostics +/* should not generate diagnostics */ // Valid: awaited bracket notation test('awaited bracket notation for test.step', async ({ page }) => { await test["step"]('do something', async () => { diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/valid/bracket-notation-valid.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/valid/bracket-notation-valid.js.snap index 85e14c82071b..69a40b3ba4cb 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/valid/bracket-notation-valid.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/valid/bracket-notation-valid.js.snap @@ -4,7 +4,7 @@ expression: bracket-notation-valid.js --- # Input ```js -// should not generate diagnostics +/* should not generate diagnostics */ // Valid: awaited bracket notation test('awaited bracket notation for test.step', async ({ page }) => { await test["step"]('do something', async () => { diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/valid/promise-all.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/valid/promise-all.js index aa28d8f317de..97a8c16af510 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/valid/promise-all.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/valid/promise-all.js @@ -1,3 +1,4 @@ +/* should not generate diagnostics */ test('example', async ({ page }) => { await Promise.all([ expect(page.locator('.one')).toBeVisible(), diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/valid/promise-all.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/valid/promise-all.js.snap index 70c2648dc396..f582e75c6af4 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/valid/promise-all.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/valid/promise-all.js.snap @@ -1,10 +1,10 @@ --- source: crates/biome_js_analyze/tests/spec_tests.rs -assertion_line: 152 expression: promise-all.js --- # Input ```js +/* should not generate diagnostics */ test('example', async ({ page }) => { await Promise.all([ expect(page.locator('.one')).toBeVisible(), diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/valid/promise-combinators-valid.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/valid/promise-combinators-valid.js index 2a94677ab2b0..908dfa9dd8bc 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/valid/promise-combinators-valid.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/valid/promise-combinators-valid.js @@ -1,4 +1,4 @@ -// should not generate diagnostics +/* should not generate diagnostics */ // Valid: awaited Promise combinators test('awaited Promise.all', async ({ page }) => { await Promise.all([ diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/valid/promise-combinators-valid.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/valid/promise-combinators-valid.js.snap index e6a8bf71e81a..20ff61cf8b96 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/valid/promise-combinators-valid.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/valid/promise-combinators-valid.js.snap @@ -4,7 +4,7 @@ expression: promise-combinators-valid.js --- # Input ```js -// should not generate diagnostics +/* should not generate diagnostics */ // Valid: awaited Promise combinators test('awaited Promise.all', async ({ page }) => { await Promise.all([ diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/valid/returned.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/valid/returned.js index 599763181855..709022a4ff95 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/valid/returned.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/valid/returned.js @@ -1,3 +1,4 @@ +/* should not generate diagnostics */ test('example', async ({ page }) => { return expect(page.locator('body')).toBeVisible(); }); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/valid/returned.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/valid/returned.js.snap index 1c90ed90792f..a0c40af721e8 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/valid/returned.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/valid/returned.js.snap @@ -4,6 +4,7 @@ expression: returned.js --- # Input ```js +/* should not generate diagnostics */ test('example', async ({ page }) => { return expect(page.locator('body')).toBeVisible(); }); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/all-methods.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/all-methods.js index 5b2dd92bce72..e61d308d3f0b 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/all-methods.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/all-methods.js @@ -1,3 +1,4 @@ +/* should generate diagnostics */ // Test all methods that support networkidle option page.waitForLoadState("networkidle"); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/all-methods.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/all-methods.js.snap index 098e3a07b78b..5630275850b3 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/all-methods.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/all-methods.js.snap @@ -4,6 +4,7 @@ expression: all-methods.js --- # Input ```js +/* should generate diagnostics */ // Test all methods that support networkidle option page.waitForLoadState("networkidle"); @@ -23,15 +24,16 @@ page.goForward({ waitUntil: "networkidle" }); # Diagnostics ``` -all-methods.js:2:1 lint/nursery/noPlaywrightNetworkidle ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +all-methods.js:3:1 lint/nursery/noPlaywrightNetworkidle ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Unexpected use of networkidle option. - 1 │ // Test all methods that support networkidle option - > 2 │ page.waitForLoadState("networkidle"); + 1 │ /* should generate diagnostics */ + 2 │ // Test all methods that support networkidle option + > 3 │ page.waitForLoadState("networkidle"); │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 3 │ - 4 │ page.waitForURL("http://example.com", { waitUntil: "networkidle" }); + 4 │ + 5 │ page.waitForURL("http://example.com", { waitUntil: "networkidle" }); i The networkidle event is unreliable and can lead to flaky tests. @@ -41,16 +43,16 @@ all-methods.js:2:1 lint/nursery/noPlaywrightNetworkidle ━━━━━━━━ ``` ``` -all-methods.js:4:1 lint/nursery/noPlaywrightNetworkidle ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +all-methods.js:5:1 lint/nursery/noPlaywrightNetworkidle ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Unexpected use of networkidle option. - 2 │ page.waitForLoadState("networkidle"); - 3 │ - > 4 │ page.waitForURL("http://example.com", { waitUntil: "networkidle" }); + 3 │ page.waitForLoadState("networkidle"); + 4 │ + > 5 │ page.waitForURL("http://example.com", { waitUntil: "networkidle" }); │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 5 │ - 6 │ page.goto("http://example.com", { waitUntil: "networkidle" }); + 6 │ + 7 │ page.goto("http://example.com", { waitUntil: "networkidle" }); i The networkidle event is unreliable and can lead to flaky tests. @@ -60,16 +62,16 @@ all-methods.js:4:1 lint/nursery/noPlaywrightNetworkidle ━━━━━━━━ ``` ``` -all-methods.js:6:1 lint/nursery/noPlaywrightNetworkidle ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +all-methods.js:7:1 lint/nursery/noPlaywrightNetworkidle ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Unexpected use of networkidle option. - 4 │ page.waitForURL("http://example.com", { waitUntil: "networkidle" }); - 5 │ - > 6 │ page.goto("http://example.com", { waitUntil: "networkidle" }); + 5 │ page.waitForURL("http://example.com", { waitUntil: "networkidle" }); + 6 │ + > 7 │ page.goto("http://example.com", { waitUntil: "networkidle" }); │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 7 │ - 8 │ page.reload({ waitUntil: "networkidle" }); + 8 │ + 9 │ page.reload({ waitUntil: "networkidle" }); i The networkidle event is unreliable and can lead to flaky tests. @@ -79,16 +81,16 @@ all-methods.js:6:1 lint/nursery/noPlaywrightNetworkidle ━━━━━━━━ ``` ``` -all-methods.js:8:1 lint/nursery/noPlaywrightNetworkidle ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +all-methods.js:9:1 lint/nursery/noPlaywrightNetworkidle ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Unexpected use of networkidle option. - 6 │ page.goto("http://example.com", { waitUntil: "networkidle" }); - 7 │ - > 8 │ page.reload({ waitUntil: "networkidle" }); + 7 │ page.goto("http://example.com", { waitUntil: "networkidle" }); + 8 │ + > 9 │ page.reload({ waitUntil: "networkidle" }); │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 9 │ - 10 │ page.setContent("", { waitUntil: "networkidle" }); + 10 │ + 11 │ page.setContent("", { waitUntil: "networkidle" }); i The networkidle event is unreliable and can lead to flaky tests. @@ -98,16 +100,16 @@ all-methods.js:8:1 lint/nursery/noPlaywrightNetworkidle ━━━━━━━━ ``` ``` -all-methods.js:10:1 lint/nursery/noPlaywrightNetworkidle ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +all-methods.js:11:1 lint/nursery/noPlaywrightNetworkidle ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Unexpected use of networkidle option. - 8 │ page.reload({ waitUntil: "networkidle" }); - 9 │ - > 10 │ page.setContent("", { waitUntil: "networkidle" }); + 9 │ page.reload({ waitUntil: "networkidle" }); + 10 │ + > 11 │ page.setContent("", { waitUntil: "networkidle" }); │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 11 │ - 12 │ page.goBack({ waitUntil: "networkidle" }); + 12 │ + 13 │ page.goBack({ waitUntil: "networkidle" }); i The networkidle event is unreliable and can lead to flaky tests. @@ -117,16 +119,16 @@ all-methods.js:10:1 lint/nursery/noPlaywrightNetworkidle ━━━━━━━ ``` ``` -all-methods.js:12:1 lint/nursery/noPlaywrightNetworkidle ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +all-methods.js:13:1 lint/nursery/noPlaywrightNetworkidle ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Unexpected use of networkidle option. - 10 │ page.setContent("", { waitUntil: "networkidle" }); - 11 │ - > 12 │ page.goBack({ waitUntil: "networkidle" }); + 11 │ page.setContent("", { waitUntil: "networkidle" }); + 12 │ + > 13 │ page.goBack({ waitUntil: "networkidle" }); │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 13 │ - 14 │ page.goForward({ waitUntil: "networkidle" }); + 14 │ + 15 │ page.goForward({ waitUntil: "networkidle" }); i The networkidle event is unreliable and can lead to flaky tests. @@ -136,15 +138,15 @@ all-methods.js:12:1 lint/nursery/noPlaywrightNetworkidle ━━━━━━━ ``` ``` -all-methods.js:14:1 lint/nursery/noPlaywrightNetworkidle ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +all-methods.js:15:1 lint/nursery/noPlaywrightNetworkidle ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Unexpected use of networkidle option. - 12 │ page.goBack({ waitUntil: "networkidle" }); - 13 │ - > 14 │ page.goForward({ waitUntil: "networkidle" }); + 13 │ page.goBack({ waitUntil: "networkidle" }); + 14 │ + > 15 │ page.goForward({ waitUntil: "networkidle" }); │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 15 │ + 16 │ i The networkidle event is unreliable and can lead to flaky tests. diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/bracket-notation.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/bracket-notation.js deleted file mode 100644 index 19e05b262f2f..000000000000 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/bracket-notation.js +++ /dev/null @@ -1,10 +0,0 @@ -// Test bracket notation for networkidle option -page["waitForLoadState"]("networkidle"); - -page[`waitForLoadState`]("networkidle"); - -page["waitForURL"](url, { waitUntil: "networkidle" }); - -page[`goto`](url, { waitUntil: "networkidle" }); - -page["reload"](url, { waitUntil: "networkidle" }); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/bracket-notation.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/bracket-notation.js.snap deleted file mode 100644 index c43d68f11769..000000000000 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/bracket-notation.js.snap +++ /dev/null @@ -1,18 +0,0 @@ ---- -source: crates/biome_js_analyze/tests/spec_tests.rs -expression: bracket-notation.js ---- -# Input -```js -// Test bracket notation for networkidle option -page["waitForLoadState"]("networkidle"); - -page[`waitForLoadState`]("networkidle"); - -page["waitForURL"](url, { waitUntil: "networkidle" }); - -page[`goto`](url, { waitUntil: "networkidle" }); - -page["reload"](url, { waitUntil: "networkidle" }); - -``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/goto.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/goto.js index b1495d10aee3..e20c0649c1e1 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/goto.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/goto.js @@ -1,3 +1,4 @@ +/* should generate diagnostics */ await page.goto('https://example.com', { waitUntil: 'networkidle' }); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/goto.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/goto.js.snap index 3dd4025db38f..5c90ee49155e 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/goto.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/goto.js.snap @@ -1,10 +1,10 @@ --- source: crates/biome_js_analyze/tests/spec_tests.rs -assertion_line: 152 expression: goto.js --- # Input ```js +/* should generate diagnostics */ await page.goto('https://example.com', { waitUntil: 'networkidle' }); @@ -13,13 +13,14 @@ await page.goto('https://example.com', { waitUntil: 'networkidle' }); # Diagnostics ``` -goto.js:1:7 lint/nursery/noPlaywrightNetworkidle ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +goto.js:2:7 lint/nursery/noPlaywrightNetworkidle ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Unexpected use of networkidle option. - > 1 │ await page.goto('https://example.com', { waitUntil: 'networkidle' }); + 1 │ /* should generate diagnostics */ + > 2 │ await page.goto('https://example.com', { waitUntil: 'networkidle' }); │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 2 │ + 3 │ i The networkidle event is unreliable and can lead to flaky tests. diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/wait-for-load-state.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/wait-for-load-state.js index 46c4a5436271..6209e4efdf62 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/wait-for-load-state.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/wait-for-load-state.js @@ -1,3 +1,4 @@ +/* should generate diagnostics */ await page.waitForLoadState('networkidle'); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/wait-for-load-state.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/wait-for-load-state.js.snap index 5cb43ce0c56f..349466c9a6f7 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/wait-for-load-state.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/invalid/wait-for-load-state.js.snap @@ -1,10 +1,10 @@ --- source: crates/biome_js_analyze/tests/spec_tests.rs -assertion_line: 152 expression: wait-for-load-state.js --- # Input ```js +/* should generate diagnostics */ await page.waitForLoadState('networkidle'); @@ -13,13 +13,14 @@ await page.waitForLoadState('networkidle'); # Diagnostics ``` -wait-for-load-state.js:1:7 lint/nursery/noPlaywrightNetworkidle ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +wait-for-load-state.js:2:7 lint/nursery/noPlaywrightNetworkidle ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Unexpected use of networkidle option. - > 1 │ await page.waitForLoadState('networkidle'); + 1 │ /* should generate diagnostics */ + > 2 │ await page.waitForLoadState('networkidle'); │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 2 │ + 3 │ i The networkidle event is unreliable and can lead to flaky tests. diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/valid/load.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/valid/load.js index 1fdd1e732479..3c3e7bd6ea43 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/valid/load.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/valid/load.js @@ -1,3 +1,4 @@ +/* should not generate diagnostics */ await page.waitForLoadState('load'); await page.goto('https://example.com'); await page.locator('.content').waitFor(); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/valid/load.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/valid/load.js.snap index b03ff5162d38..fb5097c205ab 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/valid/load.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/valid/load.js.snap @@ -1,10 +1,10 @@ --- source: crates/biome_js_analyze/tests/spec_tests.rs -assertion_line: 152 expression: load.js --- # Input ```js +/* should not generate diagnostics */ await page.waitForLoadState('load'); await page.goto('https://example.com'); await page.locator('.content').waitFor(); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/valid/other-wait-options.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/valid/other-wait-options.js index e89b09c78025..ffb51f7f493a 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/valid/other-wait-options.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/valid/other-wait-options.js @@ -1,3 +1,4 @@ +/* should not generate diagnostics */ // Valid: using other waitUntil options page.waitForLoadState(); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/valid/other-wait-options.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/valid/other-wait-options.js.snap index 0afa5f259403..fdf2a41c9176 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/valid/other-wait-options.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightNetworkidle/valid/other-wait-options.js.snap @@ -4,6 +4,7 @@ expression: other-wait-options.js --- # Input ```js +/* should not generate diagnostics */ // Valid: using other waitUntil options page.waitForLoadState(); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/bracket-notation.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/bracket-notation.js deleted file mode 100644 index e2a22a80b29f..000000000000 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/bracket-notation.js +++ /dev/null @@ -1,8 +0,0 @@ -// Test bracket notation for page.pause() -await page["pause"](); - -await page[`pause`](); - -await this.page["pause"](); - -await this.page[`pause`](); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/bracket-notation.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/bracket-notation.js.snap deleted file mode 100644 index 396085d351c7..000000000000 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/bracket-notation.js.snap +++ /dev/null @@ -1,16 +0,0 @@ ---- -source: crates/biome_js_analyze/tests/spec_tests.rs -expression: bracket-notation.js ---- -# Input -```js -// Test bracket notation for page.pause() -await page["pause"](); - -await page[`pause`](); - -await this.page["pause"](); - -await this.page[`pause`](); - -``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/frame.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/frame.js index f9cb9c794f7f..89eb4fc64992 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/frame.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/frame.js @@ -1,3 +1,4 @@ +/* should generate diagnostics */ const frame = page.frame('iframe'); await frame.pause(); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/frame.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/frame.js.snap index 6cb37b2e5caf..837ac28251cb 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/frame.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/frame.js.snap @@ -4,6 +4,7 @@ expression: frame.js --- # Input ```js +/* should generate diagnostics */ const frame = page.frame('iframe'); await frame.pause(); @@ -12,14 +13,15 @@ await frame.pause(); # Diagnostics ``` -frame.js:2:7 lint/nursery/noPlaywrightPagePause ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +frame.js:3:7 lint/nursery/noPlaywrightPagePause ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Unexpected use of frame.pause(). - 1 │ const frame = page.frame('iframe'); - > 2 │ await frame.pause(); + 1 │ /* should generate diagnostics */ + 2 │ const frame = page.frame('iframe'); + > 3 │ await frame.pause(); │ ^^^^^^^^^^^^^ - 3 │ + 4 │ i frame.pause() is a debugging utility and should not be committed to version control. diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/in-test.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/in-test.js index d6ce5b7b5b73..b65d934f911d 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/in-test.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/in-test.js @@ -1,3 +1,4 @@ +/* should generate diagnostics */ test('example', async ({ page }) => { await page.click('button'); await page.pause(); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/in-test.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/in-test.js.snap index 853c28686845..22dfcda3907e 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/in-test.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/in-test.js.snap @@ -1,10 +1,10 @@ --- source: crates/biome_js_analyze/tests/spec_tests.rs -assertion_line: 152 expression: in-test.js --- # Input ```js +/* should generate diagnostics */ test('example', async ({ page }) => { await page.click('button'); await page.pause(); @@ -15,16 +15,16 @@ test('example', async ({ page }) => { # Diagnostics ``` -in-test.js:3:11 lint/nursery/noPlaywrightPagePause ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +in-test.js:4:11 lint/nursery/noPlaywrightPagePause ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Unexpected use of page.pause(). - 1 │ test('example', async ({ page }) => { - 2 │ await page.click('button'); - > 3 │ await page.pause(); + 2 │ test('example', async ({ page }) => { + 3 │ await page.click('button'); + > 4 │ await page.pause(); │ ^^^^^^^^^^^^ - 4 │ }); - 5 │ + 5 │ }); + 6 │ i page.pause() is a debugging utility and should not be committed to version control. diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/simple.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/simple.js index cbb22482e37e..b50a938a6c6d 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/simple.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/simple.js @@ -1,2 +1,3 @@ +/* should generate diagnostics */ await page.pause(); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/simple.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/simple.js.snap index 322ee2c54f12..4af0231f25bb 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/simple.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/invalid/simple.js.snap @@ -1,10 +1,10 @@ --- source: crates/biome_js_analyze/tests/spec_tests.rs -assertion_line: 152 expression: simple.js --- # Input ```js +/* should generate diagnostics */ await page.pause(); @@ -12,13 +12,14 @@ await page.pause(); # Diagnostics ``` -simple.js:1:7 lint/nursery/noPlaywrightPagePause ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +simple.js:2:7 lint/nursery/noPlaywrightPagePause ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Unexpected use of page.pause(). - > 1 │ await page.pause(); + 1 │ /* should generate diagnostics */ + > 2 │ await page.pause(); │ ^^^^^^^^^^^^ - 2 │ + 3 │ i page.pause() is a debugging utility and should not be committed to version control. diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/valid/click.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/valid/click.js index a0466ab9edcd..e3f3d905345e 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/valid/click.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/valid/click.js @@ -1,3 +1,4 @@ +/* should not generate diagnostics */ test('example', async ({ page }) => { await page.click('button'); await page.waitForSelector('.result'); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/valid/click.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/valid/click.js.snap index 3772f6c214b8..a4db77433033 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/valid/click.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/valid/click.js.snap @@ -1,10 +1,10 @@ --- source: crates/biome_js_analyze/tests/spec_tests.rs -assertion_line: 152 expression: click.js --- # Input ```js +/* should not generate diagnostics */ test('example', async ({ page }) => { await page.click('button'); await page.waitForSelector('.result'); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/valid/other-methods.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/valid/other-methods.js index d2a09c6361b8..1ddadd1397f0 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/valid/other-methods.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/valid/other-methods.js @@ -1,3 +1,4 @@ +/* should not generate diagnostics */ await page.goto('https://example.com'); await page.fill('input', 'text'); await page.screenshot(); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/valid/other-methods.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/valid/other-methods.js.snap index 01c681c25694..d883b99e0710 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/valid/other-methods.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/valid/other-methods.js.snap @@ -1,10 +1,10 @@ --- source: crates/biome_js_analyze/tests/spec_tests.rs -assertion_line: 152 expression: other-methods.js --- # Input ```js +/* should not generate diagnostics */ await page.goto('https://example.com'); await page.fill('input', 'text'); await page.screenshot(); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/valid/pause-function.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/valid/pause-function.js index 93bed2cd851d..44cdb2cd1868 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/valid/pause-function.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/valid/pause-function.js @@ -1,3 +1,4 @@ +/* should not generate diagnostics */ // This should be valid - pause() is a function, not page.pause() function pause() { console.log('pausing'); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/valid/pause-function.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/valid/pause-function.js.snap index 8461d4c173f2..f81b51f0d24c 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/valid/pause-function.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightPagePause/valid/pause-function.js.snap @@ -1,10 +1,10 @@ --- source: crates/biome_js_analyze/tests/spec_tests.rs -assertion_line: 152 expression: pause-function.js --- # Input ```js +/* should not generate diagnostics */ // This should be valid - pause() is a function, not page.pause() function pause() { console.log('pausing'); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightSkippedTest/invalid/describe-skip.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightSkippedTest/invalid/describe-skip.js deleted file mode 100644 index 1e87679c8d8e..000000000000 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightSkippedTest/invalid/describe-skip.js +++ /dev/null @@ -1,5 +0,0 @@ -test.describe.skip('skip suite', () => { - test('one', async ({ page }) => {}); - test('two', async ({ page }) => {}); -}); - diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightSkippedTest/invalid/describe-skip.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightSkippedTest/invalid/describe-skip.js.snap deleted file mode 100644 index 65a8de46a2cd..000000000000 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightSkippedTest/invalid/describe-skip.js.snap +++ /dev/null @@ -1,35 +0,0 @@ ---- -source: crates/biome_js_analyze/tests/spec_tests.rs -assertion_line: 152 -expression: describe-skip.js ---- -# Input -```js -test.describe.skip('skip suite', () => { - test('one', async ({ page }) => {}); - test('two', async ({ page }) => {}); -}); - - -``` - -# Diagnostics -``` -describe-skip.js:1:1 lint/nursery/noPlaywrightSkippedTest ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - i Unexpected skipped test using .skip annotation. - - > 1 │ test.describe.skip('skip suite', () => { - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - > 2 │ test('one', async ({ page }) => {}); - > 3 │ test('two', async ({ page }) => {}); - > 4 │ }); - │ ^^ - 5 │ - - i Skipped tests should not be committed to version control. - - i Either remove the test or complete the implementation and remove the .skip annotation. - - -``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightSkippedTest/invalid/test-skip.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightSkippedTest/invalid/test-skip.js deleted file mode 100644 index 851ef37c899b..000000000000 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightSkippedTest/invalid/test-skip.js +++ /dev/null @@ -1,2 +0,0 @@ -test.skip('skip this test', async ({ page }) => {}); - diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightSkippedTest/invalid/test-skip.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightSkippedTest/invalid/test-skip.js.snap deleted file mode 100644 index fe2d38474727..000000000000 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightSkippedTest/invalid/test-skip.js.snap +++ /dev/null @@ -1,28 +0,0 @@ ---- -source: crates/biome_js_analyze/tests/spec_tests.rs -assertion_line: 152 -expression: test-skip.js ---- -# Input -```js -test.skip('skip this test', async ({ page }) => {}); - - -``` - -# Diagnostics -``` -test-skip.js:1:1 lint/nursery/noPlaywrightSkippedTest ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - i Unexpected skipped test using .skip annotation. - - > 1 │ test.skip('skip this test', async ({ page }) => {}); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 2 │ - - i Skipped tests should not be committed to version control. - - i Either remove the test or complete the implementation and remove the .skip annotation. - - -``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/expect-mixed-chains.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/expect-mixed-chains.js index dbef0d6edca2..6c0538464281 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/expect-mixed-chains.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/expect-mixed-chains.js @@ -1,3 +1,4 @@ +/* should generate diagnostics */ // Invalid: sync expect without async modifiers should trigger the rule await expect(1).toBe(1); await expect(value).toEqual(expectedValue); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/expect-mixed-chains.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/expect-mixed-chains.js.snap index 118e4e02c424..ea213f383094 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/expect-mixed-chains.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/expect-mixed-chains.js.snap @@ -4,6 +4,7 @@ expression: expect-mixed-chains.js --- # Input ```js +/* should generate diagnostics */ // Invalid: sync expect without async modifiers should trigger the rule await expect(1).toBe(1); await expect(value).toEqual(expectedValue); @@ -18,15 +19,16 @@ await expect.soft(value).toBe(123); # Diagnostics ``` -expect-mixed-chains.js:2:1 lint/nursery/noPlaywrightUselessAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━ +expect-mixed-chains.js:3:1 lint/nursery/noPlaywrightUselessAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━ i Unnecessary await expression. - 1 │ // Invalid: sync expect without async modifiers should trigger the rule - > 2 │ await expect(1).toBe(1); + 1 │ /* should generate diagnostics */ + 2 │ // Invalid: sync expect without async modifiers should trigger the rule + > 3 │ await expect(1).toBe(1); │ ^^^^^^^^^^^^^^^^^^^^^^^ - 3 │ await expect(value).toEqual(expectedValue); - 4 │ await expect(str).toMatch(/pattern/); + 4 │ await expect(value).toEqual(expectedValue); + 5 │ await expect(str).toMatch(/pattern/); i This method does not return a Promise. @@ -34,22 +36,22 @@ expect-mixed-chains.js:2:1 lint/nursery/noPlaywrightUselessAwait FIXABLE ━ i Safe fix: Remove unnecessary await - 2 │ await·expect(1).toBe(1); + 3 │ await·expect(1).toBe(1); │ ------ ``` ``` -expect-mixed-chains.js:3:1 lint/nursery/noPlaywrightUselessAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━ +expect-mixed-chains.js:4:1 lint/nursery/noPlaywrightUselessAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━ i Unnecessary await expression. - 1 │ // Invalid: sync expect without async modifiers should trigger the rule - 2 │ await expect(1).toBe(1); - > 3 │ await expect(value).toEqual(expectedValue); + 2 │ // Invalid: sync expect without async modifiers should trigger the rule + 3 │ await expect(1).toBe(1); + > 4 │ await expect(value).toEqual(expectedValue); │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 4 │ await expect(str).toMatch(/pattern/); - 5 │ await expect(arr).toHaveLength(3); + 5 │ await expect(str).toMatch(/pattern/); + 6 │ await expect(arr).toHaveLength(3); i This method does not return a Promise. @@ -57,22 +59,22 @@ expect-mixed-chains.js:3:1 lint/nursery/noPlaywrightUselessAwait FIXABLE ━ i Safe fix: Remove unnecessary await - 3 │ await·expect(value).toEqual(expectedValue); + 4 │ await·expect(value).toEqual(expectedValue); │ ------ ``` ``` -expect-mixed-chains.js:4:1 lint/nursery/noPlaywrightUselessAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━ +expect-mixed-chains.js:5:1 lint/nursery/noPlaywrightUselessAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━ i Unnecessary await expression. - 2 │ await expect(1).toBe(1); - 3 │ await expect(value).toEqual(expectedValue); - > 4 │ await expect(str).toMatch(/pattern/); + 3 │ await expect(1).toBe(1); + 4 │ await expect(value).toEqual(expectedValue); + > 5 │ await expect(str).toMatch(/pattern/); │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 5 │ await expect(arr).toHaveLength(3); - 6 │ + 6 │ await expect(arr).toHaveLength(3); + 7 │ i This method does not return a Promise. @@ -80,22 +82,22 @@ expect-mixed-chains.js:4:1 lint/nursery/noPlaywrightUselessAwait FIXABLE ━ i Safe fix: Remove unnecessary await - 4 │ await·expect(str).toMatch(/pattern/); + 5 │ await·expect(str).toMatch(/pattern/); │ ------ ``` ``` -expect-mixed-chains.js:5:1 lint/nursery/noPlaywrightUselessAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━ +expect-mixed-chains.js:6:1 lint/nursery/noPlaywrightUselessAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━ i Unnecessary await expression. - 3 │ await expect(value).toEqual(expectedValue); - 4 │ await expect(str).toMatch(/pattern/); - > 5 │ await expect(arr).toHaveLength(3); + 4 │ await expect(value).toEqual(expectedValue); + 5 │ await expect(str).toMatch(/pattern/); + > 6 │ await expect(arr).toHaveLength(3); │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 6 │ - 7 │ // Invalid: expect.soft with sync matcher (soft doesn't make it async) + 7 │ + 8 │ // Invalid: expect.soft with sync matcher (soft doesn't make it async) i This method does not return a Promise. @@ -103,20 +105,20 @@ expect-mixed-chains.js:5:1 lint/nursery/noPlaywrightUselessAwait FIXABLE ━ i Safe fix: Remove unnecessary await - 5 │ await·expect(arr).toHaveLength(3); + 6 │ await·expect(arr).toHaveLength(3); │ ------ ``` ``` -expect-mixed-chains.js:8:1 lint/nursery/noPlaywrightUselessAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━ +expect-mixed-chains.js:9:1 lint/nursery/noPlaywrightUselessAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━ i Unnecessary await expression. - 7 │ // Invalid: expect.soft with sync matcher (soft doesn't make it async) - > 8 │ await expect.soft(value).toBe(123); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 9 │ + 8 │ // Invalid: expect.soft with sync matcher (soft doesn't make it async) + > 9 │ await expect.soft(value).toBe(123); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 10 │ i This method does not return a Promise. @@ -124,7 +126,7 @@ expect-mixed-chains.js:8:1 lint/nursery/noPlaywrightUselessAwait FIXABLE ━ i Safe fix: Remove unnecessary await - 8 │ await·expect.soft(value).toBe(123); + 9 │ await·expect.soft(value).toBe(123); │ ------ ``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/expect-sync.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/expect-sync.js index 571ed5323f4f..29f65acc4cbf 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/expect-sync.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/expect-sync.js @@ -1,2 +1,3 @@ +/* should generate diagnostics */ await expect(1).toBe(1); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/expect-sync.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/expect-sync.js.snap index 3f1b2abd3f47..2e0708f61991 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/expect-sync.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/expect-sync.js.snap @@ -1,10 +1,10 @@ --- source: crates/biome_js_analyze/tests/spec_tests.rs -assertion_line: 152 expression: expect-sync.js --- # Input ```js +/* should generate diagnostics */ await expect(1).toBe(1); @@ -12,13 +12,14 @@ await expect(1).toBe(1); # Diagnostics ``` -expect-sync.js:1:1 lint/nursery/noPlaywrightUselessAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +expect-sync.js:2:1 lint/nursery/noPlaywrightUselessAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Unnecessary await expression. - > 1 │ await expect(1).toBe(1); + 1 │ /* should generate diagnostics */ + > 2 │ await expect(1).toBe(1); │ ^^^^^^^^^^^^^^^^^^^^^^^ - 2 │ + 3 │ i This method does not return a Promise. @@ -26,7 +27,7 @@ expect-sync.js:1:1 lint/nursery/noPlaywrightUselessAwait FIXABLE ━━━━ i Safe fix: Remove unnecessary await - 1 │ await·expect(1).toBe(1); + 2 │ await·expect(1).toBe(1); │ ------ ``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/getByRole.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/getByRole.js index bf688e110fda..bb163f5a9772 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/getByRole.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/getByRole.js @@ -1,2 +1,3 @@ +/* should generate diagnostics */ await page.getByRole('button'); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/getByRole.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/getByRole.js.snap index fe33b956d3b4..7ea9604f9276 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/getByRole.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/getByRole.js.snap @@ -1,10 +1,10 @@ --- source: crates/biome_js_analyze/tests/spec_tests.rs -assertion_line: 152 expression: getByRole.js --- # Input ```js +/* should generate diagnostics */ await page.getByRole('button'); @@ -12,13 +12,14 @@ await page.getByRole('button'); # Diagnostics ``` -getByRole.js:1:1 lint/nursery/noPlaywrightUselessAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +getByRole.js:2:1 lint/nursery/noPlaywrightUselessAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Unnecessary await expression. - > 1 │ await page.getByRole('button'); + 1 │ /* should generate diagnostics */ + > 2 │ await page.getByRole('button'); │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 2 │ + 3 │ i This method does not return a Promise. @@ -26,7 +27,7 @@ getByRole.js:1:1 lint/nursery/noPlaywrightUselessAwait FIXABLE ━━━━━ i Safe fix: Remove unnecessary await - 1 │ await·page.getByRole('button'); + 2 │ await·page.getByRole('button'); │ ------ ``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/locator.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/locator.js index 2e461c837956..f8ec36776089 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/locator.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/locator.js @@ -1,2 +1,3 @@ +/* should generate diagnostics */ await page.locator('.my-element'); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/locator.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/locator.js.snap index 269691b9447b..d2818423df4b 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/locator.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/locator.js.snap @@ -1,10 +1,10 @@ --- source: crates/biome_js_analyze/tests/spec_tests.rs -assertion_line: 152 expression: locator.js --- # Input ```js +/* should generate diagnostics */ await page.locator('.my-element'); @@ -12,13 +12,14 @@ await page.locator('.my-element'); # Diagnostics ``` -locator.js:1:1 lint/nursery/noPlaywrightUselessAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +locator.js:2:1 lint/nursery/noPlaywrightUselessAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Unnecessary await expression. - > 1 │ await page.locator('.my-element'); + 1 │ /* should generate diagnostics */ + > 2 │ await page.locator('.my-element'); │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 2 │ + 3 │ i This method does not return a Promise. @@ -26,7 +27,7 @@ locator.js:1:1 lint/nursery/noPlaywrightUselessAwait FIXABLE ━━━━━ i Safe fix: Remove unnecessary await - 1 │ await·page.locator('.my-element'); + 2 │ await·page.locator('.my-element'); │ ------ ``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/page-frame.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/page-frame.js index 5149f37fae6a..d07f64f48050 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/page-frame.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/page-frame.js @@ -1,3 +1,4 @@ +/* should generate diagnostics */ await page.frame('iframe'); await page.frames(); await page.url(); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/page-frame.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/page-frame.js.snap index f0c9e7930aa3..ab8d2850ef73 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/page-frame.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/page-frame.js.snap @@ -1,10 +1,10 @@ --- source: crates/biome_js_analyze/tests/spec_tests.rs -assertion_line: 152 expression: page-frame.js --- # Input ```js +/* should generate diagnostics */ await page.frame('iframe'); await page.frames(); await page.url(); @@ -14,14 +14,15 @@ await page.url(); # Diagnostics ``` -page-frame.js:1:1 lint/nursery/noPlaywrightUselessAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +page-frame.js:2:1 lint/nursery/noPlaywrightUselessAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Unnecessary await expression. - > 1 │ await page.frame('iframe'); + 1 │ /* should generate diagnostics */ + > 2 │ await page.frame('iframe'); │ ^^^^^^^^^^^^^^^^^^^^^^^^^^ - 2 │ await page.frames(); - 3 │ await page.url(); + 3 │ await page.frames(); + 4 │ await page.url(); i This method does not return a Promise. @@ -29,21 +30,22 @@ page-frame.js:1:1 lint/nursery/noPlaywrightUselessAwait FIXABLE ━━━━ i Safe fix: Remove unnecessary await - 1 │ await·page.frame('iframe'); + 2 │ await·page.frame('iframe'); │ ------ ``` ``` -page-frame.js:2:1 lint/nursery/noPlaywrightUselessAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +page-frame.js:3:1 lint/nursery/noPlaywrightUselessAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Unnecessary await expression. - 1 │ await page.frame('iframe'); - > 2 │ await page.frames(); + 1 │ /* should generate diagnostics */ + 2 │ await page.frame('iframe'); + > 3 │ await page.frames(); │ ^^^^^^^^^^^^^^^^^^^ - 3 │ await page.url(); - 4 │ + 4 │ await page.url(); + 5 │ i This method does not return a Promise. @@ -51,21 +53,21 @@ page-frame.js:2:1 lint/nursery/noPlaywrightUselessAwait FIXABLE ━━━━ i Safe fix: Remove unnecessary await - 2 │ await·page.frames(); + 3 │ await·page.frames(); │ ------ ``` ``` -page-frame.js:3:1 lint/nursery/noPlaywrightUselessAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +page-frame.js:4:1 lint/nursery/noPlaywrightUselessAwait FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Unnecessary await expression. - 1 │ await page.frame('iframe'); - 2 │ await page.frames(); - > 3 │ await page.url(); + 2 │ await page.frame('iframe'); + 3 │ await page.frames(); + > 4 │ await page.url(); │ ^^^^^^^^^^^^^^^^ - 4 │ + 5 │ i This method does not return a Promise. @@ -73,7 +75,7 @@ page-frame.js:3:1 lint/nursery/noPlaywrightUselessAwait FIXABLE ━━━━ i Safe fix: Remove unnecessary await - 3 │ await·page.url(); + 4 │ await·page.url(); │ ------ ``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/valid/async-methods.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/valid/async-methods.js index 319691319bc6..ec170f3b5242 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/valid/async-methods.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/valid/async-methods.js @@ -1,3 +1,4 @@ +/* should not generate diagnostics */ await page.locator('.my-element').click(); await page.goto('https://example.com'); await expect(page.locator('.foo')).toBeVisible(); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/valid/async-methods.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/valid/async-methods.js.snap index 7ca9ba039cf0..0afc3d3e55a5 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/valid/async-methods.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/valid/async-methods.js.snap @@ -1,10 +1,10 @@ --- source: crates/biome_js_analyze/tests/spec_tests.rs -assertion_line: 152 expression: async-methods.js --- # Input ```js +/* should not generate diagnostics */ await page.locator('.my-element').click(); await page.goto('https://example.com'); await expect(page.locator('.foo')).toBeVisible(); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/valid/expect-with-async-modifiers.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/valid/expect-with-async-modifiers.js index a06f2cff36f5..c521d77ebe3b 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/valid/expect-with-async-modifiers.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/valid/expect-with-async-modifiers.js @@ -1,3 +1,4 @@ +/* should not generate diagnostics */ // Valid: expect with .resolves modifier await expect(promise).resolves.toBe(1); await expect(fetchData()).resolves.toEqual({ foo: 'bar' }); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/valid/expect-with-async-modifiers.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/valid/expect-with-async-modifiers.js.snap index f3c1a43e5eb2..295d01c9ee9c 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/valid/expect-with-async-modifiers.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/valid/expect-with-async-modifiers.js.snap @@ -4,6 +4,7 @@ expression: expect-with-async-modifiers.js --- # Input ```js +/* should not generate diagnostics */ // Valid: expect with .resolves modifier await expect(promise).resolves.toBe(1); await expect(fetchData()).resolves.toEqual({ foo: 'bar' }); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/valid/sync-no-await.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/valid/sync-no-await.js index 6566793cf3b4..756baff005fb 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/valid/sync-no-await.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/valid/sync-no-await.js @@ -1,3 +1,4 @@ +/* should not generate diagnostics */ page.locator('.my-element'); page.getByRole('button'); expect(1).toBe(1); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/valid/sync-no-await.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/valid/sync-no-await.js.snap index 6dfa34061541..4f178ce52db6 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/valid/sync-no-await.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/valid/sync-no-await.js.snap @@ -1,10 +1,10 @@ --- source: crates/biome_js_analyze/tests/spec_tests.rs -assertion_line: 152 expression: sync-no-await.js --- # Input ```js +/* should not generate diagnostics */ page.locator('.my-element'); page.getByRole('button'); expect(1).toBe(1); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/invalid/simple.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/invalid/simple.js index d1ae89aaed0d..043bff9316c6 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/invalid/simple.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/invalid/simple.js @@ -1,2 +1,3 @@ +/* should generate diagnostics */ await page.waitForNavigation(); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/invalid/simple.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/invalid/simple.js.snap index ce6cecbc880c..9725b3e24e1d 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/invalid/simple.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/invalid/simple.js.snap @@ -1,10 +1,10 @@ --- source: crates/biome_js_analyze/tests/spec_tests.rs -assertion_line: 152 expression: simple.js --- # Input ```js +/* should generate diagnostics */ await page.waitForNavigation(); @@ -12,13 +12,14 @@ await page.waitForNavigation(); # Diagnostics ``` -simple.js:1:7 lint/nursery/noPlaywrightWaitForNavigation ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +simple.js:2:7 lint/nursery/noPlaywrightWaitForNavigation ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Unexpected use of page.waitForNavigation(). - > 1 │ await page.waitForNavigation(); + 1 │ /* should generate diagnostics */ + > 2 │ await page.waitForNavigation(); │ ^^^^^^^^^^^^^^^^^^^^^^^^ - 2 │ + 3 │ i waitForNavigation() is deprecated. Use page.waitForURL() or page.waitForLoadState() instead. diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/invalid/with-options.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/invalid/with-options.js index 070a720f1003..3ea8ed52bc08 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/invalid/with-options.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/invalid/with-options.js @@ -1,3 +1,4 @@ +/* should generate diagnostics */ await page.click('button'); await page.waitForNavigation({ waitUntil: 'networkidle' }); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/invalid/with-options.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/invalid/with-options.js.snap index f013b4dc8c7d..e0b7341a4b80 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/invalid/with-options.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/invalid/with-options.js.snap @@ -1,10 +1,10 @@ --- source: crates/biome_js_analyze/tests/spec_tests.rs -assertion_line: 152 expression: with-options.js --- # Input ```js +/* should generate diagnostics */ await page.click('button'); await page.waitForNavigation({ waitUntil: 'networkidle' }); @@ -13,14 +13,15 @@ await page.waitForNavigation({ waitUntil: 'networkidle' }); # Diagnostics ``` -with-options.js:2:7 lint/nursery/noPlaywrightWaitForNavigation ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +with-options.js:3:7 lint/nursery/noPlaywrightWaitForNavigation ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Unexpected use of page.waitForNavigation(). - 1 │ await page.click('button'); - > 2 │ await page.waitForNavigation({ waitUntil: 'networkidle' }); + 1 │ /* should generate diagnostics */ + 2 │ await page.click('button'); + > 3 │ await page.waitForNavigation({ waitUntil: 'networkidle' }); │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 3 │ + 4 │ i waitForNavigation() is deprecated. Use page.waitForURL() or page.waitForLoadState() instead. diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/valid/alternatives.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/valid/alternatives.js index 524aa345b687..4a24f21d0d61 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/valid/alternatives.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/valid/alternatives.js @@ -1,3 +1,4 @@ +/* should not generate diagnostics */ await page.waitForURL('/home'); await page.waitForLoadState('load'); await page.goto('/home'); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/valid/alternatives.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/valid/alternatives.js.snap index 96cb0b53a266..7f727616c57e 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/valid/alternatives.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForNavigation/valid/alternatives.js.snap @@ -4,6 +4,7 @@ expression: alternatives.js --- # Input ```js +/* should not generate diagnostics */ await page.waitForURL('/home'); await page.waitForLoadState('load'); await page.goto('/home'); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForSelector/invalid/simple.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForSelector/invalid/simple.js index dd0c5bbe8941..845672401448 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForSelector/invalid/simple.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForSelector/invalid/simple.js @@ -1,2 +1,3 @@ +/* should generate diagnostics */ await page.waitForSelector('.submit-button'); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForSelector/invalid/simple.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForSelector/invalid/simple.js.snap index ef674a3c8a85..85ea212e97ef 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForSelector/invalid/simple.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForSelector/invalid/simple.js.snap @@ -1,10 +1,10 @@ --- source: crates/biome_js_analyze/tests/spec_tests.rs -assertion_line: 152 expression: simple.js --- # Input ```js +/* should generate diagnostics */ await page.waitForSelector('.submit-button'); @@ -12,13 +12,14 @@ await page.waitForSelector('.submit-button'); # Diagnostics ``` -simple.js:1:7 lint/nursery/noPlaywrightWaitForSelector ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +simple.js:2:7 lint/nursery/noPlaywrightWaitForSelector ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Unexpected use of page.waitForSelector(). - > 1 │ await page.waitForSelector('.submit-button'); + 1 │ /* should generate diagnostics */ + > 2 │ await page.waitForSelector('.submit-button'); │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 2 │ + 3 │ i Use locator-based page.locator() or page.getByRole() APIs instead. diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForSelector/invalid/with-state.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForSelector/invalid/with-state.js index ca69b4010007..710c9467e49b 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForSelector/invalid/with-state.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForSelector/invalid/with-state.js @@ -1,3 +1,4 @@ +/* should generate diagnostics */ await page.waitForSelector('#dialog', { state: 'visible' }); await page.click('#dialog .button'); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForSelector/invalid/with-state.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForSelector/invalid/with-state.js.snap index 0392523664fe..424bd1cba5e7 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForSelector/invalid/with-state.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForSelector/invalid/with-state.js.snap @@ -1,10 +1,10 @@ --- source: crates/biome_js_analyze/tests/spec_tests.rs -assertion_line: 152 expression: with-state.js --- # Input ```js +/* should generate diagnostics */ await page.waitForSelector('#dialog', { state: 'visible' }); await page.click('#dialog .button'); @@ -13,14 +13,15 @@ await page.click('#dialog .button'); # Diagnostics ``` -with-state.js:1:7 lint/nursery/noPlaywrightWaitForSelector ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +with-state.js:2:7 lint/nursery/noPlaywrightWaitForSelector ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Unexpected use of page.waitForSelector(). - > 1 │ await page.waitForSelector('#dialog', { state: 'visible' }); + 1 │ /* should generate diagnostics */ + > 2 │ await page.waitForSelector('#dialog', { state: 'visible' }); │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 2 │ await page.click('#dialog .button'); - 3 │ + 3 │ await page.click('#dialog .button'); + 4 │ i Use locator-based page.locator() or page.getByRole() APIs instead. diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForSelector/valid/locators.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForSelector/valid/locators.js index b8499282ab09..642863211d74 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForSelector/valid/locators.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForSelector/valid/locators.js @@ -1,3 +1,4 @@ +/* should not generate diagnostics */ await page.locator('.submit-button').click(); await expect(page.locator('#dialog')).toBeVisible(); const button = page.getByRole('button', { name: 'Submit' }); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForSelector/valid/locators.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForSelector/valid/locators.js.snap index 15f288943037..298f10a8d370 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForSelector/valid/locators.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForSelector/valid/locators.js.snap @@ -1,10 +1,10 @@ --- source: crates/biome_js_analyze/tests/spec_tests.rs -assertion_line: 152 expression: locators.js --- # Input ```js +/* should not generate diagnostics */ await page.locator('.submit-button').click(); await expect(page.locator('#dialog')).toBeVisible(); const button = page.getByRole('button', { name: 'Submit' }); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/invalid/in-test.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/invalid/in-test.js index 03d6728f28ef..5b07e16134c6 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/invalid/in-test.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/invalid/in-test.js @@ -1,3 +1,4 @@ +/* should generate diagnostics */ test('wait', async ({ page }) => { await page.click('button'); await page.waitForTimeout(1000); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/invalid/in-test.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/invalid/in-test.js.snap index 2b23422fc820..87c6af4c270e 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/invalid/in-test.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/invalid/in-test.js.snap @@ -4,6 +4,7 @@ expression: in-test.js --- # Input ```js +/* should generate diagnostics */ test('wait', async ({ page }) => { await page.click('button'); await page.waitForTimeout(1000); @@ -14,16 +15,16 @@ test('wait', async ({ page }) => { # Diagnostics ``` -in-test.js:3:11 lint/nursery/noPlaywrightWaitForTimeout ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +in-test.js:4:11 lint/nursery/noPlaywrightWaitForTimeout ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Unexpected use of page.waitForTimeout(). - 1 │ test('wait', async ({ page }) => { - 2 │ await page.click('button'); - > 3 │ await page.waitForTimeout(1000); + 2 │ test('wait', async ({ page }) => { + 3 │ await page.click('button'); + > 4 │ await page.waitForTimeout(1000); │ ^^^^^^^^^^^^^^^^^^^^^^^^^ - 4 │ }); - 5 │ + 5 │ }); + 6 │ i Prefer using built-in wait methods like page.waitForLoadState(), page.waitForURL(), or page.waitForFunction() instead. diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/invalid/simple.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/invalid/simple.js index 48de15e92214..b193cd5e57ea 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/invalid/simple.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/invalid/simple.js @@ -1,2 +1,3 @@ +/* should generate diagnostics */ await page.waitForTimeout(5000); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/invalid/simple.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/invalid/simple.js.snap index b91f95b1e934..518a31a41500 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/invalid/simple.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/invalid/simple.js.snap @@ -4,6 +4,7 @@ expression: simple.js --- # Input ```js +/* should generate diagnostics */ await page.waitForTimeout(5000); @@ -11,13 +12,14 @@ await page.waitForTimeout(5000); # Diagnostics ``` -simple.js:1:7 lint/nursery/noPlaywrightWaitForTimeout ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +simple.js:2:7 lint/nursery/noPlaywrightWaitForTimeout ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Unexpected use of page.waitForTimeout(). - > 1 │ await page.waitForTimeout(5000); + 1 │ /* should generate diagnostics */ + > 2 │ await page.waitForTimeout(5000); │ ^^^^^^^^^^^^^^^^^^^^^^^^^ - 2 │ + 3 │ i Prefer using built-in wait methods like page.waitForLoadState(), page.waitForURL(), or page.waitForFunction() instead. diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/valid/other-methods.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/valid/other-methods.js index 576527a28c92..a2f75edbe895 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/valid/other-methods.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/valid/other-methods.js @@ -1,3 +1,4 @@ +/* should not generate diagnostics */ await page.waitForLoadState(); await page.waitForURL('/home'); await page.waitForFunction(() => window.innerWidth < 100); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/valid/other-methods.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/valid/other-methods.js.snap index 483eb4cdf238..345a0ac44976 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/valid/other-methods.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightWaitForTimeout/valid/other-methods.js.snap @@ -1,10 +1,10 @@ --- source: crates/biome_js_analyze/tests/spec_tests.rs -assertion_line: 152 expression: other-methods.js --- # Input ```js +/* should not generate diagnostics */ await page.waitForLoadState(); await page.waitForURL('/home'); await page.waitForFunction(() => window.innerWidth < 100); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/async.js b/crates/biome_js_analyze/tests/specs/nursery/usePlaywrightValidDescribeCallback/invalid/async.js similarity index 71% rename from crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/async.js rename to crates/biome_js_analyze/tests/specs/nursery/usePlaywrightValidDescribeCallback/invalid/async.js index 03eb46689518..1b93842a806f 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/async.js +++ b/crates/biome_js_analyze/tests/specs/nursery/usePlaywrightValidDescribeCallback/invalid/async.js @@ -1,3 +1,4 @@ +/* should generate diagnostics */ test.describe('suite', async () => { test('one', async ({ page }) => {}); }); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/async.js.snap b/crates/biome_js_analyze/tests/specs/nursery/usePlaywrightValidDescribeCallback/invalid/async.js.snap similarity index 58% rename from crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/async.js.snap rename to crates/biome_js_analyze/tests/specs/nursery/usePlaywrightValidDescribeCallback/invalid/async.js.snap index 1ebc7662028f..7736c4287f88 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/async.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/usePlaywrightValidDescribeCallback/invalid/async.js.snap @@ -4,6 +4,7 @@ expression: async.js --- # Input ```js +/* should generate diagnostics */ test.describe('suite', async () => { test('one', async ({ page }) => {}); }); @@ -14,16 +15,17 @@ test.describe('suite', async () => { # Diagnostics ``` -async.js:1:1 lint/nursery/noPlaywrightValidDescribeCallback ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +async.js:2:1 lint/nursery/usePlaywrightValidDescribeCallback ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Describe callback should not be async. - > 1 │ test.describe('suite', async () => { + 1 │ /* should generate diagnostics */ + > 2 │ test.describe('suite', async () => { │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - > 2 │ test('one', async ({ page }) => {}); - > 3 │ }); + > 3 │ test('one', async ({ page }) => {}); + > 4 │ }); │ ^^ - 4 │ + 5 │ i Describe blocks are meant to organize tests, not contain asynchronous logic. Async operations should be placed within individual test callbacks. diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/has-params.js b/crates/biome_js_analyze/tests/specs/nursery/usePlaywrightValidDescribeCallback/invalid/has-params.js similarity index 70% rename from crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/has-params.js rename to crates/biome_js_analyze/tests/specs/nursery/usePlaywrightValidDescribeCallback/invalid/has-params.js index f432661fef42..d23f257cf553 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/has-params.js +++ b/crates/biome_js_analyze/tests/specs/nursery/usePlaywrightValidDescribeCallback/invalid/has-params.js @@ -1,3 +1,4 @@ +/* should generate diagnostics */ test.describe('suite', (done) => { test('one', async ({ page }) => {}); }); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/has-params.js.snap b/crates/biome_js_analyze/tests/specs/nursery/usePlaywrightValidDescribeCallback/invalid/has-params.js.snap similarity index 55% rename from crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/has-params.js.snap rename to crates/biome_js_analyze/tests/specs/nursery/usePlaywrightValidDescribeCallback/invalid/has-params.js.snap index d4170f203bcb..3c4cce0355f1 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/has-params.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/usePlaywrightValidDescribeCallback/invalid/has-params.js.snap @@ -4,6 +4,7 @@ expression: has-params.js --- # Input ```js +/* should generate diagnostics */ test.describe('suite', (done) => { test('one', async ({ page }) => {}); }); @@ -14,16 +15,17 @@ test.describe('suite', (done) => { # Diagnostics ``` -has-params.js:1:1 lint/nursery/noPlaywrightValidDescribeCallback ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +has-params.js:2:1 lint/nursery/usePlaywrightValidDescribeCallback ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Describe callback should not have parameters. - > 1 │ test.describe('suite', (done) => { + 1 │ /* should generate diagnostics */ + > 2 │ test.describe('suite', (done) => { │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - > 2 │ test('one', async ({ page }) => {}); - > 3 │ }); + > 3 │ test('one', async ({ page }) => {}); + > 4 │ }); │ ^^ - 4 │ + 5 │ i Describe callbacks are invoked without arguments by the test framework. diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/missing-callback.js b/crates/biome_js_analyze/tests/specs/nursery/usePlaywrightValidDescribeCallback/invalid/missing-callback.js similarity index 65% rename from crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/missing-callback.js rename to crates/biome_js_analyze/tests/specs/nursery/usePlaywrightValidDescribeCallback/invalid/missing-callback.js index f9b26f95fdd2..839cd7f6d8e2 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/missing-callback.js +++ b/crates/biome_js_analyze/tests/specs/nursery/usePlaywrightValidDescribeCallback/invalid/missing-callback.js @@ -1,3 +1,4 @@ +/* should generate diagnostics */ // Test missing callback test.describe("foo"); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/missing-callback.js.snap b/crates/biome_js_analyze/tests/specs/nursery/usePlaywrightValidDescribeCallback/invalid/missing-callback.js.snap similarity index 54% rename from crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/missing-callback.js.snap rename to crates/biome_js_analyze/tests/specs/nursery/usePlaywrightValidDescribeCallback/invalid/missing-callback.js.snap index 3f0046beece2..34c433a7345f 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/missing-callback.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/usePlaywrightValidDescribeCallback/invalid/missing-callback.js.snap @@ -4,6 +4,7 @@ expression: missing-callback.js --- # Input ```js +/* should generate diagnostics */ // Test missing callback test.describe("foo"); @@ -13,15 +14,16 @@ describe("bar"); # Diagnostics ``` -missing-callback.js:2:1 lint/nursery/noPlaywrightValidDescribeCallback ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +missing-callback.js:3:1 lint/nursery/usePlaywrightValidDescribeCallback ━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Describe requires a callback function. - 1 │ // Test missing callback - > 2 │ test.describe("foo"); + 1 │ /* should generate diagnostics */ + 2 │ // Test missing callback + > 3 │ test.describe("foo"); │ ^^^^^^^^^^^^^^^^^^^^ - 3 │ - 4 │ describe("bar"); + 4 │ + 5 │ describe("bar"); i The second argument to describe must be a function that contains the test definitions. @@ -31,15 +33,15 @@ missing-callback.js:2:1 lint/nursery/noPlaywrightValidDescribeCallback ━━━ ``` ``` -missing-callback.js:4:1 lint/nursery/noPlaywrightValidDescribeCallback ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +missing-callback.js:5:1 lint/nursery/usePlaywrightValidDescribeCallback ━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Describe requires a callback function. - 2 │ test.describe("foo"); - 3 │ - > 4 │ describe("bar"); + 3 │ test.describe("foo"); + 4 │ + > 5 │ describe("bar"); │ ^^^^^^^^^^^^^^^ - 5 │ + 6 │ i The second argument to describe must be a function that contains the test definitions. diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/not-function.js b/crates/biome_js_analyze/tests/specs/nursery/usePlaywrightValidDescribeCallback/invalid/not-function.js similarity index 86% rename from crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/not-function.js rename to crates/biome_js_analyze/tests/specs/nursery/usePlaywrightValidDescribeCallback/invalid/not-function.js index a1ef4d7ff456..14065eba3c1b 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/not-function.js +++ b/crates/biome_js_analyze/tests/specs/nursery/usePlaywrightValidDescribeCallback/invalid/not-function.js @@ -1,3 +1,4 @@ +/* should generate diagnostics */ // Test non-function callbacks test.describe("foo", "foo2"); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/not-function.js.snap b/crates/biome_js_analyze/tests/specs/nursery/usePlaywrightValidDescribeCallback/invalid/not-function.js.snap similarity index 56% rename from crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/not-function.js.snap rename to crates/biome_js_analyze/tests/specs/nursery/usePlaywrightValidDescribeCallback/invalid/not-function.js.snap index 67354d45db81..238c96053a21 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/invalid/not-function.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/usePlaywrightValidDescribeCallback/invalid/not-function.js.snap @@ -4,6 +4,7 @@ expression: not-function.js --- # Input ```js +/* should generate diagnostics */ // Test non-function callbacks test.describe("foo", "foo2"); @@ -21,15 +22,16 @@ test.describe("another", someVariable); # Diagnostics ``` -not-function.js:2:1 lint/nursery/noPlaywrightValidDescribeCallback ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +not-function.js:3:1 lint/nursery/usePlaywrightValidDescribeCallback ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Describe callback must be a function. - 1 │ // Test non-function callbacks - > 2 │ test.describe("foo", "foo2"); + 1 │ /* should generate diagnostics */ + 2 │ // Test non-function callbacks + > 3 │ test.describe("foo", "foo2"); │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 3 │ - 4 │ test.describe("bar", 42); + 4 │ + 5 │ test.describe("bar", 42); i The second argument to describe must be a function, not a string, number, object, or other type. @@ -39,16 +41,16 @@ not-function.js:2:1 lint/nursery/noPlaywrightValidDescribeCallback ━━━━ ``` ``` -not-function.js:4:1 lint/nursery/noPlaywrightValidDescribeCallback ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +not-function.js:5:1 lint/nursery/usePlaywrightValidDescribeCallback ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Describe callback must be a function. - 2 │ test.describe("foo", "foo2"); - 3 │ - > 4 │ test.describe("bar", 42); + 3 │ test.describe("foo", "foo2"); + 4 │ + > 5 │ test.describe("bar", 42); │ ^^^^^^^^^^^^^^^^^^^^^^^^ - 5 │ - 6 │ test.describe("baz", { tag: ["@slow"] }); + 6 │ + 7 │ test.describe("baz", { tag: ["@slow"] }); i The second argument to describe must be a function, not a string, number, object, or other type. @@ -58,16 +60,16 @@ not-function.js:4:1 lint/nursery/noPlaywrightValidDescribeCallback ━━━━ ``` ``` -not-function.js:6:1 lint/nursery/noPlaywrightValidDescribeCallback ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +not-function.js:7:1 lint/nursery/usePlaywrightValidDescribeCallback ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Describe callback must be a function. - 4 │ test.describe("bar", 42); - 5 │ - > 6 │ test.describe("baz", { tag: ["@slow"] }); + 5 │ test.describe("bar", 42); + 6 │ + > 7 │ test.describe("baz", { tag: ["@slow"] }); │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 7 │ - 8 │ describe("qux", null); + 8 │ + 9 │ describe("qux", null); i The second argument to describe must be a function, not a string, number, object, or other type. @@ -77,16 +79,16 @@ not-function.js:6:1 lint/nursery/noPlaywrightValidDescribeCallback ━━━━ ``` ``` -not-function.js:8:1 lint/nursery/noPlaywrightValidDescribeCallback ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +not-function.js:9:1 lint/nursery/usePlaywrightValidDescribeCallback ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Describe callback must be a function. - 6 │ test.describe("baz", { tag: ["@slow"] }); - 7 │ - > 8 │ describe("qux", null); + 7 │ test.describe("baz", { tag: ["@slow"] }); + 8 │ + > 9 │ describe("qux", null); │ ^^^^^^^^^^^^^^^^^^^^^ - 9 │ - 10 │ describe("suite", undefined); + 10 │ + 11 │ describe("suite", undefined); i The second argument to describe must be a function, not a string, number, object, or other type. @@ -96,16 +98,16 @@ not-function.js:8:1 lint/nursery/noPlaywrightValidDescribeCallback ━━━━ ``` ``` -not-function.js:10:1 lint/nursery/noPlaywrightValidDescribeCallback ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +not-function.js:11:1 lint/nursery/usePlaywrightValidDescribeCallback ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Describe callback must be a function. - 8 │ describe("qux", null); - 9 │ - > 10 │ describe("suite", undefined); + 9 │ describe("qux", null); + 10 │ + > 11 │ describe("suite", undefined); │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 11 │ - 12 │ test.describe("another", someVariable); + 12 │ + 13 │ test.describe("another", someVariable); i The second argument to describe must be a function, not a string, number, object, or other type. @@ -115,15 +117,15 @@ not-function.js:10:1 lint/nursery/noPlaywrightValidDescribeCallback ━━━━ ``` ``` -not-function.js:12:1 lint/nursery/noPlaywrightValidDescribeCallback ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +not-function.js:13:1 lint/nursery/usePlaywrightValidDescribeCallback ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Describe callback must be a function. - 10 │ describe("suite", undefined); - 11 │ - > 12 │ test.describe("another", someVariable); + 11 │ describe("suite", undefined); + 12 │ + > 13 │ test.describe("another", someVariable); │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 13 │ + 14 │ i The second argument to describe must be a function, not a string, number, object, or other type. diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/valid/correct.js b/crates/biome_js_analyze/tests/specs/nursery/usePlaywrightValidDescribeCallback/valid/correct.js similarity index 83% rename from crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/valid/correct.js rename to crates/biome_js_analyze/tests/specs/nursery/usePlaywrightValidDescribeCallback/valid/correct.js index 68e1e6a2a8de..be4bfe242757 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/valid/correct.js +++ b/crates/biome_js_analyze/tests/specs/nursery/usePlaywrightValidDescribeCallback/valid/correct.js @@ -1,3 +1,4 @@ +/* should not generate diagnostics */ test.describe('suite', () => { test('one', async ({ page }) => {}); test('two', async ({ page }) => {}); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/valid/correct.js.snap b/crates/biome_js_analyze/tests/specs/nursery/usePlaywrightValidDescribeCallback/valid/correct.js.snap similarity index 88% rename from crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/valid/correct.js.snap rename to crates/biome_js_analyze/tests/specs/nursery/usePlaywrightValidDescribeCallback/valid/correct.js.snap index 7ce115644972..e026682e3870 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightValidDescribeCallback/valid/correct.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/usePlaywrightValidDescribeCallback/valid/correct.js.snap @@ -1,10 +1,10 @@ --- source: crates/biome_js_analyze/tests/spec_tests.rs -assertion_line: 152 expression: correct.js --- # Input ```js +/* should not generate diagnostics */ test.describe('suite', () => { test('one', async ({ page }) => {}); test('two', async ({ page }) => {}); diff --git a/crates/biome_js_analyze/tests/specs/suspicious/noFocusedTests/invalid.js b/crates/biome_js_analyze/tests/specs/suspicious/noFocusedTests/invalid.js index 7c3d207faa1a..3ee6ca1eb46f 100644 --- a/crates/biome_js_analyze/tests/specs/suspicious/noFocusedTests/invalid.js +++ b/crates/biome_js_analyze/tests/specs/suspicious/noFocusedTests/invalid.js @@ -28,3 +28,7 @@ test.only(name = name || "bar", () => {}); describe.only.each([["a"], ["b"]])("%s", (a) => {}); it.only.each([["a"], ["b"]])("%s", (a) => {}); test.only.each([["a"], ["b"]])("%s", (a) => {}); + +// Playwright patterns: test.describe.only +test.describe.only("bar", () => {}); +test.describe.only.each([["a"], ["b"]])("%s", (a) => {}); diff --git a/crates/biome_js_analyze/tests/specs/suspicious/noFocusedTests/invalid.js.snap b/crates/biome_js_analyze/tests/specs/suspicious/noFocusedTests/invalid.js.snap index 4c724c283d7e..b65f22d1ee8e 100644 --- a/crates/biome_js_analyze/tests/specs/suspicious/noFocusedTests/invalid.js.snap +++ b/crates/biome_js_analyze/tests/specs/suspicious/noFocusedTests/invalid.js.snap @@ -35,6 +35,10 @@ describe.only.each([["a"], ["b"]])("%s", (a) => {}); it.only.each([["a"], ["b"]])("%s", (a) => {}); test.only.each([["a"], ["b"]])("%s", (a) => {}); +// Playwright patterns: test.describe.only +test.describe.only("bar", () => {}); +test.describe.only.each([["a"], ["b"]])("%s", (a) => {}); + ``` # Diagnostics @@ -554,6 +558,7 @@ invalid.js:30:6 lint/suspicious/noFocusedTests FIXABLE ━━━━━━━ > 30 │ test.only.each([["a"], ["b"]])("%s", (a) => {}); │ ^^^^ 31 │ + 32 │ // Playwright patterns: test.describe.only i The 'only' method is often used for debugging or during implementation. @@ -565,3 +570,47 @@ invalid.js:30:6 lint/suspicious/noFocusedTests FIXABLE ━━━━━━━ │ ----- ``` + +``` +invalid.js:33:15 lint/suspicious/noFocusedTests FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Don't focus the test. + + 32 │ // Playwright patterns: test.describe.only + > 33 │ test.describe.only("bar", () => {}); + │ ^^^^ + 34 │ test.describe.only.each([["a"], ["b"]])("%s", (a) => {}); + 35 │ + + i The 'only' method is often used for debugging or during implementation. + + i Consider removing 'only' to ensure all tests are executed. + + i Unsafe fix: Remove focus from test. + + 33 │ test.describe.only("bar",·()·=>·{}); + │ ----- + +``` + +``` +invalid.js:34:15 lint/suspicious/noFocusedTests FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Don't focus the test. + + 32 │ // Playwright patterns: test.describe.only + 33 │ test.describe.only("bar", () => {}); + > 34 │ test.describe.only.each([["a"], ["b"]])("%s", (a) => {}); + │ ^^^^ + 35 │ + + i The 'only' method is often used for debugging or during implementation. + + i Consider removing 'only' to ensure all tests are executed. + + i Unsafe fix: Remove focus from test. + + 34 │ test.describe.only.each([["a"],·["b"]])("%s",·(a)·=>·{}); + │ ----- + +``` diff --git a/crates/biome_js_analyze/tests/specs/suspicious/noFocusedTests/valid.js b/crates/biome_js_analyze/tests/specs/suspicious/noFocusedTests/valid.js index 2b893df280be..67987f122721 100644 --- a/crates/biome_js_analyze/tests/specs/suspicious/noFocusedTests/valid.js +++ b/crates/biome_js_analyze/tests/specs/suspicious/noFocusedTests/valid.js @@ -7,4 +7,6 @@ xit("test", () => {}) describe.each([["a"], ["b"]])("%s", (a) => {}); it.each([["a"], ["b"]])("%s", (a) => {}); test.each([["a"], ["b"]])("%s", (a) => {}); -foo.fit(); \ No newline at end of file +foo.fit(); +test.describe("test", () => {}); +test.describe.each([["a"], ["b"]])("%s", (a) => {}); \ No newline at end of file diff --git a/crates/biome_js_analyze/tests/specs/suspicious/noFocusedTests/valid.js.snap b/crates/biome_js_analyze/tests/specs/suspicious/noFocusedTests/valid.js.snap index 59bc5c698d6a..44bfde30bf25 100644 --- a/crates/biome_js_analyze/tests/specs/suspicious/noFocusedTests/valid.js.snap +++ b/crates/biome_js_analyze/tests/specs/suspicious/noFocusedTests/valid.js.snap @@ -14,4 +14,6 @@ describe.each([["a"], ["b"]])("%s", (a) => {}); it.each([["a"], ["b"]])("%s", (a) => {}); test.each([["a"], ["b"]])("%s", (a) => {}); foo.fit(); +test.describe("test", () => {}); +test.describe.each([["a"], ["b"]])("%s", (a) => {}); ``` diff --git a/crates/biome_rule_options/src/lib.rs b/crates/biome_rule_options/src/lib.rs index 996ee2fac0d4..7e91d52413ed 100644 --- a/crates/biome_rule_options/src/lib.rs +++ b/crates/biome_rule_options/src/lib.rs @@ -154,9 +154,7 @@ pub mod no_playwright_force_option; pub mod no_playwright_missing_await; pub mod no_playwright_networkidle; pub mod no_playwright_page_pause; -pub mod no_playwright_skipped_test; pub mod no_playwright_useless_await; -pub mod no_playwright_valid_describe_callback; pub mod no_playwright_wait_for_navigation; pub mod no_playwright_wait_for_selector; pub mod no_playwright_wait_for_timeout; @@ -355,6 +353,7 @@ pub mod use_numeric_separators; pub mod use_object_spread; pub mod use_optional_chain; pub mod use_parse_int_radix; +pub mod use_playwright_valid_describe_callback; pub mod use_qwik_classlist; pub mod use_qwik_method_usage; pub mod use_qwik_valid_lexical_scope; diff --git a/crates/biome_rule_options/src/no_playwright_valid_describe_callback.rs b/crates/biome_rule_options/src/no_playwright_valid_describe_callback.rs deleted file mode 100644 index dd5c37c4082e..000000000000 --- a/crates/biome_rule_options/src/no_playwright_valid_describe_callback.rs +++ /dev/null @@ -1,6 +0,0 @@ -use biome_deserialize_macros::{Deserializable, Merge}; -use serde::{Deserialize, Serialize}; -#[derive(Default, Clone, Debug, Deserialize, Deserializable, Merge, Eq, PartialEq, Serialize)] -#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] -#[serde(rename_all = "camelCase", deny_unknown_fields, default)] -pub struct NoPlaywrightValidDescribeCallbackOptions {} diff --git a/crates/biome_rule_options/src/no_playwright_skipped_test.rs b/crates/biome_rule_options/src/use_playwright_valid_describe_callback.rs similarity index 84% rename from crates/biome_rule_options/src/no_playwright_skipped_test.rs rename to crates/biome_rule_options/src/use_playwright_valid_describe_callback.rs index d2542f1f2d9d..90adcfafc959 100644 --- a/crates/biome_rule_options/src/no_playwright_skipped_test.rs +++ b/crates/biome_rule_options/src/use_playwright_valid_describe_callback.rs @@ -3,4 +3,4 @@ use serde::{Deserialize, Serialize}; #[derive(Default, Clone, Debug, Deserialize, Deserializable, Merge, Eq, PartialEq, Serialize)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[serde(rename_all = "camelCase", deny_unknown_fields, default)] -pub struct NoPlaywrightSkippedTestOptions {} +pub struct UsePlaywrightValidDescribeCallbackOptions {} diff --git a/packages/@biomejs/backend-jsonrpc/src/workspace.ts b/packages/@biomejs/backend-jsonrpc/src/workspace.ts index ee5d155f1f82..8bd13ebe98ff 100644 --- a/packages/@biomejs/backend-jsonrpc/src/workspace.ts +++ b/packages/@biomejs/backend-jsonrpc/src/workspace.ts @@ -1967,21 +1967,11 @@ See https://biomejs.dev/linter/rules/no-playwright-page-pause */ noPlaywrightPagePause?: NoPlaywrightPagePauseConfiguration; /** - * Disallow usage of .skip annotation in Playwright tests. -See https://biomejs.dev/linter/rules/no-playwright-skipped-test - */ - noPlaywrightSkippedTest?: NoPlaywrightSkippedTestConfiguration; - /** * Disallow unnecessary await for Playwright methods that don't return promises. See https://biomejs.dev/linter/rules/no-playwright-useless-await */ noPlaywrightUselessAwait?: NoPlaywrightUselessAwaitConfiguration; /** - * Enforce valid describe() callback. -See https://biomejs.dev/linter/rules/no-playwright-valid-describe-callback - */ - noPlaywrightValidDescribeCallback?: NoPlaywrightValidDescribeCallbackConfiguration; - /** * Disallow using page.waitForNavigation(). See https://biomejs.dev/linter/rules/no-playwright-wait-for-navigation */ @@ -2121,6 +2111,11 @@ See https://biomejs.dev/linter/rules/use-max-params */ useMaxParams?: UseMaxParamsConfiguration; /** + * Enforce valid describe() callback. +See https://biomejs.dev/linter/rules/use-playwright-valid-describe-callback + */ + usePlaywrightValidDescribeCallback?: UsePlaywrightValidDescribeCallbackConfiguration; + /** * Disallow use* hooks outside of component$ or other use* hooks in Qwik applications. See https://biomejs.dev/linter/rules/use-qwik-method-usage */ @@ -3695,15 +3690,9 @@ export type NoPlaywrightNetworkidleConfiguration = export type NoPlaywrightPagePauseConfiguration = | RulePlainConfiguration | RuleWithNoPlaywrightPagePauseOptions; -export type NoPlaywrightSkippedTestConfiguration = - | RulePlainConfiguration - | RuleWithNoPlaywrightSkippedTestOptions; export type NoPlaywrightUselessAwaitConfiguration = | RulePlainConfiguration | RuleWithNoPlaywrightUselessAwaitOptions; -export type NoPlaywrightValidDescribeCallbackConfiguration = - | RulePlainConfiguration - | RuleWithNoPlaywrightValidDescribeCallbackOptions; export type NoPlaywrightWaitForNavigationConfiguration = | RulePlainConfiguration | RuleWithNoPlaywrightWaitForNavigationOptions; @@ -3785,6 +3774,9 @@ export type UseFindConfiguration = export type UseMaxParamsConfiguration = | RulePlainConfiguration | RuleWithUseMaxParamsOptions; +export type UsePlaywrightValidDescribeCallbackConfiguration = + | RulePlainConfiguration + | RuleWithUsePlaywrightValidDescribeCallbackOptions; export type UseQwikMethodUsageConfiguration = | RulePlainConfiguration | RuleWithUseQwikMethodUsageOptions; @@ -5156,19 +5148,11 @@ export interface RuleWithNoPlaywrightPagePauseOptions { level: RulePlainConfiguration; options?: NoPlaywrightPagePauseOptions; } -export interface RuleWithNoPlaywrightSkippedTestOptions { - level: RulePlainConfiguration; - options?: NoPlaywrightSkippedTestOptions; -} export interface RuleWithNoPlaywrightUselessAwaitOptions { fix?: FixKind; level: RulePlainConfiguration; options?: NoPlaywrightUselessAwaitOptions; } -export interface RuleWithNoPlaywrightValidDescribeCallbackOptions { - level: RulePlainConfiguration; - options?: NoPlaywrightValidDescribeCallbackOptions; -} export interface RuleWithNoPlaywrightWaitForNavigationOptions { level: RulePlainConfiguration; options?: NoPlaywrightWaitForNavigationOptions; @@ -5283,6 +5267,10 @@ export interface RuleWithUseMaxParamsOptions { level: RulePlainConfiguration; options?: UseMaxParamsOptions; } +export interface RuleWithUsePlaywrightValidDescribeCallbackOptions { + level: RulePlainConfiguration; + options?: UsePlaywrightValidDescribeCallbackOptions; +} export interface RuleWithUseQwikMethodUsageOptions { level: RulePlainConfiguration; options?: UseQwikMethodUsageOptions; @@ -6503,9 +6491,7 @@ export type NoPlaywrightForceOptionOptions = {}; export type NoPlaywrightMissingAwaitOptions = {}; export type NoPlaywrightNetworkidleOptions = {}; export type NoPlaywrightPagePauseOptions = {}; -export type NoPlaywrightSkippedTestOptions = {}; export type NoPlaywrightUselessAwaitOptions = {}; -export type NoPlaywrightValidDescribeCallbackOptions = {}; export type NoPlaywrightWaitForNavigationOptions = {}; export type NoPlaywrightWaitForSelectorOptions = {}; export type NoPlaywrightWaitForTimeoutOptions = {}; @@ -6565,6 +6551,7 @@ export interface UseMaxParamsOptions { */ max?: number; } +export type UsePlaywrightValidDescribeCallbackOptions = {}; export type UseQwikMethodUsageOptions = {}; export type UseQwikValidLexicalScopeOptions = {}; export type UseRegexpExecOptions = {}; @@ -7357,9 +7344,8 @@ export type Category = | "lint/nursery/noPlaywrightMissingAwait" | "lint/nursery/noPlaywrightNetworkidle" | "lint/nursery/noPlaywrightPagePause" - | "lint/nursery/noPlaywrightSkippedTest" | "lint/nursery/noPlaywrightUselessAwait" - | "lint/nursery/noPlaywrightValidDescribeCallback" + | "lint/nursery/usePlaywrightValidDescribeCallback" | "lint/nursery/noPlaywrightWaitForNavigation" | "lint/nursery/noPlaywrightWaitForSelector" | "lint/nursery/noPlaywrightWaitForTimeout" diff --git a/packages/@biomejs/biome/configuration_schema.json b/packages/@biomejs/biome/configuration_schema.json index 8b9b006b8752..47547fd82ead 100644 --- a/packages/@biomejs/biome/configuration_schema.json +++ b/packages/@biomejs/biome/configuration_schema.json @@ -4005,16 +4005,6 @@ "type": "object", "additionalProperties": false }, - "NoPlaywrightSkippedTestConfiguration": { - "oneOf": [ - { "$ref": "#/$defs/RulePlainConfiguration" }, - { "$ref": "#/$defs/RuleWithNoPlaywrightSkippedTestOptions" } - ] - }, - "NoPlaywrightSkippedTestOptions": { - "type": "object", - "additionalProperties": false - }, "NoPlaywrightUselessAwaitConfiguration": { "oneOf": [ { "$ref": "#/$defs/RulePlainConfiguration" }, @@ -4025,16 +4015,6 @@ "type": "object", "additionalProperties": false }, - "NoPlaywrightValidDescribeCallbackConfiguration": { - "oneOf": [ - { "$ref": "#/$defs/RulePlainConfiguration" }, - { "$ref": "#/$defs/RuleWithNoPlaywrightValidDescribeCallbackOptions" } - ] - }, - "NoPlaywrightValidDescribeCallbackOptions": { - "type": "object", - "additionalProperties": false - }, "NoPlaywrightWaitForNavigationConfiguration": { "oneOf": [ { "$ref": "#/$defs/RulePlainConfiguration" }, @@ -5359,13 +5339,6 @@ { "type": "null" } ] }, - "noPlaywrightSkippedTest": { - "description": "Disallow usage of .skip annotation in Playwright tests.\nSee https://biomejs.dev/linter/rules/no-playwright-skipped-test", - "anyOf": [ - { "$ref": "#/$defs/NoPlaywrightSkippedTestConfiguration" }, - { "type": "null" } - ] - }, "noPlaywrightUselessAwait": { "description": "Disallow unnecessary await for Playwright methods that don't return promises.\nSee https://biomejs.dev/linter/rules/no-playwright-useless-await", "anyOf": [ @@ -5373,15 +5346,6 @@ { "type": "null" } ] }, - "noPlaywrightValidDescribeCallback": { - "description": "Enforce valid describe() callback.\nSee https://biomejs.dev/linter/rules/no-playwright-valid-describe-callback", - "anyOf": [ - { - "$ref": "#/$defs/NoPlaywrightValidDescribeCallbackConfiguration" - }, - { "type": "null" } - ] - }, "noPlaywrightWaitForNavigation": { "description": "Disallow using page.waitForNavigation().\nSee https://biomejs.dev/linter/rules/no-playwright-wait-for-navigation", "anyOf": [ @@ -5575,6 +5539,15 @@ { "type": "null" } ] }, + "usePlaywrightValidDescribeCallback": { + "description": "Enforce valid describe() callback.\nSee https://biomejs.dev/linter/rules/use-playwright-valid-describe-callback", + "anyOf": [ + { + "$ref": "#/$defs/UsePlaywrightValidDescribeCallbackConfiguration" + }, + { "type": "null" } + ] + }, "useQwikMethodUsage": { "description": "Disallow use* hooks outside of component$ or other use* hooks in Qwik applications.\nSee https://biomejs.dev/linter/rules/use-qwik-method-usage", "anyOf": [ @@ -7638,15 +7611,6 @@ "additionalProperties": false, "required": ["level"] }, - "RuleWithNoPlaywrightSkippedTestOptions": { - "type": "object", - "properties": { - "level": { "$ref": "#/$defs/RulePlainConfiguration" }, - "options": { "$ref": "#/$defs/NoPlaywrightSkippedTestOptions" } - }, - "additionalProperties": false, - "required": ["level"] - }, "RuleWithNoPlaywrightUselessAwaitOptions": { "type": "object", "properties": { @@ -7657,17 +7621,6 @@ "additionalProperties": false, "required": ["level"] }, - "RuleWithNoPlaywrightValidDescribeCallbackOptions": { - "type": "object", - "properties": { - "level": { "$ref": "#/$defs/RulePlainConfiguration" }, - "options": { - "$ref": "#/$defs/NoPlaywrightValidDescribeCallbackOptions" - } - }, - "additionalProperties": false, - "required": ["level"] - }, "RuleWithNoPlaywrightWaitForNavigationOptions": { "type": "object", "properties": { @@ -9539,6 +9492,17 @@ "additionalProperties": false, "required": ["level"] }, + "RuleWithUsePlaywrightValidDescribeCallbackOptions": { + "type": "object", + "properties": { + "level": { "$ref": "#/$defs/RulePlainConfiguration" }, + "options": { + "$ref": "#/$defs/UsePlaywrightValidDescribeCallbackOptions" + } + }, + "additionalProperties": false, + "required": ["level"] + }, "RuleWithUseQwikClasslistOptions": { "type": "object", "properties": { @@ -12457,6 +12421,16 @@ "type": "object", "additionalProperties": false }, + "UsePlaywrightValidDescribeCallbackConfiguration": { + "oneOf": [ + { "$ref": "#/$defs/RulePlainConfiguration" }, + { "$ref": "#/$defs/RuleWithUsePlaywrightValidDescribeCallbackOptions" } + ] + }, + "UsePlaywrightValidDescribeCallbackOptions": { + "type": "object", + "additionalProperties": false + }, "UseQwikClasslistConfiguration": { "oneOf": [ { "$ref": "#/$defs/RulePlainConfiguration" }, From 533ed8d56873c05747e7e10158e2feeb74bbcca2 Mon Sep 17 00:00:00 2001 From: Josh Delsman <206746594+josh-ninjatrader@users.noreply.github.com> Date: Sun, 30 Nov 2025 15:49:58 -0600 Subject: [PATCH 22/24] refactor(lint): simplify diagnostic messages for missing awaits and useless awaits - Removed redundant notes suggesting to add "await" or return promises in diagnostic messages for `noPlaywrightMissingAwait` and `noPlaywrightUselessAwait` rules. - Updated snapshots to reflect changes in diagnostic output for various test cases. --- .../nursery/no_playwright_missing_await.rs | 23 ++++++++----------- .../nursery/no_playwright_useless_await.rs | 3 --- .../invalid/expect-async-matcher.js.snap | 2 -- .../invalid/expect-poll-sync-matchers.js.snap | 8 +++---- .../invalid/expect-poll.js.snap | 2 +- .../invalid/module-level.js.snap | 2 -- .../invalid/nested-expects.js.snap | 6 ----- .../invalid/non-async-context.js.snap | 2 -- .../invalid/promise-all-not-awaited.js.snap | 4 ---- .../invalid/promise-combinators.js.snap | 12 ---------- .../invalid/test-step.js.snap | 2 +- .../invalid/expect-mixed-chains.js.snap | 10 -------- .../invalid/expect-sync.js.snap | 2 -- .../invalid/getByRole.js.snap | 2 -- .../invalid/locator.js.snap | 2 -- .../invalid/page-frame.js.snap | 6 ----- 16 files changed, 15 insertions(+), 73 deletions(-) diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_missing_await.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_missing_await.rs index bb501781af23..cca90ae63dac 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_playwright_missing_await.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_playwright_missing_await.rs @@ -138,18 +138,13 @@ impl Rule for NoPlaywrightMissingAwait { match state { MissingAwaitType::ExpectMatcher(matcher) => { let matcher_text = matcher.text(); - Some( - RuleDiagnostic::new( - rule_category!(), - node.range(), - markup! { - "Async matcher "{matcher_text}" must be awaited or returned." - }, - ) - .note(markup! { - "Add ""await"" before the expect call or return the promise." - }), - ) + Some(RuleDiagnostic::new( + rule_category!(), + node.range(), + markup! { + "Async matcher "{matcher_text}" must be awaited or returned." + }, + )) } MissingAwaitType::ExpectPoll => Some( RuleDiagnostic::new( @@ -160,7 +155,7 @@ impl Rule for NoPlaywrightMissingAwait { }, ) .note(markup! { - "The ""expect.poll"" method converts any synchronous expect to an asynchronous polling one. Add ""await"" before the call or return the promise." + "The ""expect.poll"" method converts any synchronous expect to an asynchronous polling one." }), ), MissingAwaitType::TestStep => Some( @@ -172,7 +167,7 @@ impl Rule for NoPlaywrightMissingAwait { }, ) .note(markup! { - "Test steps are asynchronous. Add ""await"" before ""test.step()"" or return the promise." + "Test steps are asynchronous." }), ), } diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_useless_await.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_useless_await.rs index b8519dfd47af..b573b200b523 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_playwright_useless_await.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_playwright_useless_await.rs @@ -180,9 +180,6 @@ impl Rule for NoPlaywrightUselessAwait { ) .note(markup! { "This method does not return a Promise." - }) - .note(markup! { - "Remove the ""await"" keyword." }), ) } diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/expect-async-matcher.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/expect-async-matcher.js.snap index 08133193f8f6..0a9e86fc4c4c 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/expect-async-matcher.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/expect-async-matcher.js.snap @@ -25,8 +25,6 @@ expect-async-matcher.js:3:5 lint/nursery/noPlaywrightMissingAwait FIXABLE ━ 4 │ }); 5 │ - i Add await before the expect call or return the promise. - i Unsafe fix: Add await before the call. 3 │ ····await·expect(page.locator('body')).toBeVisible(); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/expect-poll-sync-matchers.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/expect-poll-sync-matchers.js.snap index 0ec4d83b9a05..097fffee88e8 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/expect-poll-sync-matchers.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/expect-poll-sync-matchers.js.snap @@ -37,7 +37,7 @@ expect-poll-sync-matchers.js:4:5 lint/nursery/noPlaywrightMissingAwait FIXABLE 5 │ }); 6 │ - i The expect.poll method converts any synchronous expect to an asynchronous polling one. Add await before the call or return the promise. + i The expect.poll method converts any synchronous expect to an asynchronous polling one. i Unsafe fix: Add await before the call. @@ -57,7 +57,7 @@ expect-poll-sync-matchers.js:8:5 lint/nursery/noPlaywrightMissingAwait FIXABLE 9 │ }); 10 │ - i The expect.poll method converts any synchronous expect to an asynchronous polling one. Add await before the call or return the promise. + i The expect.poll method converts any synchronous expect to an asynchronous polling one. i Unsafe fix: Add await before the call. @@ -77,7 +77,7 @@ expect-poll-sync-matchers.js:12:5 lint/nursery/noPlaywrightMissingAwait FIXABLE 13 │ }); 14 │ - i The expect.poll method converts any synchronous expect to an asynchronous polling one. Add await before the call or return the promise. + i The expect.poll method converts any synchronous expect to an asynchronous polling one. i Unsafe fix: Add await before the call. @@ -97,7 +97,7 @@ expect-poll-sync-matchers.js:16:5 lint/nursery/noPlaywrightMissingAwait FIXABLE 17 │ }); 18 │ - i The expect.poll method converts any synchronous expect to an asynchronous polling one. Add await before the call or return the promise. + i The expect.poll method converts any synchronous expect to an asynchronous polling one. i Unsafe fix: Add await before the call. diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/expect-poll.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/expect-poll.js.snap index 10c9b46c19bb..8d6eff9bb706 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/expect-poll.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/expect-poll.js.snap @@ -25,7 +25,7 @@ expect-poll.js:3:5 lint/nursery/noPlaywrightMissingAwait FIXABLE ━━━━ 4 │ }); 5 │ - i The expect.poll method converts any synchronous expect to an asynchronous polling one. Add await before the call or return the promise. + i The expect.poll method converts any synchronous expect to an asynchronous polling one. i Unsafe fix: Add await before the call. diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/module-level.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/module-level.js.snap index 91e40901d63a..4bda4abd3154 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/module-level.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/module-level.js.snap @@ -24,8 +24,6 @@ module-level.js:3:1 lint/nursery/noPlaywrightMissingAwait FIXABLE ━━━━ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 4 │ - i Add await before the expect call or return the promise. - i Unsafe fix: Add await before the call. 3 │ await·expect(page.locator('body')).toBeVisible(); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/nested-expects.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/nested-expects.js.snap index cb9d8daeabbb..eb6fd22df28f 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/nested-expects.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/nested-expects.js.snap @@ -33,8 +33,6 @@ nested-expects.js:4:5 lint/nursery/noPlaywrightMissingAwait FIXABLE ━━━ 5 │ }); 6 │ - i Add await before the expect call or return the promise. - i Unsafe fix: Add await before the call. 4 │ ····await·expect(page).not.toBeVisible(); @@ -53,8 +51,6 @@ nested-expects.js:8:5 lint/nursery/noPlaywrightMissingAwait FIXABLE ━━━ 9 │ }); 10 │ - i Add await before the expect call or return the promise. - i Unsafe fix: Add await before the call. 8 │ ····await·expect.soft(page).not.toHaveText("foo"); @@ -73,8 +69,6 @@ nested-expects.js:12:5 lint/nursery/noPlaywrightMissingAwait FIXABLE ━━━ 13 │ }); 14 │ - i Add await before the expect call or return the promise. - i Unsafe fix: Add await before the call. 12 │ ····await·expect(page).not.not.toBeEnabled(); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/non-async-context.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/non-async-context.js.snap index 46f35f9b9e2d..5df51b3893d8 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/non-async-context.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/non-async-context.js.snap @@ -26,7 +26,5 @@ non-async-context.js:3:5 lint/nursery/noPlaywrightMissingAwait ━━━━━ 4 │ }); 5 │ - i Add await before the expect call or return the promise. - ``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/promise-all-not-awaited.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/promise-all-not-awaited.js.snap index aa7e693b5f9b..20be08522d8b 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/promise-all-not-awaited.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/promise-all-not-awaited.js.snap @@ -28,8 +28,6 @@ promise-all-not-awaited.js:4:9 lint/nursery/noPlaywrightMissingAwait FIXABLE 5 │ expect(page.locator('.two')).toBeVisible() 6 │ ]); - i Add await before the expect call or return the promise. - i Unsafe fix: Add await before the Promise combinator. 3 │ ····await·Promise.all([ @@ -49,8 +47,6 @@ promise-all-not-awaited.js:5:9 lint/nursery/noPlaywrightMissingAwait FIXABLE 6 │ ]); 7 │ }); - i Add await before the expect call or return the promise. - i Unsafe fix: Add await before the Promise combinator. 3 │ ····await·Promise.all([ diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/promise-combinators.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/promise-combinators.js.snap index 58b389121da0..c280b705d0ca 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/promise-combinators.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/promise-combinators.js.snap @@ -41,8 +41,6 @@ promise-combinators.js:4:9 lint/nursery/noPlaywrightMissingAwait FIXABLE ━ 5 │ expect(page).toHaveText('foo') 6 │ ]); - i Add await before the expect call or return the promise. - i Unsafe fix: Add await before the Promise combinator. 3 │ ····await·Promise.allSettled([ @@ -62,8 +60,6 @@ promise-combinators.js:5:9 lint/nursery/noPlaywrightMissingAwait FIXABLE ━ 6 │ ]); 7 │ }); - i Add await before the expect call or return the promise. - i Unsafe fix: Add await before the Promise combinator. 3 │ ····await·Promise.allSettled([ @@ -83,8 +79,6 @@ promise-combinators.js:11:9 lint/nursery/noPlaywrightMissingAwait FIXABLE ━ 12 │ expect(page).toHaveText('foo') 13 │ ]); - i Add await before the expect call or return the promise. - i Unsafe fix: Add await before the Promise combinator. 10 │ ····await·Promise.race([ @@ -104,8 +98,6 @@ promise-combinators.js:12:9 lint/nursery/noPlaywrightMissingAwait FIXABLE ━ 13 │ ]); 14 │ }); - i Add await before the expect call or return the promise. - i Unsafe fix: Add await before the Promise combinator. 10 │ ····await·Promise.race([ @@ -125,8 +117,6 @@ promise-combinators.js:18:9 lint/nursery/noPlaywrightMissingAwait FIXABLE ━ 19 │ expect(page).toHaveText('foo') 20 │ ]); - i Add await before the expect call or return the promise. - i Unsafe fix: Add await before the Promise combinator. 17 │ ····await·Promise.any([ @@ -146,8 +136,6 @@ promise-combinators.js:19:9 lint/nursery/noPlaywrightMissingAwait FIXABLE ━ 20 │ ]); 21 │ }); - i Add await before the expect call or return the promise. - i Unsafe fix: Add await before the Promise combinator. 17 │ ····await·Promise.any([ diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/test-step.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/test-step.js.snap index ab85de9bbe17..a2f99454ce55 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/test-step.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/test-step.js.snap @@ -30,7 +30,7 @@ test-step.js:3:5 lint/nursery/noPlaywrightMissingAwait FIXABLE ━━━━━ 6 │ }); 7 │ - i Test steps are asynchronous. Add await before test.step() or return the promise. + i Test steps are asynchronous. i Unsafe fix: Add await before the call. diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/expect-mixed-chains.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/expect-mixed-chains.js.snap index ea213f383094..7ae816eb0d2c 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/expect-mixed-chains.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/expect-mixed-chains.js.snap @@ -32,8 +32,6 @@ expect-mixed-chains.js:3:1 lint/nursery/noPlaywrightUselessAwait FIXABLE ━ i This method does not return a Promise. - i Remove the await keyword. - i Safe fix: Remove unnecessary await 3 │ await·expect(1).toBe(1); @@ -55,8 +53,6 @@ expect-mixed-chains.js:4:1 lint/nursery/noPlaywrightUselessAwait FIXABLE ━ i This method does not return a Promise. - i Remove the await keyword. - i Safe fix: Remove unnecessary await 4 │ await·expect(value).toEqual(expectedValue); @@ -78,8 +74,6 @@ expect-mixed-chains.js:5:1 lint/nursery/noPlaywrightUselessAwait FIXABLE ━ i This method does not return a Promise. - i Remove the await keyword. - i Safe fix: Remove unnecessary await 5 │ await·expect(str).toMatch(/pattern/); @@ -101,8 +95,6 @@ expect-mixed-chains.js:6:1 lint/nursery/noPlaywrightUselessAwait FIXABLE ━ i This method does not return a Promise. - i Remove the await keyword. - i Safe fix: Remove unnecessary await 6 │ await·expect(arr).toHaveLength(3); @@ -122,8 +114,6 @@ expect-mixed-chains.js:9:1 lint/nursery/noPlaywrightUselessAwait FIXABLE ━ i This method does not return a Promise. - i Remove the await keyword. - i Safe fix: Remove unnecessary await 9 │ await·expect.soft(value).toBe(123); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/expect-sync.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/expect-sync.js.snap index 2e0708f61991..1588bad5c1bc 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/expect-sync.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/expect-sync.js.snap @@ -23,8 +23,6 @@ expect-sync.js:2:1 lint/nursery/noPlaywrightUselessAwait FIXABLE ━━━━ i This method does not return a Promise. - i Remove the await keyword. - i Safe fix: Remove unnecessary await 2 │ await·expect(1).toBe(1); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/getByRole.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/getByRole.js.snap index 7ea9604f9276..91f0d9202b40 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/getByRole.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/getByRole.js.snap @@ -23,8 +23,6 @@ getByRole.js:2:1 lint/nursery/noPlaywrightUselessAwait FIXABLE ━━━━━ i This method does not return a Promise. - i Remove the await keyword. - i Safe fix: Remove unnecessary await 2 │ await·page.getByRole('button'); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/locator.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/locator.js.snap index d2818423df4b..21855971f839 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/locator.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/locator.js.snap @@ -23,8 +23,6 @@ locator.js:2:1 lint/nursery/noPlaywrightUselessAwait FIXABLE ━━━━━ i This method does not return a Promise. - i Remove the await keyword. - i Safe fix: Remove unnecessary await 2 │ await·page.locator('.my-element'); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/page-frame.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/page-frame.js.snap index ab8d2850ef73..063c6e19cd44 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/page-frame.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightUselessAwait/invalid/page-frame.js.snap @@ -26,8 +26,6 @@ page-frame.js:2:1 lint/nursery/noPlaywrightUselessAwait FIXABLE ━━━━ i This method does not return a Promise. - i Remove the await keyword. - i Safe fix: Remove unnecessary await 2 │ await·page.frame('iframe'); @@ -49,8 +47,6 @@ page-frame.js:3:1 lint/nursery/noPlaywrightUselessAwait FIXABLE ━━━━ i This method does not return a Promise. - i Remove the await keyword. - i Safe fix: Remove unnecessary await 3 │ await·page.frames(); @@ -71,8 +67,6 @@ page-frame.js:4:1 lint/nursery/noPlaywrightUselessAwait FIXABLE ━━━━ i This method does not return a Promise. - i Remove the await keyword. - i Safe fix: Remove unnecessary await 4 │ await·page.url(); From b6cdc5df4e929a24f10cd73d5b6555ebc867e1f6 Mon Sep 17 00:00:00 2001 From: Josh Delsman <206746594+josh-ninjatrader@users.noreply.github.com> Date: Sun, 30 Nov 2025 15:58:58 -0600 Subject: [PATCH 23/24] refactor(lint): update diagnostic examples for Playwright await rules - Modified examples in `no_playwright_missing_await` to use `page.getByRole('button')` instead of `page` directly for better clarity. - Adjusted the `use_playwright_valid_describe_callback` rule to check for non-empty parameter lists more clearly. - Updated test cases and snapshots to reflect changes in the usage of locators for better accuracy in diagnostics. --- .../nursery/no_playwright_missing_await.rs | 6 +-- .../use_playwright_valid_describe_callback.rs | 2 +- .../invalid/promise-combinators.js | 12 ++--- .../invalid/promise-combinators.js.snap | 48 +++++++++---------- .../valid/promise-combinators-valid.js | 16 +++---- .../valid/promise-combinators-valid.js.snap | 16 +++---- 6 files changed, 50 insertions(+), 50 deletions(-) diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_missing_await.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_missing_await.rs index cca90ae63dac..f5a3b1328a1b 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_playwright_missing_await.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_playwright_missing_await.rs @@ -25,7 +25,7 @@ declare_lint_rule! { /// /// ```js,expect_diagnostic /// test('example', async ({ page }) => { - /// expect(page).toBeVisible(); + /// expect(page.getByRole('button')).toBeVisible(); /// }); /// ``` /// @@ -39,7 +39,7 @@ declare_lint_rule! { /// /// ```js /// test('example', async ({ page }) => { - /// await expect(page).toBeVisible(); + /// await expect(page.getByRole('button')).toBeVisible(); /// }); /// ``` /// @@ -51,7 +51,7 @@ declare_lint_rule! { /// /// ```js /// test('example', async ({ page }) => { - /// return expect(page).toBeVisible(); + /// return expect(page.getByRole('button')).toBeVisible(); /// }); /// ``` /// diff --git a/crates/biome_js_analyze/src/lint/nursery/use_playwright_valid_describe_callback.rs b/crates/biome_js_analyze/src/lint/nursery/use_playwright_valid_describe_callback.rs index 8d711f65c1fe..ad0217f4e00e 100644 --- a/crates/biome_js_analyze/src/lint/nursery/use_playwright_valid_describe_callback.rs +++ b/crates/biome_js_analyze/src/lint/nursery/use_playwright_valid_describe_callback.rs @@ -127,7 +127,7 @@ impl Rule for UsePlaywrightValidDescribeCallback { let has_params = match params { biome_js_syntax::AnyJsArrowFunctionParameters::AnyJsBinding(_) => true, biome_js_syntax::AnyJsArrowFunctionParameters::JsParameters(p) => { - p.items().len() > 0 + !p.items().is_empty() } }; if has_params { diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/promise-combinators.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/promise-combinators.js index dbd57c6d2165..fccfaf03a0e5 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/promise-combinators.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/promise-combinators.js @@ -1,21 +1,21 @@ /* should generate diagnostics */ test('Promise.allSettled', async ({ page }) => { Promise.allSettled([ - expect(page).toBeVisible(), - expect(page).toHaveText('foo') + expect(page.locator('.foo')).toBeVisible(), + expect(page.locator('.bar')).toHaveText('foo') ]); }); test('Promise.race', async ({ page }) => { Promise.race([ - expect(page).toBeVisible(), - expect(page).toHaveText('foo') + expect(page.locator('.foo')).toBeVisible(), + expect(page.locator('.bar')).toHaveText('foo') ]); }); test('Promise.any', async ({ page }) => { Promise.any([ - expect(page).toBeVisible(), - expect(page).toHaveText('foo') + expect(page.locator('.foo')).toBeVisible(), + expect(page.locator('.bar')).toHaveText('foo') ]); }); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/promise-combinators.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/promise-combinators.js.snap index c280b705d0ca..48da99139584 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/promise-combinators.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/invalid/promise-combinators.js.snap @@ -7,22 +7,22 @@ expression: promise-combinators.js /* should generate diagnostics */ test('Promise.allSettled', async ({ page }) => { Promise.allSettled([ - expect(page).toBeVisible(), - expect(page).toHaveText('foo') + expect(page.locator('.foo')).toBeVisible(), + expect(page.locator('.bar')).toHaveText('foo') ]); }); test('Promise.race', async ({ page }) => { Promise.race([ - expect(page).toBeVisible(), - expect(page).toHaveText('foo') + expect(page.locator('.foo')).toBeVisible(), + expect(page.locator('.bar')).toHaveText('foo') ]); }); test('Promise.any', async ({ page }) => { Promise.any([ - expect(page).toBeVisible(), - expect(page).toHaveText('foo') + expect(page.locator('.foo')).toBeVisible(), + expect(page.locator('.bar')).toHaveText('foo') ]); }); @@ -36,9 +36,9 @@ promise-combinators.js:4:9 lint/nursery/noPlaywrightMissingAwait FIXABLE ━ 2 │ test('Promise.allSettled', async ({ page }) => { 3 │ Promise.allSettled([ - > 4 │ expect(page).toBeVisible(), - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^ - 5 │ expect(page).toHaveText('foo') + > 4 │ expect(page.locator('.foo')).toBeVisible(), + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 5 │ expect(page.locator('.bar')).toHaveText('foo') 6 │ ]); i Unsafe fix: Add await before the Promise combinator. @@ -54,9 +54,9 @@ promise-combinators.js:5:9 lint/nursery/noPlaywrightMissingAwait FIXABLE ━ i Async matcher toHaveText must be awaited or returned. 3 │ Promise.allSettled([ - 4 │ expect(page).toBeVisible(), - > 5 │ expect(page).toHaveText('foo') - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 4 │ expect(page.locator('.foo')).toBeVisible(), + > 5 │ expect(page.locator('.bar')).toHaveText('foo') + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 6 │ ]); 7 │ }); @@ -74,9 +74,9 @@ promise-combinators.js:11:9 lint/nursery/noPlaywrightMissingAwait FIXABLE ━ 9 │ test('Promise.race', async ({ page }) => { 10 │ Promise.race([ - > 11 │ expect(page).toBeVisible(), - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^ - 12 │ expect(page).toHaveText('foo') + > 11 │ expect(page.locator('.foo')).toBeVisible(), + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 12 │ expect(page.locator('.bar')).toHaveText('foo') 13 │ ]); i Unsafe fix: Add await before the Promise combinator. @@ -92,9 +92,9 @@ promise-combinators.js:12:9 lint/nursery/noPlaywrightMissingAwait FIXABLE ━ i Async matcher toHaveText must be awaited or returned. 10 │ Promise.race([ - 11 │ expect(page).toBeVisible(), - > 12 │ expect(page).toHaveText('foo') - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 11 │ expect(page.locator('.foo')).toBeVisible(), + > 12 │ expect(page.locator('.bar')).toHaveText('foo') + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 13 │ ]); 14 │ }); @@ -112,9 +112,9 @@ promise-combinators.js:18:9 lint/nursery/noPlaywrightMissingAwait FIXABLE ━ 16 │ test('Promise.any', async ({ page }) => { 17 │ Promise.any([ - > 18 │ expect(page).toBeVisible(), - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^ - 19 │ expect(page).toHaveText('foo') + > 18 │ expect(page.locator('.foo')).toBeVisible(), + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 19 │ expect(page.locator('.bar')).toHaveText('foo') 20 │ ]); i Unsafe fix: Add await before the Promise combinator. @@ -130,9 +130,9 @@ promise-combinators.js:19:9 lint/nursery/noPlaywrightMissingAwait FIXABLE ━ i Async matcher toHaveText must be awaited or returned. 17 │ Promise.any([ - 18 │ expect(page).toBeVisible(), - > 19 │ expect(page).toHaveText('foo') - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 18 │ expect(page.locator('.foo')).toBeVisible(), + > 19 │ expect(page.locator('.bar')).toHaveText('foo') + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 20 │ ]); 21 │ }); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/valid/promise-combinators-valid.js b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/valid/promise-combinators-valid.js index 908dfa9dd8bc..00e33cd135b8 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/valid/promise-combinators-valid.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/valid/promise-combinators-valid.js @@ -2,28 +2,28 @@ // Valid: awaited Promise combinators test('awaited Promise.all', async ({ page }) => { await Promise.all([ - expect(page).toBeVisible(), - expect(page).toHaveText('foo') + expect(page.locator('.foo')).toBeVisible(), + expect(page.locator('.bar')).toHaveText('foo') ]); }); test('awaited Promise.allSettled', async ({ page }) => { await Promise.allSettled([ - expect(page).toBeVisible(), - expect(page).toHaveText('foo') + expect(page.locator('.foo')).toBeVisible(), + expect(page.locator('.bar')).toHaveText('foo') ]); }); test('returned Promise.race', async ({ page }) => { return Promise.race([ - expect(page).toBeVisible(), - expect(page).toHaveText('foo') + expect(page.locator('.foo')).toBeVisible(), + expect(page.locator('.bar')).toHaveText('foo') ]); }); test('returned Promise.any', async ({ page }) => { return Promise.any([ - expect(page).toBeVisible(), - expect(page).toHaveText('foo') + expect(page.locator('.foo')).toBeVisible(), + expect(page.locator('.bar')).toHaveText('foo') ]); }); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/valid/promise-combinators-valid.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/valid/promise-combinators-valid.js.snap index 20ff61cf8b96..c220a08254d5 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/valid/promise-combinators-valid.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightMissingAwait/valid/promise-combinators-valid.js.snap @@ -8,29 +8,29 @@ expression: promise-combinators-valid.js // Valid: awaited Promise combinators test('awaited Promise.all', async ({ page }) => { await Promise.all([ - expect(page).toBeVisible(), - expect(page).toHaveText('foo') + expect(page.locator('.foo')).toBeVisible(), + expect(page.locator('.bar')).toHaveText('foo') ]); }); test('awaited Promise.allSettled', async ({ page }) => { await Promise.allSettled([ - expect(page).toBeVisible(), - expect(page).toHaveText('foo') + expect(page.locator('.foo')).toBeVisible(), + expect(page.locator('.bar')).toHaveText('foo') ]); }); test('returned Promise.race', async ({ page }) => { return Promise.race([ - expect(page).toBeVisible(), - expect(page).toHaveText('foo') + expect(page.locator('.foo')).toBeVisible(), + expect(page.locator('.bar')).toHaveText('foo') ]); }); test('returned Promise.any', async ({ page }) => { return Promise.any([ - expect(page).toBeVisible(), - expect(page).toHaveText('foo') + expect(page.locator('.foo')).toBeVisible(), + expect(page.locator('.bar')).toHaveText('foo') ]); }); From ae23713f4e90d43fb819f36b9eaa72b9c123306a Mon Sep 17 00:00:00 2001 From: Josh Delsman <206746594+josh-ninjatrader@users.noreply.github.com> Date: Sun, 30 Nov 2025 16:12:05 -0600 Subject: [PATCH 24/24] refactor(lint): ensure dynamic caller is supported --- .../nursery/no_playwright_element_handle.rs | 19 ++++++++++++++----- .../src/lint/nursery/no_playwright_eval.rs | 19 ++++++++++++++----- .../invalid/frame.js.snap | 4 ++-- 3 files changed, 30 insertions(+), 12 deletions(-) diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_element_handle.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_element_handle.rs index f5d099a6d273..bd818c518030 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_playwright_element_handle.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_playwright_element_handle.rs @@ -54,9 +54,14 @@ declare_lint_rule! { } } +pub struct ElementHandleCall { + pub receiver: TokenText, + pub method: TokenText, +} + impl Rule for NoPlaywrightElementHandle { type Query = Ast; - type State = TokenText; + type State = ElementHandleCall; type Signals = Option; type Options = (); @@ -99,7 +104,10 @@ impl Rule for NoPlaywrightElementHandle { || object_text.ends_with("Page") || object_text.ends_with("Frame") { - Some(member_str) + Some(ElementHandleCall { + receiver: object_text, + method: member_str, + }) } else { None } @@ -107,7 +115,8 @@ impl Rule for NoPlaywrightElementHandle { fn diagnostic(ctx: &RuleContext, state: &Self::State) -> Option { let node = ctx.query(); - let state_text = state.text(); + let receiver = state.receiver.text(); + let method = state.method.text(); Some( RuleDiagnostic::new( rule_category!(), @@ -117,10 +126,10 @@ impl Rule for NoPlaywrightElementHandle { }, ) .note(markup! { - "Element handles like ""page."{{state_text}}"()"" are discouraged." + "Element handles like "{receiver}"."{{method}}"()"" are discouraged." }) .note(markup! { - "Use ""page.locator()"" or other locator methods like ""getByRole()"" instead." + "Use "{receiver}".locator()"" or other locator methods like ""getByRole()"" instead." }) .note(markup! { "Locators auto-wait and are more reliable than element handles." diff --git a/crates/biome_js_analyze/src/lint/nursery/no_playwright_eval.rs b/crates/biome_js_analyze/src/lint/nursery/no_playwright_eval.rs index 7ed416e2a469..d35c13d1140d 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_playwright_eval.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_playwright_eval.rs @@ -43,9 +43,14 @@ declare_lint_rule! { } } +pub struct EvalMethodCall { + pub receiver: TokenText, + pub method: TokenText, +} + impl Rule for NoPlaywrightEval { type Query = Ast; - type State = TokenText; + type State = EvalMethodCall; type Signals = Option; type Options = (); @@ -87,7 +92,10 @@ impl Rule for NoPlaywrightEval { || object_text.ends_with("Page") || object_text.ends_with("Frame") { - Some(member_str) + Some(EvalMethodCall { + receiver: object_text, + method: member_str, + }) } else { None } @@ -95,15 +103,16 @@ impl Rule for NoPlaywrightEval { fn diagnostic(ctx: &RuleContext, state: &Self::State) -> Option { let node = ctx.query(); - let state_text = state.text(); - let is_eval = state_text == "$eval"; + let receiver = state.receiver.text(); + let method = state.method.text(); + let is_eval = method == "$eval"; Some( RuleDiagnostic::new( rule_category!(), node.range(), markup! { - "Unexpected use of ""page."{{state_text}}"()""." + "Unexpected use of "{receiver}"."{{method}}"()""." }, ) .note(markup! { diff --git a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/frame.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/frame.js.snap index fcb0bbdff878..8710984cb515 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/frame.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noPlaywrightElementHandle/invalid/frame.js.snap @@ -21,9 +21,9 @@ frame.js:2:23 lint/nursery/noPlaywrightElementHandle ━━━━━━━━━ │ ^^^^^^^^^^^^^^^^^^^ 3 │ - i Element handles like page.$() are discouraged. + i Element handles like frame.$() are discouraged. - i Use page.locator() or other locator methods like getByRole() instead. + i Use frame.locator() or other locator methods like getByRole() instead. i Locators auto-wait and are more reliable than element handles.