diff --git a/.changeset/add-use-baseline-css-rule.md b/.changeset/add-use-baseline-css-rule.md new file mode 100644 index 000000000000..0c1f42d1b01e --- /dev/null +++ b/.changeset/add-use-baseline-css-rule.md @@ -0,0 +1,11 @@ +--- +"@biomejs/biome": patch +--- + +Added new nursery lint rule `useBaseline` for CSS. The rule reports when CSS properties, property values, at-rules, media conditions, functions, or pseudo-selectors are not part of the configured [Baseline](https://developer.mozilla.org/en-US/docs/Glossary/Baseline/Compatibility) tier. + +For example, *at the time of writing*, the rule will trigger for the use of `accent-color` because it has limited availability: + +```css +a { accent-color: bar; } +``` diff --git a/.changeset/add-use-imports-first.md b/.changeset/add-use-imports-first.md new file mode 100644 index 000000000000..49640af4cd13 --- /dev/null +++ b/.changeset/add-use-imports-first.md @@ -0,0 +1,17 @@ +--- +"@biomejs/biome": patch +--- + +Added the nursery rule [`useImportsFirst`](https://biomejs.dev/linter/rules/use-imports-first/) that enforces all import statements appear before any non-import statements in a module. Inspired by the eslint-plugin-import [`import/first`](https://github.com/import-js/eslint-plugin-import/blob/HEAD/docs/rules/first.md) rule. + +```js +// Invalid +import { foo } from "foo"; +const bar = 1; +import { baz } from "baz"; // ← flagged + +// Valid +import { foo } from "foo"; +import { baz } from "baz"; +const bar = 1; +``` diff --git a/.changeset/curly-games-follow.md b/.changeset/curly-games-follow.md new file mode 100644 index 000000000000..f3870f7bbe24 --- /dev/null +++ b/.changeset/curly-games-follow.md @@ -0,0 +1,5 @@ +--- +"@biomejs/biome": patch +--- + +Fixed [#9432](https://github.com/biomejs/biome/issues/9432): Values referenced as a JSX element in Astro/Vue/Svelte templates are now correctly detected; `noUnusedImports` and `useImportType` rules no longer reports these values as false positives. diff --git a/.changeset/fix-css-unicode-escape-in-string.md b/.changeset/fix-css-unicode-escape-in-string.md new file mode 100644 index 000000000000..4d67d13b49e3 --- /dev/null +++ b/.changeset/fix-css-unicode-escape-in-string.md @@ -0,0 +1,5 @@ +--- +"@biomejs/biome": patch +--- + +Fixed [#9385](https://github.com/biomejs/biome/issues/9385): [`noUselessEscapeInString`](https://biomejs.dev/linter/rules/no-useless-escape-in-string/) no longer incorrectly flags valid CSS hex escapes (e.g. `\e7bb`) as useless. The rule now recognizes all hex digits (`0-9`, `a-f`, `A-F`) as valid escape characters in CSS strings. diff --git a/.changeset/fix-embedded-template-crash.md b/.changeset/fix-embedded-template-crash.md new file mode 100644 index 000000000000..e261607eacb1 --- /dev/null +++ b/.changeset/fix-embedded-template-crash.md @@ -0,0 +1,5 @@ +--- +"@biomejs/biome": patch +--- + +Fixed [#9131](https://github.com/biomejs/biome/issues/9131), [#9112](https://github.com/biomejs/biome/issues/9112), [#9166](https://github.com/biomejs/biome/issues/9166): the formatter no longer crashes or produces corrupt output when a JS file with `experimentalEmbeddedSnippetsEnabled` contains non-embedded template literals alongside embedded ones (e.g. `console.log(\`test\`)` next to `graphql(\`...\`)`). diff --git a/.changeset/fix-tailwind-utility-slash.md b/.changeset/fix-tailwind-utility-slash.md new file mode 100644 index 000000000000..4723d6a91747 --- /dev/null +++ b/.changeset/fix-tailwind-utility-slash.md @@ -0,0 +1,5 @@ +--- +"@biomejs/biome": patch +--- + +Fixed [#8897](https://github.com/biomejs/biome/issues/8897): Biome now parses `@utility` names containing `/` when Tailwind directives are enabled. diff --git a/.changeset/fix-use-semantic-elements-base-concepts.md b/.changeset/fix-use-semantic-elements-base-concepts.md new file mode 100644 index 000000000000..b840878c8735 --- /dev/null +++ b/.changeset/fix-use-semantic-elements-base-concepts.md @@ -0,0 +1,5 @@ +--- +"@biomejs/biome": patch +--- + +Fixed [#9245](https://github.com/biomejs/biome/issues/9245): the [`useSemanticElements`](https://biomejs.dev/linter/rules/use-semantic-elements/) rule no longer suggests `` for `role="status"` and `role="alert"`. The `` element is only a `relatedConcept` of these roles, not a direct semantic equivalent. These roles are now excluded from suggestions, aligning with the intended behavior of the upstream `prefer-tag-over-role` rule. diff --git a/.changeset/lovely-clouds-change.md b/.changeset/lovely-clouds-change.md new file mode 100644 index 000000000000..84893a80e446 --- /dev/null +++ b/.changeset/lovely-clouds-change.md @@ -0,0 +1,5 @@ +--- +"@biomejs/biome": patch +--- + +Fixed [#9433](https://github.com/biomejs/biome/issues/9433): [`noBlankTarget`](https://biomejs.dev/linter/rules/no-blank-target/) now correctly handles dynamic href attributes, such as ``. diff --git a/.changeset/resolve-record-type.md b/.changeset/resolve-record-type.md new file mode 100644 index 000000000000..252c1aea15a8 --- /dev/null +++ b/.changeset/resolve-record-type.md @@ -0,0 +1,5 @@ +--- +"@biomejs/biome": patch +--- + +Fixed [#6606](https://github.com/biomejs/biome/issues/6606): The type inference engine now resolves `Record` types, synthesizing them as object types with index signatures. This improves accuracy for type-aware lint rules such as `noFloatingPromises`, `noMisusedPromises`, `useAwaitThenable`, and `useArraySortCompare` when operating on Record-typed values. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 97cb7b9c9710..75c927a0b86e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -346,7 +346,6 @@ jobs: path: | ./packages/@biomejs/js-api/dist if-no-files-found: error - # Publishing jobs publish-cli: @@ -357,6 +356,8 @@ jobs: - build-binaries-gnu - build-wasm if: needs.build-binaries.outputs.version + outputs: + version: ${{ needs.build-binaries.outputs.version }} environment: npm-publish permissions: contents: write @@ -416,20 +417,62 @@ jobs: fail_on_unmatched_files: true generate_release_notes: true - - name: Codegen website schema + - name: Generate docker images uses: peter-evans/repository-dispatch@28959ce8df70de7be546dd1250a005dd32156697 # v4.0.1 with: token: ${{ secrets.BIOME_REPOSITORY_DISPATCH }} - repository: ${{ env.BIOME_WEBSITE_REPO }} + repository: ${{ env.BIOME_DOCKER_REPO }} event-type: ${{ env.BIOME_RELEASE_CLI_EVENT }} - client-payload: | - { "sha": "${{ github.sha }}", "tag": "@biomejs/biome@${{ needs.build-binaries.outputs.version }}", "version": "${{ needs.build-binaries.outputs.version }}" } - - name: Generate docker images + + + # Publish wasm-web on pkg-pr-new and dispatch event to website repo. + # This job is intentionally not required by any other job so it cannot block the release. + publish-wasm-web: + name: Publish wasm-web preview + runs-on: depot-ubuntu-24.04-arm-small + needs: + - build-wasm + - publish-cli + # Run after the release is published; never block the release pipeline. + if: always() && needs.build-wasm.result == 'success' && needs.publish-cli.result == 'success' + continue-on-error: true + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Download wasm-web artifact + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 + with: + name: wasm-web-artefact + path: packages/@biomejs + + - name: Install pnpm + run: npm i -g --force corepack && corepack enable + + - name: Setup node + uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + with: + node-version: 24.13.1 + cache: pnpm + + - name: Update package.json + working-directory: packages/@biomejs/wasm-web + run: | + npm pkg set name='@biomejs/wasm-web' + npm pkg set version='0.0.0-rev.${{ github.sha }}' + + - name: Publish on pkg-pr-new + working-directory: packages/@biomejs/wasm-web + run: pnpx pkg-pr-new publish + + - name: Dispatch event to website repo uses: peter-evans/repository-dispatch@28959ce8df70de7be546dd1250a005dd32156697 # v4.0.1 with: token: ${{ secrets.BIOME_REPOSITORY_DISPATCH }} - repository: ${{ env.BIOME_DOCKER_REPO }} + repository: ${{ env.BIOME_WEBSITE_REPO }} event-type: ${{ env.BIOME_RELEASE_CLI_EVENT }} + client-payload: | + { "sha": "${{ github.sha }}", "tag": "@biomejs/biome@${{ needs.publish-cli.outputs.version }}", "version": "${{ needs.publish-cli.outputs.version }}" } publish-js-api: name: Publish JS API diff --git a/Cargo.lock b/Cargo.lock index 2e11f43950d1..afa837df5006 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -344,6 +344,7 @@ dependencies = [ "filetime", "insta", "mimalloc", + "phf 0.13.1", "regex", "rustc-hash 2.1.1", "schemars", diff --git a/crates/biome_analyze/src/rule.rs b/crates/biome_analyze/src/rule.rs index eb45fcf475af..38e51b98d3da 100644 --- a/crates/biome_analyze/src/rule.rs +++ b/crates/biome_analyze/src/rule.rs @@ -186,6 +186,8 @@ pub enum RuleSource<'a> { EslintMarkdown(&'a str), /// Rules from [Eslint Plugin Yml](https://ota-meshi.github.io/eslint-plugin-yml/) EslintYml(&'a str), + /// Rules from [Eslint CSS](https://github.com/eslint/css) + EslintCss(&'a str), /// Action for https://github.com/keithamus/sort-package-json SortPackageJson, } @@ -241,6 +243,7 @@ impl<'a> std::fmt::Display for RuleSource<'a> { Self::EslintJson(_) => write!(f, "@eslint/json"), Self::EslintMarkdown(_) => write!(f, "@eslint/markdown"), Self::EslintYml(_) => write!(f, "eslint-plugin-yml"), + Self::EslintCss(_) => write!(f, "@eslint/css"), Self::SortPackageJson => write!(f, "sort-package-json"), } } @@ -306,8 +309,8 @@ impl<'a> RuleSource<'a> { Self::EslintJson(_) => 41, Self::EslintMarkdown(_) => 42, Self::EslintYml(_) => 43, - Self::SortPackageJson => 44, - + Self::EslintCss(_) => 44, + Self::SortPackageJson => 45, } } @@ -367,6 +370,7 @@ impl<'a> RuleSource<'a> { | Self::Stylelint(rule_name) | Self::EslintTurbo(rule_name) | Self::HtmlEslint(rule_name) + | Self::EslintCss(rule_name) | Self::EslintPlaywright(rule_name) | Self::EslintJson(rule_name) | Self::EslintMarkdown(rule_name) @@ -422,6 +426,7 @@ impl<'a> RuleSource<'a> { Self::EslintJson(_) => "json", Self::EslintMarkdown(_) => "markdown", Self::EslintYml(_) => "yml", + Self::EslintCss(_) => "css", } } @@ -479,6 +484,7 @@ impl<'a> RuleSource<'a> { Self::EslintJson(rule_name) => format!("https://github.com/eslint/json/blob/main/docs/rules/{rule_name}.md"), Self::EslintMarkdown(rule_name) => format!("https://github.com/eslint/markdown/blob/main/docs/rules/{rule_name}.md"), Self::EslintYml(rule_name) => format!("https://ota-meshi.github.io/eslint-plugin-yml/rules/{rule_name}.html"), + Self::EslintCss(rule_name) => format!("https://github.com/eslint/css/blob/main/docs/rules/{rule_name}.md"), Self::SortPackageJson => "https://github.com/keithamus/sort-package-json".to_string(), } } 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 faf8a615d3d7..d050614940e6 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 @@ -1145,6 +1145,22 @@ pub(crate) fn migrate_eslint_any_rule( .get_or_insert(Default::default()); rule.set_level(rule.level().max(rule_severity.into())); } + "css/use-baseline" => { + if !options.include_inspired { + results.add(eslint_name, eslint_to_biome::RuleMigrationResult::Inspired); + return false; + } + 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() + .use_baseline + .get_or_insert(Default::default()); + rule.set_level(rule.level().max(rule_severity.into())); + } "curly" => { let group = rules.style.get_or_insert_with(Default::default); let rule = group @@ -1293,6 +1309,18 @@ pub(crate) fn migrate_eslint_any_rule( .get_or_insert(Default::default()); rule.set_level(rule.level().max(rule_severity.into())); } + "import/first" => { + 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() + .use_imports_first + .get_or_insert(Default::default()); + rule.set_level(rule.level().max(rule_severity.into())); + } "import/named" => { if !options.include_inspired { results.add(eslint_name, eslint_to_biome::RuleMigrationResult::Inspired); diff --git a/crates/biome_cli/tests/cases/handle_astro_files.rs b/crates/biome_cli/tests/cases/handle_astro_files.rs index 0472c2f21824..ca4887a8b51f 100644 --- a/crates/biome_cli/tests/cases/handle_astro_files.rs +++ b/crates/biome_cli/tests/cases/handle_astro_files.rs @@ -1054,6 +1054,54 @@ let { )); } +#[test] +fn use_import_type_not_triggered_for_components_in_template() { + let fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + + fs.insert( + "biome.json".into(), + r#"{ "html": { "linter": {"enabled": true}, "experimentalFullSupportEnabled": true } }"# + .as_bytes(), + ); + + let file = Utf8Path::new("file.astro"); + fs.insert( + file.into(), + r#"--- +import type { ComponentProps } from "astro/types"; + +import Badge from "@/components/mdx-components/Badge.astro"; + +interface Props { + badge?: ComponentProps; +} + +const { badge } = Astro.props; +--- + +{badge && } +"# + .as_bytes(), + ); + + let (fs, result) = run_cli( + fs, + &mut console, + Args::from(["lint", "--only=useImportType", file.as_str()].as_slice()), + ); + + assert!(result.is_ok(), "run_cli returned {result:?}"); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "use_import_type_not_triggered_for_components_in_template", + fs, + console, + result, + )); +} + #[test] fn no_useless_lone_block_statements_is_not_triggered() { let fs = MemoryFileSystem::default(); diff --git a/crates/biome_cli/tests/snapshots/main_cases_handle_astro_files/use_import_type_not_triggered_for_components_in_template.snap b/crates/biome_cli/tests/snapshots/main_cases_handle_astro_files/use_import_type_not_triggered_for_components_in_template.snap new file mode 100644 index 000000000000..731dee9fc4ee --- /dev/null +++ b/crates/biome_cli/tests/snapshots/main_cases_handle_astro_files/use_import_type_not_triggered_for_components_in_template.snap @@ -0,0 +1,39 @@ +--- +source: crates/biome_cli/tests/snap_test.rs +expression: redactor(content) +--- +## `biome.json` + +```json +{ + "html": { + "linter": { "enabled": true }, + "experimentalFullSupportEnabled": true + } +} +``` + +## `file.astro` + +```astro +--- +import type { ComponentProps } from "astro/types"; + +import Badge from "@/components/mdx-components/Badge.astro"; + +interface Props { + badge?: ComponentProps; +} + +const { badge } = Astro.props; +--- + +{badge && } + +``` + +# Emitted Messages + +```block +Checked 1 file in