Skip to content

chore: biome v2.4#8275

Merged
ematipico merged 102 commits intomainfrom
next
Feb 14, 2026
Merged

chore: biome v2.4#8275
ematipico merged 102 commits intomainfrom
next

Conversation

@dyc3
Copy link
Contributor

@dyc3 dyc3 commented Nov 26, 2025

Summary

Caution

  • Do not rebase this branch. To bring it up to date, do a normal merge main -> next.
  • When merging this, do not squash and merge it.

Test Plan

Docs

siketyan and others added 10 commits November 5, 2025 22:18
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Naoki Ikeguchi <me@s6n.jp>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Emanuele Stoppa <my.burning@gmail.com>
Co-authored-by: Emanuele Stoppa <my.burning@gmail.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

Co-authored-by: arendjr <533294+arendjr@users.noreply.github.com>
Co-authored-by: Hamir Mahal <hirehamir@gmail.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Carson McManus <dyc3@users.noreply.github.com>
@changeset-bot
Copy link

changeset-bot bot commented Nov 26, 2025

🦋 Changeset detected

Latest commit: 4804acf

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 14 packages
Name Type
@biomejs/biome Minor
@biomejs/cli-win32-x64 Minor
@biomejs/cli-win32-arm64 Minor
@biomejs/cli-darwin-x64 Minor
@biomejs/cli-darwin-arm64 Minor
@biomejs/cli-linux-x64 Minor
@biomejs/cli-linux-arm64 Minor
@biomejs/cli-linux-x64-musl Minor
@biomejs/cli-linux-arm64-musl Minor
@biomejs/wasm-web Minor
@biomejs/wasm-bundler Minor
@biomejs/wasm-nodejs Minor
@biomejs/backend-jsonrpc Patch
@biomejs/js-api Major

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions github-actions bot added A-CLI Area: CLI A-Project Area: project A-Linter Area: linter A-Parser Area: parser A-Formatter Area: formatter A-Tooling Area: internal tools A-LSP Area: language server protocol L-JavaScript Language: JavaScript and super languages A-Diagnostic Area: diagnostocis L-HTML Language: HTML and super languages labels Nov 26, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 26, 2025

Walkthrough

This PR introduces multiple enhancements across Biome: new HTML accessibility lint rules (noAccessKey, useButtonType, useHtmlLang), a TypeScript interface member sorting assist, JSX factory configuration support from tsconfig.json, improvements to the useHookAtTopLevel rule for detecting module-level hook usage, custom extension mappings for useImportExtensions, configurable file watcher options, standardised logging configuration via LogOptions, LSP project-scanning progress reporting with UUID tokens, and a complete new YAML formatter crate. Additionally, KeyValuePair's API shifts from tuple-like construction to an explicit new() constructor with optional padding configuration.

Possibly related PRs

Suggested labels

A-Core

🚥 Pre-merge checks | ✅ 1 | ❌ 1
❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'chore: biome v2.4' is concise and accurately reflects a version bump release, though it differs from the PR's stated objective of merging next into main. Clarify whether this PR is primarily a version release (v2.4) or a branch merge. If it's a release, the title is apt; if it's a merge PR, consider updating the title to better reflect the merge intent (e.g., 'chore: merge next into main for v2.4').
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The PR description relates to the changeset by providing explicit merge instructions and referencing the contribution guidelines, though it lacks specific details about Test Plan and Docs.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch next

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (12)
crates/biome_yaml_formatter/src/verbatim.rs (1)

97-106: Consider using .find() instead of .take_while().next().

The current chain achieves the same result as .find(), but the latter expresses intent more directly.

         let start_source = self
             .node
             .first_leading_trivia()
             .into_iter()
             .flat_map(|trivia| trivia.pieces())
             .filter(|trivia| trivia.is_skipped())
             .map(|trivia| source_range(f, trivia.text_range()).start())
-            .take_while(|start| *start < trimmed_source_range.start())
-            .next()
+            .find(|start| *start < trimmed_source_range.start())
             .unwrap_or_else(|| trimmed_source_range.start());
.changeset/witty-ears-hammer.md (1)

1-22: Good, rule-focused changeset with examples

Nice user-facing summary: past tense for the addition, present tense for behaviour, plus clear invalid/valid examples. If there’s a public rule doc URL handy, you could optionally link it here for quicker discoverability, but it’s not required.

crates/biome_cli/tests/snap_test.rs (1)

271-303: Be careful: trim() here may eat newlines/indentation

Using before.trim() normalises all surrounding whitespace, including leading newlines/indentation, then forces a single space before <BIOME_DIR>. That can join the placeholder onto the previous line rather than keeping it on its own/indented line.

If the goal is just to avoid duplicated spaces before the placeholder while preserving line breaks and leading indentation, consider something like:

-        result.push_str(before.trim());
-        result.push(' ');
+        result.push_str(before.trim_end());
+        if !before.ends_with(char::is_whitespace) {
+            result.push(' ');
+        }

Or a similar trim_end‑based approach, depending on exactly how you want the snapshots to look.

crates/biome_flags/src/lib.rs (1)

104-223: Consider extracting the repetitive Display logic.

The match-based formatting for each env variable is quite repetitive. A helper method on BiomeEnvVariable could reduce boilerplate:

impl BiomeEnvVariable {
    fn fmt_with_padding(&self, fmt: &mut Formatter, padding: usize) -> std::io::Result<()> {
        match self.value() {
            None => KeyValuePair::new(self.name, markup! { <Dim>"unset"</Dim> })
                .with_padding(padding)
                .fmt(fmt),
            Some(value) => KeyValuePair::new(self.name, markup! {{DebugDisplay(value)}})
                .with_padding(padding)
                .fmt(fmt),
        }
    }
}

Then the Display impl becomes a series of self.biome_*.fmt_with_padding(fmt, padding)?; calls.

.changeset/six-plants-lie.md (1)

9-17: Minor: Trailing comma in example.

The JSON example has a trailing comma on line 13 that makes it invalid JSON. Consider removing it for clarity, though users will understand the intent.

Apply this diff:

         "extensionMappings": {
-            "ts": "js",
+            "ts": "js"
         }
.changeset/every-beers-sleep.md (1)

1-24: Minor wording polish for watcher option description

The description is clear, but you could make it read a touch more naturally with tiny edits like:

- The option accepts the current values:
+ The option accepts the following values:
@@
-- `none`: it doesn't enable the watcher. When the watcher is disabled, changes to files aren't recorded anymore by Biome. This might have
-  repercussions on some lint rules that might rely on updated types or updated paths.
+- `none`: it doesn't enable the watcher. When the watcher is disabled, changes to files are no longer recorded by Biome. This might have
+  repercussions for some lint rules that rely on updated types or paths.

Purely optional, the existing text is already understandable.

crates/biome_js_analyze/src/lint/correctness/use_import_extensions.rs (1)

80-105: extensionMappings behaviour matches docs; consider clarifying precedence with forceJsExtensions

The new extensionMappings option and its use in get_extensionless_import look sound: when a mapping exists for the resolved extension, that mapped extension is suggested; otherwise you fall back to the resolved extension, preserving the existing .d.ts and sub‑extension handling.

Note that forceJsExtensions is evaluated first, so if both forceJsExtensions: true and extensionMappings are set, forceJsExtensions always wins and mappings are effectively ignored. If you intend mappings to be the more specific override, it might be worth either swapping the order or explicitly documenting that forceJsExtensions takes precedence.

Also applies to: 286-295

crates/biome_js_analyze/tests/specs/correctness/useHookAtTopLevel/hookLocations.js (1)

1-48: Nice coverage of hook locations across contexts

This fixture exercises a good spread of cases for useHookAtTopLevel (module scope, non‑components, components/hooks by naming convention, object/class methods, and various test callback shapes including renderHook). The mixed valid/invalid examples in a single file are fine here; just be aware it diverges from the valid/invalid file‑prefix convention if you ever rely on that mechanically.

crates/biome_cli/src/logging.rs (1)

1-3: Avoid drifting defaults between bpaf fallbacks and LogOptions::default

LogOptions nicely centralises logging configuration, but the default values are now encoded twice: once via the #[bpaf(fallback(...))] attributes and again in the manual Default impl. If one side changes (e.g. log path or prefix) it’s easy for the two to diverge.

Consider factoring the defaults into associated constants or a fn default_log_options() -> LogOptions used both by bpaf (via fallback) and by the Default impl, so there’s a single source of truth.

Also applies to: 15-83

crates/biome_js_analyze/src/lint/style/use_import_type.rs (1)

1-4: JSX factory detection in useImportType correctly avoids flagging runtime-only imports

The new is_jsx_factory_binding helper and its use at the various import sites mean:

  • Under JsxRuntime::ReactClassic, default/namespace bindings that are either:

    • the standard React global, or
    • match configured jsxFactory/jsxFragmentFactory

    are treated as value imports and exempt from the “only used as a type” checks.

That should eliminate spurious import type suggestions for React or custom JSX factory imports that are only referenced via JSX. The helper being gated on ReactClassic also matches how custom factories are used today; if you later support custom factories under other runtimes, this will be the natural place to extend.

Also applies to: 195-201, 251-268, 277-282, 343-349, 1100-1129

crates/biome_html_analyze/src/lint/a11y/use_button_type.rs (1)

111-115: Minor inconsistency with sibling rule helper.

The is_button_element helper uses name.text() whilst is_html_element in use_html_lang.rs uses token_text. Both operate on AnyHtmlElement::name(). Consider aligning the approach for consistency across the a11y rules.

 fn is_button_element(element: &AnyHtmlElement) -> bool {
     element
         .name()
-        .is_some_and(|name| name.text().eq_ignore_ascii_case("button"))
+        .is_some_and(|token_text| token_text.eq_ignore_ascii_case("button"))
 }
crates/biome_cli/src/service/unix.rs (1)

56-84: LGTM! Clean consolidation of daemon spawn arguments.

The refactor from individual parameters to structured WatcherOptions and LogOptions improves maintainability. The command-line argument construction properly forwards all relevant configuration.

One minor observation: the parameter is named watcher_configuration here (line 58) but watcher_options in the Windows counterpart (crates/biome_cli/src/service/windows.rs, line 190). Consider aligning the naming for consistency across platforms.

 fn spawn_daemon(
     stop_on_disconnect: bool,
-    watcher_configuration: WatcherOptions,
+    watcher_options: WatcherOptions,
     log_options: LogOptions,
 ) -> io::Result<Child> {

"@biomejs/biome": minor
---

Added support for `jsxFactory` and `jsxFragmentFactory`.Biome now respects `jsxFactory` and `jsxFragmentFactory` settings from `tsconfig.json` when using the classic JSX runtime, preventing false positive [noUnusedImports](https://biomejs.dev/linter/rules/no-unused-imports/) errors for custom JSX libraries like Preact.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix spacing in description text.

Line 5 has a missing space after the closing backtick. Should be:

-Added support for `jsxFactory` and `jsxFragmentFactory`.Biome now respects...
+Added support for `jsxFactory` and `jsxFragmentFactory`. Biome now respects...

Otherwise, the changeset is well-structured with good examples and a helpful rule link. Nice work! ✓

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Added support for `jsxFactory` and `jsxFragmentFactory`.Biome now respects `jsxFactory` and `jsxFragmentFactory` settings from `tsconfig.json` when using the classic JSX runtime, preventing false positive [noUnusedImports](https://biomejs.dev/linter/rules/no-unused-imports/) errors for custom JSX libraries like Preact.
Added support for `jsxFactory` and `jsxFragmentFactory`. Biome now respects `jsxFactory` and `jsxFragmentFactory` settings from `tsconfig.json` when using the classic JSX runtime, preventing false positive [noUnusedImports](https://biomejs.dev/linter/rules/no-unused-imports/) errors for custom JSX libraries like Preact.
🤖 Prompt for AI Agents
In .changeset/eight-eels-pull.md around line 5, the sentence "Added support for
`jsxFactory` and `jsxFragmentFactory`.Biome now respects..." is missing a space
after the closing backtick; edit that line to insert a space after the backtick
so it reads "...`jsxFragmentFactory`. Biome now respects..." to fix the spacing.

Comment on lines 45 to 47
fn source_map(&self) -> Option<&TransformSourceMap> {
None
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Bug: source_map() always returns None despite the field being set.

The with_source_map method on line 32 sets self.source_map, but this accessor ignores it and always returns None. This breaks source map functionality used in verbatim.rs (lines 60-63, 108-116).

     fn source_map(&self) -> Option<&TransformSourceMap> {
-        None
+        self.source_map.as_ref()
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fn source_map(&self) -> Option<&TransformSourceMap> {
None
}
fn source_map(&self) -> Option<&TransformSourceMap> {
self.source_map.as_ref()
}
🤖 Prompt for AI Agents
In crates/biome_yaml_formatter/src/context.rs around lines 45-47, the
source_map() accessor currently always returns None even though with_source_map
sets self.source_map; change the method to return a reference to the stored
Option by returning self.source_map.as_ref() (or Some(&self.source_map) if the
field is not an Option) so callers receive the actual TransformSourceMap; ensure
the function signature remains fn source_map(&self) ->
Option<&TransformSourceMap> and compile.

Comment on lines 234 to 242
/// Rule for formatting an bogus nodes.
pub(crate) trait FormatBogusNodeRule<N>
where
N: AstNode<Language = YamlLanguage>,
{
fn fmt(&self, node: &N, f: &mut YamlFormatter) -> FormatResult<()> {
format_bogus_node(node.syntax()).fmt(f)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Minor typo in doc comment.

"an bogus nodes" → "a bogus node" or "bogus nodes".

-/// Rule for formatting an bogus nodes.
+/// Rule for formatting bogus nodes.
 pub(crate) trait FormatBogusNodeRule<N>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// Rule for formatting an bogus nodes.
pub(crate) trait FormatBogusNodeRule<N>
where
N: AstNode<Language = YamlLanguage>,
{
fn fmt(&self, node: &N, f: &mut YamlFormatter) -> FormatResult<()> {
format_bogus_node(node.syntax()).fmt(f)
}
}
/// Rule for formatting bogus nodes.
pub(crate) trait FormatBogusNodeRule<N>
where
N: AstNode<Language = YamlLanguage>,
{
fn fmt(&self, node: &N, f: &mut YamlFormatter) -> FormatResult<()> {
format_bogus_node(node.syntax()).fmt(f)
}
}
🤖 Prompt for AI Agents
In crates/biome_yaml_formatter/src/lib.rs around lines 234 to 242, the doc
comment for the trait reads "Rule for formatting an bogus nodes" which contains
a typo and grammatical error; update the comment to use correct article and
number, e.g. "Rule for formatting a bogus node" or "Rule for formatting bogus
nodes" to match intended meaning and keep it consistent with surrounding docs.

@dyc3 dyc3 marked this pull request as draft November 26, 2025 14:55
@pkg-pr-new
Copy link

pkg-pr-new bot commented Nov 26, 2025

Open in StackBlitz

npm i https://pkg.pr.new/biomejs/biome/@biomejs/wasm-web@8275

commit: 4804acf

@codspeed-hq
Copy link

codspeed-hq bot commented Nov 26, 2025

Merging this PR will degrade performance by 6.06%

❌ 1 regressed benchmark
✅ 57 untouched benchmarks
⏩ 95 skipped benchmarks1

⚠️ Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
js_analyzer[lint_13640784270757307929.ts] 55.9 ms 59.5 ms -6.06%

Comparing next (4804acf) with main (5ac2ad6)

Open in CodSpeed

Footnotes

  1. 95 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@github-actions
Copy link
Contributor

github-actions bot commented Nov 26, 2025

Parser conformance results on

js/262

Test result main count This PR count Difference
Total 51887 51887 0
Passed 50674 50674 0
Failed 1171 1171 0
Panics 42 42 0
Coverage 97.66% 97.66% 0.00%

jsx/babel

Test result main count This PR count Difference
Total 40 40 0
Passed 37 37 0
Failed 3 3 0
Panics 0 0 0
Coverage 92.50% 92.50% 0.00%

symbols/microsoft

Test result main count This PR count Difference
Total 6323 6323 0
Passed 2106 2106 0
Failed 4217 4217 0
Panics 0 0 0
Coverage 33.31% 33.31% 0.00%

ts/babel

Test result main count This PR count Difference
Total 835 835 0
Passed 742 742 0
Failed 93 93 0
Panics 0 0 0
Coverage 88.86% 88.86% 0.00%

ts/microsoft

Test result main count This PR count Difference
Total 18823 18823 0
Passed 14069 14069 0
Failed 4753 4753 0
Panics 1 1 0
Coverage 74.74% 74.74% 0.00%

dyc3 and others added 18 commits January 31, 2026 09:03
Co-authored-by: Emanuele Stoppa <my.burning@gmail.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
…8908)

Co-authored-by: Emanuele Stoppa <my.burning@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Carson McManus <dyc3@users.noreply.github.com>
@github-actions github-actions bot removed A-Tooling Area: internal tools A-LSP Area: language server protocol A-Type-Inference Area: type inference labels Feb 13, 2026
dyc3 and others added 3 commits February 14, 2026 08:47
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

Co-authored-by: dyc3 <1808807+dyc3@users.noreply.github.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Victorien Elvinger <victorien@elvinger.fr>

Co-authored-by: dyc3 <1808807+dyc3@users.noreply.github.com>
Co-authored-by: Conaclos <2358560+Conaclos@users.noreply.github.com>
Co-authored-by: chansuke <501052+chansuke@users.noreply.github.com>
@ematipico ematipico marked this pull request as ready for review February 14, 2026 15:00
@ematipico ematipico changed the title chore: merge next into main chore: biome v2.4 Feb 14, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 15

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

🤖 Fix all issues with AI agents
In @.changeset/add-e18e-rule-source.md:
- Line 5: Update the six rule mentions to include their documentation links: for
each rule name useAtIndex, useExponentiationOperator, noPrototypeBuiltins,
useDateNow, useSpread, and useObjectSpread, append or replace the plain text
with a Markdown link to the official e18e/website docs (e.g.
[useAtIndex](https://.../rules/use-at-index) style). Ensure each rule reference
points to its corresponding rule page on the public docs site so all six entries
become clickable documentation links.

In @.changeset/angry-women-accept.md:
- Around line 1-3: Update the changeset front matter to use "patch" instead of
"minor" for the "@biomejs/biome" entry so it aligns with main-branch rules;
locate the YAML front matter block in the changeset (the top lines showing
"@biomejs/biome": minor) and change the value from minor to patch, keeping the
rest of the front matter intact.

In @.changeset/odd-flies-nail.md:
- Around line 1-3: The changeset currently declares a "minor" release but the PR
targets main; update the changeset declaration in odd-flies-nail.md to "patch"
if this is a non-breaking fix for main, or explicitly confirm and document that
a "minor" release should be carried (i.e., this is a feature intended for next)
so the release type matches the branch intent; ensure the first line in the
changeset front-matter is adjusted accordingly.

In @.changeset/small-seas-laugh.md:
- Around line 1-2: Update the changeset entry that currently reads
`"@biomejs/biome": minor` to use `"patch"` since this PR targets main; locate
the .changeset file containing the changeset header and change the version bump
type from minor to patch so it conforms to the branch-specific changeset
guidance.

In @.changeset/wide-masks-mix.md:
- Around line 1-5: The changeset currently labels the release as a minor change
and uses a generic description; change the frontmatter tag from "minor" to
"patch" and update the description line to begin with the required bug-fix
format "Fixed `#NUMBER`(...): ..." (replace `#NUMBER` with the actual issue number
and include the issue link if available), ensuring both the frontmatter and the
human-readable description at the top of .changeset/wide-masks-mix.md are
updated to reflect a patch bugfix.

In `@crates/biome_analyze/src/diagnostics.rs`:
- Around line 102-136: OffsetVisitor currently only overrides record_frame,
causing other Visit methods to be dropped when applying an offset; update
OffsetVisitor (used in advices on DiagnosticKind::Raw) to implement and forward
all Visit trait methods (record_log, record_list, record_diff, record_backtrace,
record_command, record_group, record_table) to self.inner unchanged while
keeping record_frame logic that adjusts the span, so all non-frame advices are
preserved and only frame spans are offset.

In `@crates/biome_analyze/src/rule.rs`:
- Around line 287-290: The Ord/PartialOrd mapping assigns duplicate index 37 to
Self::HtmlEslint and Self::EslintE18e causing incorrect comparisons; update the
index values so each variant uses a unique integer (e.g., bump one of the
duplicates to the next unused index) in the match that feeds the ordering
implementation (the match arms for HtmlEslint, EslintE18e,
EslintBetterTailwindcss), then run tests to verify deterministic ordering of
rules.

In `@crates/biome_cli/src/commands/mod.rs`:
- Around line 689-709: The match in the log_options() function incorrectly
returns None for daemon-related variants causing Start, LspProxy, and RunServer
log options to be ignored; update the match arm to return Some(log_options) for
those variants (e.g., include Self::Start { log_options, .. } | Self::LspProxy {
log_options, .. } | Self::RunServer { log_options, .. } in the Some(...) arm) so
that log_options(), and downstream helpers like log_level() / log_kind(),
receive the user's logging preferences.

In `@crates/biome_cli/src/runner/impls/collectors/default.rs`:
- Around line 262-265: The is_error logic treats any format run as an error even
when write access is allowed; change the grouping so CI always errors but
format/check only error when write access is not required: compute is_error as
execution.is_ci() || ((execution.is_format() || execution.is_check()) &&
!execution.requires_write_access()); update the is_error assignment (and its
parentheses) to use this grouping so execution.is_format(),
execution.is_check(), and execution.requires_write_access() interact correctly.
- Around line 173-183: In the Message::Error arm (in the collector
implementation in default.rs) you only increment warnings/infos, so Error and
Fatal diagnostics never increment the error counter; update the branch to check
err.severity() for Severity::Error and Severity::Fatal and call
self.errors.fetch_add(1, Ordering::Relaxed) for those cases (after the
should_skip_diagnostic check), leaving the existing increments for Warning and
Information in place.

In `@crates/biome_cli/src/runner/impls/finalizers/default.rs`:
- Around line 111-143: The JSON branch currently always writes to
console_reporter_writer so file destinations are ignored; update the
CliReporterKind::Json / JsonPretty handling to detect
cli_reporter.is_file_report() and use file_reporter_writer (instead of
console_reporter_writer) when true: construct the JsonReporter and
JsonReporterVisitor as now, call reporter.write(...) with the chosen writer and
buffer, obtain root from buffer.to_json(), then either format/print or to_string
and call file_reporter_writer.log(...) (and ensure file_reporter_writer.dump()
is invoked where needed) so both pretty and non‑pretty JSON end up in the file
output rather than only the console.

In `@crates/biome_cli/src/runner/impls/process_file/lint_and_assist.rs`:
- Around line 183-221: open_file is called on workspace but early returns for
ignored or protected files (branches using file_features.is_ignored() and
file_features.is_protected()) never call close_file, leaving documents open;
before each return in those branches, call workspace.close_file(CloseFileParams
{ project_key, path: biome_path.clone() }) (or the crate's equivalent close_file
API) to ensure the file is closed, and do this both for the ignored branch and
for the protected branch right after logging/console operations so the
open/close pairing is preserved even on early exits.
- Around line 274-306: The code currently returns
StdinDiagnostic::new_not_formatted() whenever new_content is borrowed and
execution.requires_write_access() is false, which wrongfully errors on stdin
even when formatting would produce identical output; change the logic so we only
return the NotFormatted error if the formatter (workspace.format_file / the
computed output) would have produced a different string than original_content.
Concretely: after computing output (the formatted string) and before possibly
replacing new_content, record whether formatting_changed = output !=
original_content (or compare the formatter result or transformed code that would
be written), and in the match arm for Cow::Borrowed(original_content) only
return Err(StdinDiagnostic::new_not_formatted().into()) when
!execution.requires_write_access() && formatting_changed (or other
transformations would have changed), otherwise proceed without error. Ensure you
reference the existing variables execution, output, new_content,
original_content, workspace.format_file and StdinDiagnostic::new_not_formatted()
when making the change.
- Around line 45-62: The code always constructs Some(suppression_explanation)
and passes it to workspace_file.guard().fix_file(...), which causes suppression
metadata to be applied even when execution.suppress() is false; change the logic
to build an Option<String> that is None when suppression is not enabled and
Some(reason) only when execution.suppress() is true (use
execution.suppression_reason().map(|s| s.to_string()).or_else(|| Some("ignored
using `--suppress`".to_string())) when suppressed), then pass that Option
directly to fix_file instead of wrapping an unconditional Some(...).

In `@crates/biome_cli/src/runner/process_file.rs`:
- Around line 235-270: The code reads the file into `input` once, then in the
`else` branch re-reads `file.read_to_string(&mut input)` which runs at EOF and
yields empty content; remove the redundant second read and reuse the earlier
`input` when calling `ctx.workspace().open_file` (keep `OpenOptions`, `file`,
`input`, `FileGuard`, `ctx.workspace().file_exists`, `OpenFileParams`, and
`FileContent::from_client` as-is), ensuring the content passed to `open_file` is
the initially read `input`.
🟡 Minor comments (22)
.changeset/fast-months-move.md-5-5 (1)

5-5: ⚠️ Potential issue | 🟡 Minor

Fix the typo in the user-facing description.

There’s a duplicated preposition (“in inside”) in the sentence. Consider:

✍️ Suggested edit
-Fixed [`#901`](https://github.com/biomejs/biome-vscode/issues/901). Biome now allows trailing commas in inside Zed `settings.json` and VSCode `settings.json`.
+Fixed [`#901`](https://github.com/biomejs/biome-vscode/issues/901). Biome now allows trailing commas inside Zed `settings.json` and VS Code `settings.json`.
.changeset/nine-ends-wink.md-1-5 (1)

1-5: ⚠️ Potential issue | 🟡 Minor

Align changeset type with target branch.

This changeset is marked minor, but the PR targets main. Guidelines say main should use patch for non‑breaking changes. If this is meant for main, please switch to patch; otherwise, confirm it’s intended for next only.
As per coding guidelines “Use 'patch' for bug fixes and non-breaking changes targeting the main branch, 'minor' for new features targeting the next branch, and 'major' for breaking changes targeting the next branch.”

.changeset/rich-flowers-hunt.md-1-3 (1)

1-3: ⚠️ Potential issue | 🟡 Minor

Use a patch changeset on main.

This PR targets main, so non‑breaking user‑facing changes should be patch, not minor. Please update the front matter accordingly. As per coding guidelines, "Use 'patch' for bug fixes and non-breaking changes targeting the main branch, 'minor' for new features targeting the next branch, and 'major' for breaking changes targeting the next branch."

.changeset/eight-bars-unite.md-5-5 (1)

5-5: ⚠️ Potential issue | 🟡 Minor

Tiny wording tweak: “HTML language” → “HTML”.

Saves a word and reads cleaner.

.changeset/eight-bars-unite.md-2-2 (1)

2-2: ⚠️ Potential issue | 🟡 Minor

Confirm the changeset type matches the target branch policy.

This PR targets main, but the changeset is marked minor. If this merge PR is expected to carry over “next” changesets, all good; otherwise this should likely be patch for main. As per coding guidelines: “Use 'patch' for bug fixes and non-breaking changes targeting the main branch, 'minor' for new features targeting the next branch, and 'major' for breaking changes targeting the next branch.”

.changeset/odd-flies-nail.md-7-7 (1)

7-7: ⚠️ Potential issue | 🟡 Minor

Tighten the sentence by dropping the comma before “because”.

Minor grammar tweak for readability.

✂️ Suggested edit
-If your codebase has only `*.module.css` files, you can remove the parser feature as follows, because now Biome does it for you:
+If your codebase has only `*.module.css` files, you can remove the parser feature as follows because Biome now does it for you:
.changeset/angry-women-accept.md-5-5 (1)

5-5: ⚠️ Potential issue | 🟡 Minor

Tiny grammar fix (“will a emit”).

Quick tweak for readability in the user-facing text.

✍️ Suggested edit
-... Otherwise, Biome will a emit a parse error.
+... Otherwise, Biome will emit a parse error.
.changeset/group-by-nesting-feature.md-44-44 (1)

44-44: ⚠️ Potential issue | 🟡 Minor

Fix formatting: missing space before opening brace.

The example has a spacing inconsistency.

🔧 Proposed fix
-const object ={
+const object = {
.changeset/ready-taxes-build.md-9-9 (1)

9-9: ⚠️ Potential issue | 🟡 Minor

Fix issue number mismatch in link.

The text references #6625 but the link points to #6623. Please correct the link or the issue number.

🔗 Proposed fix
-This overhaul fixes several issues ([`#5150`](https://github.com/biomejs/biome/issues/5150), [`#6625`](https://github.com/biomejs/biome/issues/6623), [`#8437`](https://github.com/biomejs/biome/issues/8437)) around whitespace sensitivity that were causing incorrect formatting in certain scenarios that were difficult or impossible to fully address before.
+This overhaul fixes several issues ([`#5150`](https://github.com/biomejs/biome/issues/5150), [`#6625`](https://github.com/biomejs/biome/issues/6625), [`#8437`](https://github.com/biomejs/biome/issues/8437)) around whitespace sensitivity that were causing incorrect formatting in certain scenarios that were difficult or impossible to fully address before.
.changeset/proud-ends-hear.md-11-21 (1)

11-21: ⚠️ Potential issue | 🟡 Minor

Refine the code example to avoid redeclaration errors.

The example declares const a three times, which would produce syntax errors in JavaScript. Whilst the point is to demonstrate unformatted code preservation, a realistic example would be clearer and more helpful.

💡 Suggested improvement
 ```js
 // biome-ignore-all format: generated

-const a = [  ]
+const a = [  ]


-const a = [  ]
+const b = {  }


-const a = [  ]
+const c = (  )=>  {}

This demonstrates the feature with valid syntax whilst showing various unformatted constructs.
</details>

</blockquote></details>
<details>
<summary>crates/biome_cli/tests/cases/handle_svelte_files.rs-686-709 (1)</summary><blockquote>

`686-709`: _⚠️ Potential issue_ | _🟡 Minor_

**Missing `biome.json` configuration.**

This test lacks the `biome.json` setup that all other new tests include and that the Vue equivalent (`use_import_type_not_triggered_for_enum_in_template` in `handle_vue_files.rs`) has. Without enabling `experimentalFullSupportEnabled`, the HTML linter behaviour may differ from what's being tested.

<details>
<summary>🔧 Proposed fix</summary>

```diff
 fn use_import_type_not_triggered_for_enum_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.svelte");
     fs.insert(file.into(), SVELTE_ENUM_IN_TEMPLATE.as_bytes());
.changeset/silly-walls-build.md-5-5 (1)

5-5: ⚠️ Potential issue | 🟡 Minor

Line 5: Change useImportTypes to useImportType.

Lint rule names follow the use<Concept> pattern for mandated concepts, so it's useImportType (singular), matching line 24.

crates/biome_cli/src/runner/finalizer.rs-14-16 (1)

14-16: ⚠️ Potential issue | 🟡 Minor

Minor typo: "finalise" should be "finalize" for consistency.

The codebase uses American English spelling ("finalize") throughout. Line 15 uses "finalise".

📝 Suggested fix
     /// Optional hook to run before finalization. Useful for commands that need
-    /// to work with the Workspace before finally finalise the command.
+    /// to work with the Workspace before finally finalizing the command.
crates/biome_cli/src/runner/impls/process_file/format.rs-55-71 (1)

55-71: ⚠️ Potential issue | 🟡 Minor

Adjust diagnostics for embedded-file offsets.
For astro/vue/svelte, the reported ranges can be off without the handler offset, which makes errors point at the wrong place.

Proposed fix
-use biome_diagnostics::{Diagnostic, DiagnosticExt, Error, PrintDiagnostic, Severity, category};
+use biome_diagnostics::{Diagnostic, DiagnosticExt, Error, PrintDiagnostic, Severity, category};
+use biome_rowan::TextSize;
@@
-        ctx.push_message(Message::Diagnostics {
+        let offset = if features_supported.supports_full_html_support() {
+            None
+        } else {
+            match workspace_file.as_extension() {
+                Some("vue") => VueFileHandler::start(input.as_str()),
+                Some("astro") => AstroFileHandler::start(input.as_str()),
+                Some("svelte") => SvelteFileHandler::start(input.as_str()),
+                _ => None,
+            }
+        };
+
+        ctx.push_message(Message::Diagnostics {
             file_path: workspace_file.path.to_string(),
             content: input.clone(),
             diagnostics: diagnostics_result
                 .diagnostics
                 .into_iter()
+                .map(|diagnostic| {
+                    if let Some(offset) = offset {
+                        diagnostic.with_offset(TextSize::from(offset))
+                    } else {
+                        diagnostic
+                    }
+                })
                 // Formatting is usually blocked by errors, so we want to print only diagnostics that
                 // Have error severity
                 .filter_map(|diagnostic| {
                     if diagnostic.severity() >= Severity::Error {
                         Some(Error::from(diagnostic))
                     } else {
                         None
                     }
                 })
                 .collect(),
             skipped_diagnostics: diagnostics_result.skipped_diagnostics as u32,
         });
.changeset/tangy-dingos-design.md-5-5 (1)

5-5: ⚠️ Potential issue | 🟡 Minor

Minor grammatical tweak needed.

The sentence reads "how Biome will attempt the configuration file is" which is missing a verb. Consider:

-Added the ability to load the hidden files `.biome.json` and `.biome.jsonc`. This is the order how Biome will attempt the configuration file is:
+Added the ability to load the hidden files `.biome.json` and `.biome.jsonc`. This is the order in which Biome will attempt to load the configuration file:
crates/biome_cli/tests/cases/reporter_sarif.rs-172-188 (1)

172-188: ⚠️ Potential issue | 🟡 Minor

Function name and command mismatch.

The function is named reports_diagnostics_sarif_check_command_file but actually runs the format command (line 188). Either rename the function to reports_diagnostics_sarif_format_command_file or change the command to "check".

Option 1: Fix the command to match the name
             [
-                "format",
+                "check",
                 "--reporter=sarif",
                 "--reporter-file=file.json",
crates/biome_cli/tests/cases/reporter_terminal.rs-141-142 (1)

141-142: ⚠️ Potential issue | 🟡 Minor

Function name may be misleading.

The function is named reports_diagnostics_check_write_command_file but doesn't include the --write flag (unlike reports_diagnostics_check_write_command_verbose above which does). Consider renaming to reports_diagnostics_check_command_file for clarity.

.changeset/all-pumas-stop.md-31-31 (1)

31-31: ⚠️ Potential issue | 🟡 Minor

Tidy the final sentence grammar.
A separator reads better here.

💬 Suggested edit
-**The `--reporter` and `--reporter-file` flags must appear next to each other, otherwise an error is thrown.**
+**The `--reporter` and `--reporter-file` flags must appear next to each other; otherwise, an error is thrown.**
crates/biome_cli/src/commands/migrate.rs-142-148 (1)

142-148: ⚠️ Potential issue | 🟡 Minor

Avoid a panic if the config path is missing.
unwrap() here can crash if call ordering ever changes. A defensive error keeps the CLI stable.

Suggested fix
-        let payload = crate::execute::migrate::MigratePayload {
+        let configuration_file_path = self.configuration_file_path.clone().ok_or_else(|| {
+            CliDiagnostic::MigrateError(MigrationDiagnostic {
+                reason: "Biome couldn't find the Biome configuration file.".to_string(),
+            })
+        })?;
+        let payload = crate::execute::migrate::MigratePayload {
             session,
             project_key,
             write: self.should_write(),
-            // SAFETY: checked during get_execution
-            configuration_file_path: self.configuration_file_path.clone().unwrap(),
+            configuration_file_path,
             sub_command: self.sub_command.clone(),
             nested_configuration_files: configuration_files,
         };
crates/biome_cli/src/runner/handler.rs-142-156 (1)

142-156: ⚠️ Potential issue | 🟡 Minor

Count panics so summaries don’t under-report.
On panic you emit diagnostics but don’t update counters, so totals can drift. Mirror the error branch.

Suggested fix
             Err(err) => {
                 let message = match err.downcast::<String>() {
                     Ok(msg) => format!("processing panicked: {msg}"),
                     Err(err) => match err.downcast::<&'static str>() {
                         Ok(msg) => format!("processing panicked: {msg}"),
                         Err(_) => String::from("processing panicked"),
                     },
                 };
+                ctx.increment_unchanged();
+                ctx.increment_skipped();
 
                 ctx.push_message(
                     PanicDiagnostic { message }
                         .with_file_path(biome_path.to_string())
                         .into(),
crates/biome_cli/src/reporter/mod.rs-201-206 (1)

201-206: ⚠️ Potential issue | 🟡 Minor

Delegate dump/clear to the wrapped console.
Stubbing these out hides output for consoles that implement them.

Suggested fix
     fn dump(&mut self) -> Option<String> {
-        None
+        self.0.dump()
     }
 
-    fn clear(&mut self) {}
+    fn clear(&mut self) {
+        self.0.clear();
+    }
crates/biome_cli/src/reporter/json.rs-412-429 (1)

412-429: ⚠️ Potential issue | 🟡 Minor

Panic risk on invalid spans.

Lines 415-416 use expect("Invalid span") which will panic if source.location() fails. Whilst the guard on line 413 ensures span and source_code exist, edge cases (e.g., spans pointing beyond source bounds) could still cause panics. Consider returning early or using defaults.

🛡️ Defensive alternative
     fn record_frame(&mut self, location: Location<'_>) -> std::io::Result<()> {
         if let (Some(span), Some(source_code)) = (location.span, location.source_code) {
             let source = SourceFile::new(source_code);
-            let start = source.location(span.start()).expect("Invalid span");
-            let end = source.location(span.end()).expect("Invalid span");
+            let Some(start) = source.location(span.start()).ok() else {
+                return Ok(());
+            };
+            let Some(end) = source.location(span.end()).ok() else {
+                return Ok(());
+            };
 
             self.suggestions.push(JsonSuggestion {
🧹 Nitpick comments (25)
.changeset/blue-buttons-own.md (1)

1-3: Change type should be patch for main-branch non‑breaking updates.

This PR targets main, so the changeset should be patch unless it’s a breaking change. Please adjust the front matter accordingly. As per coding guidelines, “Use 'patch' for bug fixes and non-breaking changes targeting the main branch.”

.changeset/add-use-anchor-content-html.md (1)

5-5: Please add an invalid example snippet.

Changesets for new lint rules should include an invalid-case example so users immediately understand what gets flagged. A short inline or fenced HTML example would do nicely.

As per coding guidelines: “For new lint rules in changesets, show an example of invalid case in inline code or code block”.

.changeset/short-points-tie.md (1)

5-55: Show formatting changes as diffs.

This is a formatter-facing change, so the examples should be presented as diff code blocks to highlight the before/after formatting. Based on learnings: “For formatter changes in changesets, show formatting changes using diff code blocks”.

.changeset/tiny-dodos-ask.md (1)

5-5: Consider adding an issue number.

As per coding guidelines, bug fix changesets should follow the format: "Fixed [#NUMBER](issue link): ..." to help users track the original issue.

.github/workflows/benchmark_html.yml (2)

10-15: Redundant path patterns.

Lines 12–14 are already covered by crates/biome_html_*/**/*.rs on line 15. Same applies to the push paths (lines 25–28). You could simplify by removing the explicit biome_html_analyze, biome_html_formatter, and biome_html_parser entries.

That said, if these are kept intentionally for clarity, feel free to ignore.

🧹 Suggested simplification
     paths:
       - 'Cargo.lock'
-      - 'crates/biome_html_analyze/**/*.rs'
-      - 'crates/biome_html_formatter/**/*.rs'
-      - 'crates/biome_html_parser/**/*.rs'
       - 'crates/biome_html_*/**/*.rs'
       - 'crates/biome_formatter/**/*.rs'
       - 'crates/biome_rowan/**/*.rs'
       - 'crates/biome_parser/**/*.rs'

63-63: Consider next branch for cache base.

cache-base is set to main, but this workflow also runs on the next branch. Depending on how often next diverges, builds on that branch might miss warm cache hits. If that's a concern, you could potentially use a conditional or accept the current behaviour.

.changeset/html-use-alt-text.md (1)

5-5: Consider simplifying to focus on impact rather than implementation details.

The description includes helpful accessibility context but enumerates specific elements and attributes. Per the guidelines, changeset descriptions should explain impact and why users care, not implementation details. The documentation link already provides the technical specifics.

✨ Suggested refinement
-Added the [`useAltText`](https://biomejs.dev/linter/rules/use-alt-text/) lint rule for HTML. This rule enforces that elements requiring alternative text (`<img>`, `<area>`, `<input type="image">`, `<object>`) provide meaningful information for screen reader users via `alt`, `title` (for objects), `aria-label`, or `aria-labelledby` attributes. Elements with `aria-hidden="true"` are exempt.
+Added the [`useAltText`](https://biomejs.dev/linter/rules/use-alt-text/) lint rule for HTML. This rule enforces that elements requiring alternative text provide meaningful information for screen reader users, improving accessibility.

Based on learnings: "Write changeset descriptions for end users, explaining impact and why they care, not implementation details. Be concise (1-3 sentences)."

.claude/skills/prettier-compare/SKILL.md (1)

44-44: Optional: Consider adding a comma before "so".

The sentence would read slightly better as: "...without an extension, so both formatters...". Totally minor though.

crates/biome_cli/tests/commands/format.rs (1)

3727-3728: Consider adding assert_file_contents for consistency.

The JSON trailing newline tests omit file content assertions while all other language tests (JS, CSS, GraphQL, HTML) include them. If this is intentional due to JSON formatter behaviour, a brief comment explaining why would be helpful. Otherwise, consider adding:

assert_file_contents(&fs, test, "{ \"name\": \"test\" }");
.claude/skills/testing-codegen/SKILL.md (1)

59-60: Consider clarifying the qt command on first use.

The just qt command is introduced here without explanation. First-time readers might not immediately recognise that "qt" stands for "quick test". Consider adding a brief note like just qt biome_js_analyze # qt = quick test or explaining it in the surrounding text.

.changeset/promote-nursery-rules-to-stable.md (1)

23-24: Minor readability suggestion.

Consider adding a comma after "Vue 3 setup" for clarity: "...props in Vue 3 setup, which causes reactivity loss."

✏️ Suggested fix
-- [`noVueSetupPropsReactivityLoss`](https://biomejs.dev/linter/rules/no-vue-setup-props-reactivity-loss/). The rule reports destructuring of props in Vue 3 setup which causes reactivity loss.
+- [`noVueSetupPropsReactivityLoss`](https://biomejs.dev/linter/rules/no-vue-setup-props-reactivity-loss/). The rule reports destructuring of props in Vue 3 setup, which causes reactivity loss.
.changeset/rotten-squids-ring.md (1)

11-11: Minor grammar nit.

"The priority how Biome will attempt" reads a bit awkwardly. Consider: "The order in which Biome will attempt" or "The priority order for how Biome will attempt".

Suggested fix
-The priority how Biome will attempt to load the configuration files is the following:
+The order in which Biome will attempt to load the configuration files is the following:
crates/biome_cli/src/runner/impls/process_file/check.rs (2)

52-77: Empty if-branch is a code smell; prefer negated condition.

The empty block at lines 53-54 makes the intent less clear. Consider inverting the condition to eliminate the empty branch.

♻️ Suggested refactor
         if features_supported.supports_format() {
-            if execution.should_skip_parse_errors() && skipped_parse_error {
-                // Parse errors are already skipped during the analyze phase, so no need to do it here.
-            } else {
+            // Parse errors are already skipped during the analyze phase, so no need to do it here.
+            if !(execution.should_skip_parse_errors() && skipped_parse_error) {
                 let format_result =
                     FormatProcessFile::process_file(ctx, workspace_file, features_supported);

30-50: Consider extracting duplicated result-handling logic.

The match arms for analyzer_result (lines 30-50) and format_result (lines 59-75) are nearly identical. A helper function could reduce duplication and improve maintainability.

Also applies to: 59-75

crates/biome_cli/src/runner/impls/executions/summary_verb.rs (1)

20-30: Duplicated Files formatting logic.

This Files struct and its Display implementation duplicate the one in crates/biome_cli/src/reporter/terminal.rs (lines 169-176). Consider extracting to a shared location to maintain consistency and reduce duplication.

crates/biome_cli/tests/commands/ci.rs (1)

987-994: Consider extracting repeated message-filtering logic.

The pattern for filtering console messages by LogLevel::Error and content appears five times. A helper function would reduce duplication and make tests more maintainable.

Also applies to: 1019-1026, 1059-1066, 1102-1109, 1146-1153

crates/biome_cli/tests/cases/reporter_checkstyle.rs (1)

190-190: Consider using .xml extension for checkstyle output.

Checkstyle format is XML, so --reporter-file=file.xml would be more conventional than file.json. Not a blocker—just a clarity nit.

crates/biome_cli/src/cli_options.rs (1)

194-204: Redundant struct-like pattern on unit variants.

The Display impl uses { .. } patterns on unit enum variants (e.g., Self::Default { .. }), but these variants have no fields. This is syntactically valid but misleading and inconsistent with the enum definition.

🔧 Suggested simplification
 impl Display for CliReporterKind {
     fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
         match self {
-            Self::Default { .. } => f.write_str("default"),
-            Self::Json { .. } => f.write_str("json"),
-            Self::JsonPretty { .. } => f.write_str("json-pretty"),
-            Self::Summary { .. } => f.write_str("summary"),
-            Self::GitHub { .. } => f.write_str("github"),
-            Self::Junit { .. } => f.write_str("junit"),
-            Self::GitLab { .. } => f.write_str("gitlab"),
-            Self::Checkstyle { .. } => f.write_str("checkstyle"),
-            Self::RdJson { .. } => f.write_str("rdjson"),
-            Self::Sarif { .. } => f.write_str("sarif"),
+            Self::Default => f.write_str("default"),
+            Self::Json => f.write_str("json"),
+            Self::JsonPretty => f.write_str("json-pretty"),
+            Self::Summary => f.write_str("summary"),
+            Self::GitHub => f.write_str("github"),
+            Self::Junit => f.write_str("junit"),
+            Self::GitLab => f.write_str("gitlab"),
+            Self::Checkstyle => f.write_str("checkstyle"),
+            Self::RdJson => f.write_str("rdjson"),
+            Self::Sarif => f.write_str("sarif"),
         }
     }
 }
crates/biome_analyze/src/profiling.rs (1)

356-478: Consider extracting the cutoff calculation logic.

The cutoff calculation pattern is repeated five times (lines 394-427). While readable, extracting a helper function could reduce duplication.

crates/biome_cli/src/runner/mod.rs (1)

444-444: TODO: CLI flag for force scan.

There's a TODO about potentially adding a CLI flag for force scanning. Consider tracking this in an issue if it's a planned feature.

Would you like me to open an issue to track this potential feature?

crates/biome_cli/src/reporter/sarif.rs (1)

147-175: Filtering logic could be simplified.

The verbose/non-verbose filtering has duplicated branches. Consider restructuring to reduce nesting.

🔧 Suggested simplification
         let sarif_results: Vec<_> = payload
             .diagnostics
             .iter()
             .filter_map(|diagnostic| {
-                if diagnostic.severity() >= payload.diagnostic_level {
-                    if diagnostic.tags().is_verbose() {
-                        if verbose {
-                            if let Some(driver_rule) =
-                                to_sarif_driver_rule(diagnostic, &self.rule_descriptions)
-                            {
-                                sarif_rules.insert(driver_rule);
-                            }
-                            to_sarif_result(diagnostic, working_directory)
-                        } else {
-                            None
-                        }
-                    } else {
-                        if let Some(driver_rule) =
-                            to_sarif_driver_rule(diagnostic, &self.rule_descriptions)
-                        {
-                            sarif_rules.insert(driver_rule);
-                        }
-                        to_sarif_result(diagnostic, working_directory)
-                    }
-                } else {
-                    None
+                if diagnostic.severity() < payload.diagnostic_level {
+                    return None;
+                }
+                if diagnostic.tags().is_verbose() && !verbose {
+                    return None;
                 }
+                if let Some(driver_rule) =
+                    to_sarif_driver_rule(diagnostic, &self.rule_descriptions)
+                {
+                    sarif_rules.insert(driver_rule);
+                }
+                to_sarif_result(diagnostic, working_directory)
             })
             .collect();
crates/biome_cli/src/reporter/github.rs (1)

56-60: Redundant condition check.

The else if diagnostic.tags().is_verbose() && verbose on line 58 is redundant—since the if on line 56 already checked !diagnostic.tags().is_verbose(), the else if branch will only execute when is_verbose() is true. Simplify to else if verbose.

✨ Suggested simplification
             if diagnostic.severity() >= diagnostics_payload.diagnostic_level {
                 if !diagnostic.tags().is_verbose() {
                     writer.log(markup! {{PrintGitHubDiagnostic(diagnostic)}});
-                } else if diagnostic.tags().is_verbose() && verbose {
+                } else if verbose {
                     writer.log(markup! {{PrintGitHubDiagnostic(diagnostic)}});
                 }
             }
crates/biome_cli/src/reporter/json.rs (2)

215-220: Dual serialisation paths present.

The Display impl uses serde_json::to_string(&self) (line 217), whilst to_json() builds a JsonRoot AST. If these are meant to produce identical output, there's a risk of divergence. If they serve different purposes (e.g., one for streaming, one for formatted output), consider adding a brief doc comment clarifying when each is used.


312-321: Consider propagating errors instead of unwrap.

Line 318 uses unwrap() on diagnostic.advices(&mut visitor). Whilst unlikely to fail, propagating the error would be more defensive.

✨ Suggested improvement
 fn to_advices(diagnostic: &Error) -> Vec<JsonSuggestion> {
     let mut visitor = SuggestionsVisitor {
         suggestions: vec![],
         last_diagnostic_length: 0,
         current_message: None,
     };
-    diagnostic.advices(&mut visitor).unwrap();
+    // Errors during advice collection are non-fatal; return partial results
+    let _ = diagnostic.advices(&mut visitor);
 
     visitor.suggestions
 }
crates/biome_cli/src/reporter/terminal.rs (1)

249-252: Vague comments could be clarified.

The comments "For now, we'll assume..." appear twice (lines 189-190 and 250-251). If this is a known limitation pending refinement, consider adding a TODO or linking to a tracking issue.

"@biomejs/biome": patch
---

Added e18e ESLint plugin as a recognized rule source. Six Biome rules now reference their e18e equivalents: `useAtIndex`, `useExponentiationOperator`, `noPrototypeBuiltins`, `useDateNow`, `useSpread`, and `useObjectSpread`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Add documentation links for the rules.

The six rules should include links to their documentation pages. As per coding guidelines, reference rules with links to the website documentation.

📚 Suggested improvement
-Added e18e ESLint plugin as a recognized rule source. Six Biome rules now reference their e18e equivalents: `useAtIndex`, `useExponentiationOperator`, `noPrototypeBuiltins`, `useDateNow`, `useSpread`, and `useObjectSpread`.
+Added e18e ESLint plugin as a recognised rule source. Six Biome rules now reference their e18e equivalents: [useAtIndex](https://biomejs.dev/linter/rules/use-at-index), [useExponentiationOperator](https://biomejs.dev/linter/rules/use-exponentiation-operator), [noPrototypeBuiltins](https://biomejs.dev/linter/rules/no-prototype-builtins), [useDateNow](https://biomejs.dev/linter/rules/use-date-now), [useSpread](https://biomejs.dev/linter/rules/use-spread), and [useObjectSpread](https://biomejs.dev/linter/rules/use-object-spread).
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Added e18e ESLint plugin as a recognized rule source. Six Biome rules now reference their e18e equivalents: `useAtIndex`, `useExponentiationOperator`, `noPrototypeBuiltins`, `useDateNow`, `useSpread`, and `useObjectSpread`.
Added e18e ESLint plugin as a recognised rule source. Six Biome rules now reference their e18e equivalents: [useAtIndex](https://biomejs.dev/linter/rules/use-at-index), [useExponentiationOperator](https://biomejs.dev/linter/rules/use-exponentiation-operator), [noPrototypeBuiltins](https://biomejs.dev/linter/rules/no-prototype-builtins), [useDateNow](https://biomejs.dev/linter/rules/use-date-now), [useSpread](https://biomejs.dev/linter/rules/use-spread), and [useObjectSpread](https://biomejs.dev/linter/rules/use-object-spread).
🤖 Prompt for AI Agents
In @.changeset/add-e18e-rule-source.md at line 5, Update the six rule mentions
to include their documentation links: for each rule name useAtIndex,
useExponentiationOperator, noPrototypeBuiltins, useDateNow, useSpread, and
useObjectSpread, append or replace the plain text with a Markdown link to the
official e18e/website docs (e.g. [useAtIndex](https://.../rules/use-at-index)
style). Ensure each rule reference points to its corresponding rule page on the
public docs site so all six entries become clickable documentation links.

Comment on lines +1 to +3
---
"@biomejs/biome": minor
---
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Change type should be patch for main.

This changeset is marked minor, but the guideline says main branch changesets must be patch. Please align the front matter accordingly. As per coding guidelines, “Use 'patch' for bug fixes and non-breaking changes targeting the main branch, 'minor' for new features targeting the next branch, and 'major' for breaking changes targeting the next branch.”

🔧 Suggested front matter update
 "@biomejs/biome": minor
+"@biomejs/biome": patch
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
---
"@biomejs/biome": minor
---
---
"@biomejs/biome": patch
---
🤖 Prompt for AI Agents
In @.changeset/angry-women-accept.md around lines 1 - 3, Update the changeset
front matter to use "patch" instead of "minor" for the "@biomejs/biome" entry so
it aligns with main-branch rules; locate the YAML front matter block in the
changeset (the top lines showing "@biomejs/biome": minor) and change the value
from minor to patch, keeping the rest of the front matter intact.

Comment on lines +1 to +3
---
"@biomejs/biome": minor
---
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Confirm the release type matches the target branch.

This PR targets main, but the changeset declares a minor release. If this is being merged into main, should this be patch instead, or is the intent to carry a next-feature changeset through the merge? A quick confirm here avoids a release bump surprise. As per coding guidelines, "Use 'patch' for bug fixes and non-breaking changes targeting the main branch, 'minor' for new features targeting the next branch, and 'major' for breaking changes targeting the next branch."

🤖 Prompt for AI Agents
In @.changeset/odd-flies-nail.md around lines 1 - 3, The changeset currently
declares a "minor" release but the PR targets main; update the changeset
declaration in odd-flies-nail.md to "patch" if this is a non-breaking fix for
main, or explicitly confirm and document that a "minor" release should be
carried (i.e., this is a feature intended for next) so the release type matches
the branch intent; ensure the first line in the changeset front-matter is
adjusted accordingly.

Comment on lines +1 to +2
---
"@biomejs/biome": minor
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Change type should be patch on main.

This PR targets main, so the changeset type should be patch, not minor. As it stands, it violates the branch-specific changeset guidance. Based on learnings: Target 'main' branch for bug fixes (patch changesets) and new nursery rules (patch changesets). Target 'next' branch for rule promotions from nursery (minor), new features (minor), and breaking changes (major). As per coding guidelines "Use 'patch' for bug fixes and non-breaking changes targeting the main branch, 'minor' for new features targeting the next branch, and 'major' for breaking changes targeting the next branch."

🤖 Prompt for AI Agents
In @.changeset/small-seas-laugh.md around lines 1 - 2, Update the changeset
entry that currently reads `"@biomejs/biome": minor` to use `"patch"` since this
PR targets main; locate the .changeset file containing the changeset header and
change the version bump type from minor to patch so it conforms to the
branch-specific changeset guidance.

Comment on lines +1 to +5
---
"@biomejs/biome": minor
---

Improved the rule `noUnusedVariables` in Svelte files, by correctly detecting variables defined in the JavaScript blocks, and used inside the templates.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Use a patch changeset and the required “Fixed #NUMBER…” format.

This reads like a bug fix on main, so the changeset should be patch, and the description must start with “Fixed #NUMBER(…)…”. Please update both accordingly. As per coding guidelines: “Use 'patch' for bug fixes and non-breaking changes targeting the main branch… For bug fixes, start with 'Fixed [#NUMBER](issue link): ...'.”

✍️ Suggested changeset edit
 ---
-"@biomejs/biome": minor
+"@biomejs/biome": patch
 ---
 
-Improved the rule `noUnusedVariables` in Svelte files, by correctly detecting variables defined in the JavaScript blocks, and used inside the templates.
+Fixed [`#NUMBER`](issue link): `noUnusedVariables` in Svelte now flags variables declared in `<script>` blocks but unused in templates.

Based on learnings: “Target 'main' branch for bug fixes (patch changesets) …” and as per coding guidelines: “Use 'patch' for bug fixes… For bug fixes, start with 'Fixed [#NUMBER](issue link): ...'.”

🤖 Prompt for AI Agents
In @.changeset/wide-masks-mix.md around lines 1 - 5, The changeset currently
labels the release as a minor change and uses a generic description; change the
frontmatter tag from "minor" to "patch" and update the description line to begin
with the required bug-fix format "Fixed `#NUMBER`(...): ..." (replace `#NUMBER` with
the actual issue number and include the issue link if available), ensuring both
the frontmatter and the human-readable description at the top of
.changeset/wide-masks-mix.md are updated to reflect a patch bugfix.

Comment on lines +111 to +143
CliReporterKind::Json | CliReporterKind::JsonPretty => {
console_reporter_writer.error(markup! {
<Warn>"The "<Emphasis>"--json"</Emphasis>" option is "<Underline>"unstable/experimental"</Underline>" and its output might change between patches/minor releases."</Warn>
});
let reporter = JsonReporter {
summary,
diagnostics_payload: &diagnostics_payload,
execution,
verbose: cli_options.verbose,
working_directory: fs.working_directory().clone(),
};
let mut buffer = JsonReporterVisitor::new(summary);
reporter.write(&mut console_reporter_writer, &mut buffer)?;
let root = buffer.to_json();
if cli_reporter.kind == CliReporterKind::JsonPretty {
let formatted = biome_json_formatter::format_node(
JsonFormatOptions::default(),
root.syntax(),
)
.expect("To format the JSON report")
.print()
.expect("To print the JSON report");

console_reporter_writer.log(markup! {
{formatted.as_code()}
});
} else {
let code = root.to_string();
console_reporter_writer.log(markup! {
{code}
});
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Respect JSON file destinations.
When cli_reporter.is_file_report() is true, JSON output still goes to the console writer, so file_reporter_writer.dump() stays empty and the destination never gets written.

Proposed fix
-                        let reporter = JsonReporter {
+                        let reporter = JsonReporter {
                             summary,
                             diagnostics_payload: &diagnostics_payload,
                             execution,
                             verbose: cli_options.verbose,
                             working_directory: fs.working_directory().clone(),
                         };
-                        let mut buffer = JsonReporterVisitor::new(summary);
-                        reporter.write(&mut console_reporter_writer, &mut buffer)?;
+                        let mut buffer = JsonReporterVisitor::new(summary);
+                        let writer: &mut dyn ReporterWriter = if cli_reporter.is_file_report() {
+                            &mut file_reporter_writer
+                        } else {
+                            &mut console_reporter_writer
+                        };
+                        reporter.write(writer, &mut buffer)?;
                         let root = buffer.to_json();
                         if cli_reporter.kind == CliReporterKind::JsonPretty {
                             let formatted = biome_json_formatter::format_node(
                                 JsonFormatOptions::default(),
                                 root.syntax(),
                             )
                             .expect("To format the JSON report")
                             .print()
                             .expect("To print the JSON report");
 
-                            console_reporter_writer.log(markup! {
+                            writer.log(markup! {
                                 {formatted.as_code()}
                             });
                         } else {
                             let code = root.to_string();
-                            console_reporter_writer.log(markup! {
+                            writer.log(markup! {
                                 {code}
                             });
                         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
CliReporterKind::Json | CliReporterKind::JsonPretty => {
console_reporter_writer.error(markup! {
<Warn>"The "<Emphasis>"--json"</Emphasis>" option is "<Underline>"unstable/experimental"</Underline>" and its output might change between patches/minor releases."</Warn>
});
let reporter = JsonReporter {
summary,
diagnostics_payload: &diagnostics_payload,
execution,
verbose: cli_options.verbose,
working_directory: fs.working_directory().clone(),
};
let mut buffer = JsonReporterVisitor::new(summary);
reporter.write(&mut console_reporter_writer, &mut buffer)?;
let root = buffer.to_json();
if cli_reporter.kind == CliReporterKind::JsonPretty {
let formatted = biome_json_formatter::format_node(
JsonFormatOptions::default(),
root.syntax(),
)
.expect("To format the JSON report")
.print()
.expect("To print the JSON report");
console_reporter_writer.log(markup! {
{formatted.as_code()}
});
} else {
let code = root.to_string();
console_reporter_writer.log(markup! {
{code}
});
}
}
CliReporterKind::Json | CliReporterKind::JsonPretty => {
console_reporter_writer.error(markup! {
<Warn>"The "<Emphasis>"--json"</Emphasis>" option is "<Underline>"unstable/experimental"</Underline>" and its output might change between patches/minor releases."</Warn>
});
let reporter = JsonReporter {
summary,
diagnostics_payload: &diagnostics_payload,
execution,
verbose: cli_options.verbose,
working_directory: fs.working_directory().clone(),
};
let mut buffer = JsonReporterVisitor::new(summary);
let writer: &mut dyn ReporterWriter = if cli_reporter.is_file_report() {
&mut file_reporter_writer
} else {
&mut console_reporter_writer
};
reporter.write(writer, &mut buffer)?;
let root = buffer.to_json();
if cli_reporter.kind == CliReporterKind::JsonPretty {
let formatted = biome_json_formatter::format_node(
JsonFormatOptions::default(),
root.syntax(),
)
.expect("To format the JSON report")
.print()
.expect("To print the JSON report");
writer.log(markup! {
{formatted.as_code()}
});
} else {
let code = root.to_string();
writer.log(markup! {
{code}
});
}
}
🤖 Prompt for AI Agents
In `@crates/biome_cli/src/runner/impls/finalizers/default.rs` around lines 111 -
143, The JSON branch currently always writes to console_reporter_writer so file
destinations are ignored; update the CliReporterKind::Json / JsonPretty handling
to detect cli_reporter.is_file_report() and use file_reporter_writer (instead of
console_reporter_writer) when true: construct the JsonReporter and
JsonReporterVisitor as now, call reporter.write(...) with the chosen writer and
buffer, obtain root from buffer.to_json(), then either format/print or to_string
and call file_reporter_writer.log(...) (and ensure file_reporter_writer.dump()
is invoked where needed) so both pretty and non‑pretty JSON end up in the file
output rather than only the console.

Comment on lines +45 to +62
if let Some(fix_mode) = execution.as_fix_file_mode() {
let suppression_explanation =
if execution.suppress() && execution.suppression_reason().is_none() {
"ignored using `--suppress`"
} else {
execution.suppression_reason().unwrap_or("<explanation>")
};

let fix_result = workspace_file
.guard()
.fix_file(
fix_mode,
features_supported.supports_format(),
categories,
only.clone(),
skip.clone(),
Some(suppression_explanation.to_string()),
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Only pass a suppression reason when suppression is enabled.
Right now suppression_explanation is always Some, which can trigger suppression actions or embed “” even when suppression isn’t requested.

Suggested fix
-            let suppression_explanation =
-                if execution.suppress() && execution.suppression_reason().is_none() {
-                    "ignored using `--suppress`"
-                } else {
-                    execution.suppression_reason().unwrap_or("<explanation>")
-                };
+            let suppression_explanation = if execution.suppress() {
+                Some(
+                    execution
+                        .suppression_reason()
+                        .unwrap_or("ignored using `--suppress`")
+                        .to_string(),
+                )
+            } else {
+                None
+            };
...
-                    Some(suppression_explanation.to_string()),
+                    suppression_explanation,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if let Some(fix_mode) = execution.as_fix_file_mode() {
let suppression_explanation =
if execution.suppress() && execution.suppression_reason().is_none() {
"ignored using `--suppress`"
} else {
execution.suppression_reason().unwrap_or("<explanation>")
};
let fix_result = workspace_file
.guard()
.fix_file(
fix_mode,
features_supported.supports_format(),
categories,
only.clone(),
skip.clone(),
Some(suppression_explanation.to_string()),
)
if let Some(fix_mode) = execution.as_fix_file_mode() {
let suppression_explanation = if execution.suppress() {
Some(
execution
.suppression_reason()
.unwrap_or("ignored using `--suppress`")
.to_string(),
)
} else {
None
};
let fix_result = workspace_file
.guard()
.fix_file(
fix_mode,
features_supported.supports_format(),
categories,
only.clone(),
skip.clone(),
suppression_explanation,
)
🤖 Prompt for AI Agents
In `@crates/biome_cli/src/runner/impls/process_file/lint_and_assist.rs` around
lines 45 - 62, The code always constructs Some(suppression_explanation) and
passes it to workspace_file.guard().fix_file(...), which causes suppression
metadata to be applied even when execution.suppress() is false; change the logic
to build an Option<String> that is None when suppression is not enabled and
Some(reason) only when execution.suppress() is true (use
execution.suppression_reason().map(|s| s.to_string()).or_else(|| Some("ignored
using `--suppress`".to_string())) when suppressed), then pass that Option
directly to fix_file instead of wrapping an unconditional Some(...).

Comment on lines +183 to +221
workspace.open_file(OpenFileParams {
project_key,
path: biome_path.clone(),
content: FileContent::from_client(content),
document_file_source: None,
persist_node_cache: false,
inline_config: None,
})?;

// apply fix file of the linter
let FileFeaturesResult {
features_supported: file_features,
} = workspace.file_features(SupportsFeatureParams {
project_key,
path: biome_path.clone(),
features: payload.execution.wanted_features(),
inline_config: None,
skip_ignore_check,
not_requested_features: payload.execution.not_requested_features(),
})?;

if file_features.is_ignored() {
console.append(markup! {{content}});
return Ok(());
}

if file_features.is_protected() {
let protected_diagnostic = WorkspaceError::protected_file(biome_path.to_string());
if protected_diagnostic.tags().is_verbose() {
if cli_options.verbose {
console.error(markup! {{PrintDiagnostic::verbose(&protected_diagnostic)}})
}
} else {
console.error(markup! {{PrintDiagnostic::simple(&protected_diagnostic)}})
}
console.append(markup! {{content}});

return Ok(());
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Close the workspace file on early exits.
open_file is called, but ignored/protected paths return without close_file, leaving an open document behind.

Suggested fix
         workspace.open_file(OpenFileParams {
             project_key,
             path: biome_path.clone(),
             content: FileContent::from_client(content),
             document_file_source: None,
             persist_node_cache: false,
             inline_config: None,
         })?;
+        let close = || {
+            let _ = workspace.close_file(CloseFileParams {
+                project_key,
+                path: biome_path.clone(),
+            });
+        };
...
         if file_features.is_ignored() {
             console.append(markup! {{content}});
+            close();
             return Ok(());
         }
...
         if file_features.is_protected() {
             let protected_diagnostic = WorkspaceError::protected_file(biome_path.to_string());
             if protected_diagnostic.tags().is_verbose() {
                 if cli_options.verbose {
                     console.error(markup! {{PrintDiagnostic::verbose(&protected_diagnostic)}})
                 }
             } else {
                 console.error(markup! {{PrintDiagnostic::simple(&protected_diagnostic)}})
             }
             console.append(markup! {{content}});
+            close();
 
             return Ok(());
         };
🤖 Prompt for AI Agents
In `@crates/biome_cli/src/runner/impls/process_file/lint_and_assist.rs` around
lines 183 - 221, open_file is called on workspace but early returns for ignored
or protected files (branches using file_features.is_ignored() and
file_features.is_protected()) never call close_file, leaving documents open;
before each return in those branches, call workspace.close_file(CloseFileParams
{ project_key, path: biome_path.clone() }) (or the crate's equivalent close_file
API) to ensure the file is closed, and do this both for the ignored branch and
for the protected branch right after logging/console operations so the
open/close pairing is preserved even on early exits.

Comment on lines +274 to +306
if file_features.supports_format() && execution.is_check() {
let printed = workspace.format_file(FormatFileParams {
project_key,
path: biome_path.clone(),
inline_config: None,
})?;
let code = printed.into_code();
let output = if !file_features.supports_full_html_support() {
match biome_path.extension() {
Some("astro") => AstroFileHandler::output(&new_content, code.as_str()),
Some("vue") => VueFileHandler::output(&new_content, code.as_str()),
Some("svelte") => SvelteFileHandler::output(&new_content, code.as_str()),
_ => code,
}
} else {
code
};
if (execution.is_safe_fixes_enabled() || execution.is_safe_and_unsafe_fixes_enabled())
&& output != new_content
{
new_content = Cow::Owned(output);
}
}

match new_content {
Cow::Borrowed(original_content) => {
console.append(markup! {
{original_content}
});

if !execution.requires_write_access() {
return Err(StdinDiagnostic::new_not_formatted().into());
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don’t error on stdin when formatting didn’t change.
NotFormatted is returned whenever content is unchanged and write isn’t allowed, even if formatting would produce identical output.

Suggested fix
-        if file_features.supports_format() && execution.is_check() {
+        let mut format_changed = false;
+        if file_features.supports_format() && execution.is_check() {
             let printed = workspace.format_file(FormatFileParams {
                 project_key,
                 path: biome_path.clone(),
                 inline_config: None,
             })?;
             let code = printed.into_code();
             let output = if !file_features.supports_full_html_support() {
                 match biome_path.extension() {
                     Some("astro") => AstroFileHandler::output(&new_content, code.as_str()),
                     Some("vue") => VueFileHandler::output(&new_content, code.as_str()),
                     Some("svelte") => SvelteFileHandler::output(&new_content, code.as_str()),
                     _ => code,
                 }
             } else {
                 code
             };
+            if output != new_content {
+                format_changed = true;
+            }
             if (execution.is_safe_fixes_enabled() || execution.is_safe_and_unsafe_fixes_enabled())
                 && output != new_content
             {
                 new_content = Cow::Owned(output);
             }
         }
...
             Cow::Borrowed(original_content) => {
                 console.append(markup! {
                     {original_content}
                 });
 
-                if !execution.requires_write_access() {
+                if format_changed && !execution.requires_write_access() {
                     return Err(StdinDiagnostic::new_not_formatted().into());
                 }
             }
🤖 Prompt for AI Agents
In `@crates/biome_cli/src/runner/impls/process_file/lint_and_assist.rs` around
lines 274 - 306, The code currently returns StdinDiagnostic::new_not_formatted()
whenever new_content is borrowed and execution.requires_write_access() is false,
which wrongfully errors on stdin even when formatting would produce identical
output; change the logic so we only return the NotFormatted error if the
formatter (workspace.format_file / the computed output) would have produced a
different string than original_content. Concretely: after computing output (the
formatted string) and before possibly replacing new_content, record whether
formatting_changed = output != original_content (or compare the formatter result
or transformed code that would be written), and in the match arm for
Cow::Borrowed(original_content) only return
Err(StdinDiagnostic::new_not_formatted().into()) when
!execution.requires_write_access() && formatting_changed (or other
transformations would have changed), otherwise proceed without error. Ensure you
reference the existing variables execution, output, new_content,
original_content, workspace.format_file and StdinDiagnostic::new_not_formatted()
when making the change.

Comment on lines +235 to +270
let open_options = OpenOptions::default()
.read(true)
.write(ctx.execution().requires_write_access());

let mut file = ctx
.fs()
.open_with_options(path.as_path(), open_options)
.with_file_path(path.to_string())?;

let mut input = String::new();
file.read_to_string(&mut input)
.with_file_path(path.to_string())?;

let guard = FileGuard::new(ctx.workspace(), ctx.project_key(), path.clone())
.with_file_path_and_code(path.to_string(), category!("internalError/fs"))?;

if ctx.workspace().file_exists(FileExitsParams {
file_path: path.clone(),
})? {
Ok(Self { guard, path, file })
} else {
let mut input = String::new();
file.read_to_string(&mut input)
.with_file_path(path.to_string())?;

ctx.workspace().open_file(OpenFileParams {
project_key: ctx.project_key(),
document_file_source: None,
path: path.clone(),
content: FileContent::from_client(&input),
persist_node_cache: false,
inline_config: None,
})?;

Ok(Self { guard, path, file })
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Avoid reading the file twice before open_file.
The second read runs at EOF, so newly opened workspace files can be initialised with empty content.

Suggested fix
-        let mut input = String::new();
-        file.read_to_string(&mut input)
-            .with_file_path(path.to_string())?;
-
         let guard = FileGuard::new(ctx.workspace(), ctx.project_key(), path.clone())
             .with_file_path_and_code(path.to_string(), category!("internalError/fs"))?;
🤖 Prompt for AI Agents
In `@crates/biome_cli/src/runner/process_file.rs` around lines 235 - 270, The code
reads the file into `input` once, then in the `else` branch re-reads
`file.read_to_string(&mut input)` which runs at EOF and yields empty content;
remove the redundant second read and reuse the earlier `input` when calling
`ctx.workspace().open_file` (keep `OpenOptions`, `file`, `input`, `FileGuard`,
`ctx.workspace().file_exists`, `OpenFileParams`, and `FileContent::from_client`
as-is), ensuring the content passed to `open_file` is the initially read
`input`.

…when applying suppressions (#9059)

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
crates/biome_cli/src/reporter/gitlab.rs (1)

160-166: ⚠️ Potential issue | 🟡 Minor

Verbose filter appears inverted.

When verbose is true, this only includes diagnostics with the verbose tag, excluding non-verbose ones. Typically verbose mode should show more diagnostics (all including verbose-tagged), not fewer.

Expected behaviour
             .filter(|d| {
-                if *verbose {
-                    d.tags().is_verbose()
-                } else {
-                    true
-                }
+                // Include all diagnostics, but skip verbose-tagged ones unless verbose mode is on
+                !d.tags().is_verbose() || *verbose
             })
crates/biome_cli/src/reporter/summary.rs (1)

170-198: ⚠️ Potential issue | 🟠 Major

Avoid double-counting verbose lint diagnostics. When --verbose is on, verbose lint diagnostics are inserted in the verbose branch and again in the general lint block, inflating counts. Suggest gating once and letting the lint block handle the insertion.

🔧 Suggested fix
-                if diagnostic.tags().is_verbose() {
-                    if verbose {
-                        if let Some(category) = category
-                            && should_report_lint_diagnostic(category)
-                        {
-                            files_to_diagnostics.insert_rule_for_file(
-                                category.name(),
-                                severity,
-                                location,
-                            );
-                        }
-                    } else {
-                        continue;
-                    }
-                }
+                if diagnostic.tags().is_verbose() && !verbose {
+                    continue;
+                }
-                if (execution.is_check() || execution.is_lint() || execution.is_ci())
-                    && let Some(category) = category
-                    && should_report_lint_diagnostic(category)
-                {
+                if (execution.is_check() || execution.is_lint() || execution.is_ci())
+                    && let Some(category) = category
+                    && should_report_lint_diagnostic(category)
+                {
                     files_to_diagnostics.insert_rule_for_file(category.name(), severity, location);
                 }
🤖 Fix all issues with AI agents
In @.changeset/bright-foxes-glow.md:
- Around line 1-3: The changeset front matter currently marks the release type
as "minor" which conflicts with main-branch guidance; update the front matter in
.changeset/bright-foxes-glow.md to use "patch" if this PR is targeting main, or
keep "minor" only if you confirm this changeset will remain on the next release
branch; ensure the YAML key "@biomejs/biome": minor is changed to
"@biomejs/biome": patch when targeting main and commit that change.

In @.changeset/tricky-masks-cry.md:
- Around line 1-5: The changeset for "@biomejs/biome" declares a "minor" release
but the PR targets main; either update the changeset change type from "minor" to
"patch" or retarget this PR to the next branch; confirm whether the change is a
user-facing new feature (keep "minor" and move the branch to next) or a
non-breaking bugfix (change the header to "patch") and update the changeset
accordingly so it matches the repository release policy.

In `@crates/biome_cli/src/reporter/json.rs`:
- Around line 387-407: The bug is that SuggestionsVisitor::record_log overwrites
previous suggestion text because self.last_diagnostic_length is never updated;
after you append to the last suggestion or otherwise mutate suggestions you
should set self.last_diagnostic_length to the current number of diagnostics so
future logs branch correctly. In the record_log method
(SuggestionsVisitor::record_log) update self.last_diagnostic_length =
current_diagnostic_length immediately after the block that mutates
self.suggestions (i.e., after the last_suggestion.text = message assignment),
and ensure any code path that creates a new suggestion or consumes
current_message also updates last_diagnostic_length accordingly so subsequent
calls don't clobber earlier entries.

In `@crates/biome_cli/src/reporter/terminal.rs`:
- Around line 154-159: The code currently falls through and prints
verbose-tagged diagnostics even when verbose is false; update the conditional in
the loop that checks diagnostic.severity() so that verbose-tagged diagnostics
are only printed when verbose is true: if diagnostic.tags().is_verbose() then
print PrintDiagnostic::verbose(diagnostic) when verbose is true and otherwise
skip that diagnostic (do not fall through to PrintDiagnostic::simple), else
print PrintDiagnostic::simple(diagnostic). Locate the branch around
diagnostic.severity(), diagnostic.tags().is_verbose(), writer.error and
PrintDiagnostic::verbose/simple and implement the explicit skip (e.g., continue)
for non-verbose context.

In `@crates/biome_cli/src/runner/mod.rs`:
- Around line 264-303: The code reads stdin twice which can consume the stream
and make the second read empty; update configure_workspace/ConfiguredWorkspace
to capture and return an optional Stdin payload (the result of
self.get_stdin/Console::read) so the workspace carries the already-read stdin,
then change the branch that currently calls self.get_stdin(console,
execution.as_ref()) to use the stored stdin from ConfiguredWorkspace; pass that
stored stdin into ProcessFile::process_std_in via ProcessStdinFilePayload (and
still call ProcessFile::should_skip_ignore_check with the biome_path and
workspace) so stdin is only read once and reused for scan-kind derivation and
processing.
🟡 Minor comments (20)
.claude/skills/prettier-compare/SKILL.md-44-44 (1)

44-44: ⚠️ Potential issue | 🟡 Minor

Add a comma for readability.

Suggested tweak: “Use -l/--language when formatting code without an extension, so both formatters pick the correct parser.”

.changeset/ninety-rice-like.md-5-5 (1)

5-5: ⚠️ Potential issue | 🟡 Minor

Changeset description must follow the “Fixed [#NUMBER](issue link): …” format.

This is a bug fix, so please prefix the description accordingly. Short and user‑focused is already on the right track.
As per coding guidelines: “For bug fixes, start with 'Fixed [#NUMBER](issue link): ...'.”

.changeset/group-by-nesting-feature.md-44-44 (1)

44-44: ⚠️ Potential issue | 🟡 Minor

Fix the syntax error in the code example.

Missing space before the opening brace.

🔧 Proposed fix
-const object ={
+const object = {
crates/biome_cli/tests/cases/editorconfig.rs-702-738 (1)

702-738: ⚠️ Potential issue | 🟡 Minor

Expectation mismatch: “should_support” yet asserts error.

With insert_final_newline = false, formatting should succeed (and avoid adding a newline) rather than erroring. Please align the assertion (or rename the test if the failure is intentional).

Suggested fix
-    assert!(result.is_err(), "run_cli returned {result:?}");
+    assert!(result.is_ok(), "run_cli returned {result:?}");
.changeset/silly-walls-build.md-7-8 (1)

7-8: ⚠️ Potential issue | 🟡 Minor

Add a comma after “Now” for readability.
Reads more naturally as “Now, variables and components…”.

.changeset/silly-walls-build.md-5-11 (1)

5-11: ⚠️ Potential issue | 🟡 Minor

Change useImportTypes to useImportType.

The canonical rule name is useImportType (singular), not useImportTypes. Update line 6 to match.

.changeset/proud-ends-hear.md-11-21 (1)

11-21: ⚠️ Potential issue | 🟡 Minor

Invalid JavaScript in the example code.

The example declares const a three times, which would cause a redeclaration error in JavaScript. Consider using unique variable names to demonstrate the suppression feature with valid code.

📝 Proposed fix with unique variable names
 ```js
 // biome-ignore-all format: generated
 
-const a = [  ]
+const arr1 = [  ]
 
 
-const a = [  ]
+const arr2 = [  ]
 
 
-const a = [  ]
+const arr3 = [  ]
</details>

</blockquote></details>
<details>
<summary>.changeset/angry-women-accept.md-5-5 (1)</summary><blockquote>

`5-5`: _⚠️ Potential issue_ | _🟡 Minor_

**Fix grammar: "will a emit" → "will emit an".**

The phrase "will a emit" is grammatically incorrect.



<details>
<summary>📝 Proposed fix</summary>

```diff
-The Biome CSS parser is now able to parse Vue SFC syntax such as `:slotted` and `:deep`. These pseudo functions are only correctly parsed when the CSS is defined inside `.vue` components. Otherwise, Biome will a emit a parse error.
+The Biome CSS parser is now able to parse Vue SFC syntax such as `:slotted` and `:deep`. These pseudo functions are only correctly parsed when the CSS is defined inside `.vue` components. Otherwise, Biome will emit an parse error.
.claude/skills/README.md-30-30 (1)

30-30: ⚠️ Potential issue | 🟡 Minor

Line count estimate seems a bit generous.

The entry claims ~320 lines, but the actual file is 265 lines. Might want to adjust to ~265 for accuracy.

📏 Suggested correction
-| **[biome-developer](./biome-developer/SKILL.md)** | General development best practices, common gotchas, Biome-specific patterns | Any agent | ~320 |
+| **[biome-developer](./biome-developer/SKILL.md)** | General development best practices, common gotchas, Biome-specific patterns | Any agent | ~265 |
.changeset/rich-flowers-hunt.md-5-5 (1)

5-5: ⚠️ Potential issue | 🟡 Minor

Fix the documentation URL—it includes "selector" when it shouldn't.

The URL should be no-unknown-pseudo-element, not no-unknown-selector-pseudo-element. See: https://biomejs.dev/linter/rules/no-unknown-pseudo-element/

crates/biome_cli/src/panic.rs-35-43 (1)

35-43: ⚠️ Potential issue | 🟡 Minor

Tidy up a small grammar hiccup.
User-facing copy reads a bit off.

✏️ Suggested fix
-        "This is a bug in Biome, not an error in your code, and we would appreciate it if you could report it to https://github.com/biomejs/biome/issues/ along with the following information to help us fixing the issue."
+        "This is a bug in Biome, not an error in your code, and we would appreciate it if you could report it to https://github.com/biomejs/biome/issues/ along with the following information to help us fix the issue."
.changeset/chubby-buttons-lie.md-5-5 (1)

5-5: ⚠️ Potential issue | 🟡 Minor

Fix the wording about exporting the env var.
You export RUST_BACKTRACE in your shell environment, not $PATH.

✍️ Proposed wording
-It's now possible to provide the stacktrace for a fatal error. The stacktrace is only available when the environment variable `RUST_BACKTRACE=1` is set, either via the CLI or exported `$PATH`. This is useful when providing detailed information for debugging purposes:
+It's now possible to provide the stacktrace for a fatal error. The stacktrace is only available when the environment variable `RUST_BACKTRACE=1` is set, either inline on the CLI or exported in your shell environment. This is useful when providing detailed information for debugging purposes:
.changeset/full-berries-follow.md-5-41 (1)

5-41: ⚠️ Potential issue | 🟡 Minor

Include a before/after diff snippet for the formatting change.
That makes the formatter impact obvious at a glance.

✍️ Suggested diff-style example
+```diff
-const PeopleCountQuery = gql`query PeopleCount { allPeople { totalCount } }`;
+const PeopleCountQuery = gql`
+  query PeopleCount {
+    allPeople {
+      totalCount
+    }
+  }
+`;
+```

Based on learnings, "For formatter changes in changesets, show formatting changes using diff code blocks".

.changeset/tiny-dodos-ask.md-5-5 (1)

5-5: ⚠️ Potential issue | 🟡 Minor

Use the required bug-fix prefix with an issue link.
Please follow the standard format for bug fixes.

✍️ Template update (replace with the real issue)
-Fixed an issue where some info diagnostics weren't tracked by the final summary.
+Fixed [`#NNNN`](https://github.com/biomejs/biome/issues/NNNN): some info diagnostics weren't tracked by the final summary.

As per coding guidelines, "For bug fixes, start with 'Fixed [#NUMBER](issue link): ...'".

.changeset/eight-bars-unite.md-5-5 (1)

5-5: ⚠️ Potential issue | 🟡 Minor

Add an invalid example for the new lint rule.
Please include a short invalid-case snippet so users immediately see what triggers the rule.

✍️ Proposed addition
 Added the rule [`useValidLang`](https://biomejs.dev/linter/rules/use-valid-lang) to the HTML language.
+
+**Invalid example**
+
+```html
+<html lang="invalid"></html>
+```

Based on learnings, "For new lint rules in changesets, show an example of invalid case in inline code or code block".

.changeset/tangy-dingos-design.md-5-5 (1)

5-5: ⚠️ Potential issue | 🟡 Minor

Minor grammatical correction needed.

The phrasing "the order how Biome will attempt the configuration file is" reads awkwardly.

📝 Suggested fix
-Added the ability to load the hidden files `.biome.json` and `.biome.jsonc`. This is the order how Biome will attempt the configuration file is:
+Added the ability to load the hidden files `.biome.json` and `.biome.jsonc`. The order in which Biome attempts to load configuration files is:
crates/biome_cli/tests/cases/reporter_sarif.rs-172-195 (1)

172-195: ⚠️ Potential issue | 🟡 Minor

Test name/command mismatch. The case says “check command file” but invokes format, so check+reporter‑file isn’t actually covered. I’d switch the CLI verb to check (or rename the test if format was intended).

✏️ Proposed fix
-                "format",
+                "check",
crates/biome_analyze/src/shared/class_dedup.rs-25-63 (1)

25-63: ⚠️ Potential issue | 🟡 Minor

Type mismatch between find_duplicate_classes and duplicate_classes_diagnostic.

find_duplicate_classes returns duplicates: Vec<&'a str> but duplicate_classes_diagnostic expects duplicates: &[Box<str>]. Callers will need to convert the borrowed slices to boxed strings before calling the diagnostic function, which seems like unnecessary allocation.

Consider aligning the signatures:

Option A: Accept borrowed slices in diagnostic
 pub fn duplicate_classes_diagnostic(
     category: &'static Category,
     span: impl AsSpan,
-    duplicates: &[Box<str>],
+    duplicates: &[&str],
 ) -> RuleDiagnostic {
Option B: Use a generic bound
 pub fn duplicate_classes_diagnostic(
     category: &'static Category,
     span: impl AsSpan,
-    duplicates: &[Box<str>],
+    duplicates: &[impl AsRef<str>],
 ) -> RuleDiagnostic {
crates/biome_cli/src/runner/impls/commands/traversal.rs-120-120 (1)

120-120: ⚠️ Potential issue | 🟡 Minor

Typo in doc comment.

CommanderRunner should be CommandRunner.

📝 Suggested fix
-    /// Alias of [CommanderRunner::get_files_to_process]
+    /// Alias of [CommandRunner::get_files_to_process]
crates/biome_cli/src/runner/impls/commands/traversal.rs-65-69 (1)

65-69: ⚠️ Potential issue | 🟡 Minor

Doc comment doesn't match the item.

The comment states "A trait that returns a [TraverseResult]" but this is a struct, not a trait.

📝 Suggested fix
-/// A trait that returns a [TraverseResult] from a traversal command.
+/// A wrapper struct that adapts a [TraversalCommand] to [CommandRunner].
 pub(crate) struct TraversalCommandImpl<C, P>(pub C)
🧹 Nitpick comments (28)
.changeset/smooth-dots-swim.md (2)

5-16: Make the changeset description more user‑focused.

Right now it reads like internal plumbing. Consider framing it around user impact (what to enable, why they care) in 1–3 sentences.

Suggested rewrite
-Added the new linter domain `types`. This is a domain that enables all rules that require the type inference engine.
-
-As opposed to the `project` domain, which only enables rules that require the module graph to function.
-
-The following **nursery** rules have been moved to the `types` domain:
+Added a new `types` domain so you can opt into type‑inference‑based rules only when you want them. Several **nursery** rules now live under `types`, so enable that domain to run them:
 - `useArraySortCompare`
 - `useAwaitThenable`
 - `useFind`
 - `useRegexpExec`
 - `noUnnecessaryConditions`
 - `noMisusedPromises`
 - `noFloatingPromises`

9-16: If any moved rules rely on default severity, mention it here.

If these rules don’t explicitly set severity, please add a short note that the default is Severity::Information to avoid confusion. Based on learnings "In changeset markdown files (e.g., .changeset/*), document that when a lint rule does not explicitly set the severity in declare_lint_rule!, the default severity is Severity::Information (RuleMetadata::default()), not Severity::Error."

.changeset/fix-diagnostic-offset-in-embedded-files.md (1)

5-5: Consider splitting the description for improved readability.

Whilst technically compliant, the 82-word sentence could be split into two for easier scanning. For example:

"Fixed #9057: Incorrect diagnostic spans for suppression comments and other raw diagnostics in HTML-ish files (Vue, Svelte, Astro). Previously, diagnostics like "unused suppression" pointed to the wrong location due to the diagnostic offset not being applied."

As per coding guidelines, changeset descriptions should be concise (1-3 sentences) and user-focused.

.changeset/bright-foxes-glow.md (1)

5-30: Trim the changeset to 1–3 user‑focused sentences.

This reads like mini‑docs (bullets + examples). The guideline asks for concise, end‑user impact, not how‑to content. As per coding guidelines, “Write changeset descriptions for end users, explaining impact and why they care, not implementation details. Be concise (1–3 sentences).”

.changeset/solid-maps-relax.md (1)

5-8: Consider rephrasing for a more user-focused description.

The description is clear and concise, but could be slightly more user-focused. Consider: "The noSvgWithoutTitle rule now supports graphics-document and graphics-symbol roles, as well as multiple role specifications." This emphasises the benefit to users rather than the implementation action.

✍️ Suggested rewording
-Added two new behaviors to the `noSvgWithoutTitle` rule.
+The `noSvgWithoutTitle` rule now supports additional accessibility roles and configurations:

-- Support for `graphics-document` and `graphics-symbol` roles.
-- Support for multiple role specifications.
+- `graphics-document` and `graphics-symbol` roles
+- Multiple role specifications

As per coding guidelines: changeset descriptions should be written for end users, explaining impact and why they care, not implementation details.

.changeset/witty-coats-serve.md (1)

5-5: Consider mentioning accessibility benefits.

The description is concise and clear, but could be more user-focused by explaining why this rule matters. SVG title elements improve accessibility for screen reader users.

✨ Suggested enhancement
-Added the [`noSvgWithoutTitle`](https://biomejs.dev/linter/rules/no-svg-without-title/) lint rule to HTML. The rule enforces the usage of the `title` element for the `svg` element.
+Added the [`noSvgWithoutTitle`](https://biomejs.dev/linter/rules/no-svg-without-title/) lint rule to HTML. The rule enforces the usage of the `title` element for the `svg` element to improve accessibility for screen reader users.

As per coding guidelines: "Write changeset descriptions for end users, explaining impact and why they care, not implementation details."

.gitignore (1)

19-19: LGTM – consider broader pattern for consistency.

The pattern works, though it's more specific than similar entries. If you want to ignore the entire .claude/ directory (like .vscode/* and .zed/* above), consider /.claude/* instead.

.changeset/calm-goats-talk.md (1)

5-5: Make the description explicitly user‑focused (why it matters).

Consider adding a short impact statement, e.g. accessibility benefits or clearer embeds, rather than just “adds rule”.
As per coding guidelines: “Write changeset descriptions for end users, explaining impact and why they care.”

.changeset/petite-mice-say.md (1)

5-5: Minor formatting: Remove backticks from the issue reference.

The issue reference uses backticks inside the markdown link: [#7912]. Standard changeset format is [#7912] without the backticks.

Suggested adjustment
-Fixed [`#7912`](https://github.com/biomejs/biome/issues/7912), where Biome incorrectly added a leading newline to the code contained inside the Astro frontmatter.
+Fixed [`#7912`](https://github.com/biomejs/biome/issues/7912), where Biome incorrectly added a leading newline to the code contained inside the Astro frontmatter.

As per coding guidelines: Create changeset files with YAML front matter and user-focused descriptions. For bug fixes, start with 'Fixed [#NUMBER](issue link): ...'.

.changeset/proud-ends-hear.md (1)

11-21: Consider using a diff block to show the formatting suppression.

Based on learnings, formatter changes in changesets should demonstrate formatting changes using diff code blocks. A before/after comparison would more clearly illustrate how the suppression prevents formatting.

♻️ Example with diff showing suppressed formatting

Replace the current code block with:

Without the suppression comment, Biome would format the code like this:

```diff
-const arr1 = [  ]
+const arr1 = []

-const arr2 = [  ]
+const arr2 = []
```

With `biome-ignore-all format: generated`, the original spacing is preserved:

```js
// biome-ignore-all format: generated

const arr1 = [  ]
const arr2 = [  ]
```
.changeset/no-duplicate-classes-rule.md (1)

5-17: Consider condensing the description slightly.

The changeset is clear and informative, but the guidelines recommend 1-3 sentences for conciseness. The current version has four sentences plus a code example. As per coding guidelines, changeset descriptions should be concise whilst explaining impact.

You might condense it to something like:

Added the [`noDuplicateClasses`](https://biomejs.dev/assist/actions/no-duplicate-classes/) assist action to detect and remove duplicate CSS classes. Supports JSX (`class`, `className`, and utility functions like `clsx`, `cn`, `cva`) and HTML (`class` attributes) — the first assist action for HTML.

```jsx
// Before
<div class="flex p-4 flex" />;

// After
<div class="flex p-4" />;

That said, your current version provides valuable context about the scope across JSX and HTML, so this is more of a style refinement.

</blockquote></details>
<details>
<summary>.changeset/angry-women-accept.md (1)</summary><blockquote>

`5-5`: **Consider "can parse" instead of "is now able to parse" for brevity.**

As a minor style improvement, "can now parse" is more concise than "is now able to parse".



<details>
<summary>✍️ Optional style improvement</summary>

```diff
-The Biome CSS parser is now able to parse Vue SFC syntax such as `:slotted` and `:deep`.
+The Biome CSS parser can now parse Vue SFC syntax such as `:slotted` and `:deep`.
crates/biome_cli/src/runner/process_file.rs (1)

64-69: Consider removing or documenting commented-out code.

If DiagnosticsWithActions is planned for future use, a TODO comment would clarify intent. Otherwise, removing dead code keeps the codebase tidy.

.changeset/smooth-clocks-change.md (1)

5-5: Consider highlighting the user benefit.

The description is functional, but could emphasise why this matters to users—improved accessibility, better UX, or alignment with modern web standards.

✨ Optional enhancement
-Added the `noDistractingElements` lint rule for HTML. The rule enforces that no distracting elements like `<marquee>` or `<blink>` are used.
+Added the `noDistractingElements` lint rule for HTML to improve accessibility and user experience by disallowing distracting elements like `<marquee>` and `<blink>`.
.changeset/silent-turtles-walk.md (1)

5-5: Add documentation link for the rule.

As per coding guidelines, rules should include links to their documentation. Consider adding the link like other changesets in this PR.

📝 Suggested fix
-Added the `useAriaPropsForRole` lint rule for HTML. The rule enforces that elements with ARIA roles must have all required ARIA attributes for that role.
+Added the [`useAriaPropsForRole`](https://biomejs.dev/linter/rules/use-aria-props-for-role/) lint rule for HTML. The rule enforces that elements with ARIA roles must have all required ARIA attributes for that role.
.github/workflows/benchmark_html.yml (1)

10-18: Minor path filter redundancy.

The glob pattern crates/biome_html_*/**/*.rs (lines 15, 28) already covers the specific paths for biome_html_analyze, biome_html_formatter, and biome_html_parser listed above it. These explicit paths are technically redundant.

That said, keeping them explicit may aid readability by showing exactly which crates are benchmarked. Your call on whether to simplify.

Also applies to: 23-31

crates/biome_cli/src/runner/handler.rs (1)

142-155: Consider counting panics as skipped files.
The panic branch emits a diagnostic but doesn’t update skipped/unchanged counters, so summary stats can under‑report. Mirroring the Err path would keep totals consistent.

💡 Suggested tweak
             Err(err) => {
                 let message = match err.downcast::<String>() {
                     Ok(msg) => format!("processing panicked: {msg}"),
                     Err(err) => match err.downcast::<&'static str>() {
                         Ok(msg) => format!("processing panicked: {msg}"),
                         Err(_) => String::from("processing panicked"),
                     },
                 };

                 ctx.push_message(
                     PanicDiagnostic { message }
                         .with_file_path(biome_path.to_string())
                         .into(),
                 );
+                ctx.increment_unchanged();
+                ctx.increment_skipped();
             }
.changeset/all-pumas-stop.md (1)

31-31: Minor grammar nit.

The word "otherwise" is an adverb and reads better when separated from the sentence. Consider:

Suggested fix
-**The `--reporter` and `--reporter-file` flags must appear next to each other, otherwise an error is thrown.**
+**The `--reporter` and `--reporter-file` flags must appear next to each other; otherwise, an error is thrown.**
crates/biome_analyze/src/analyzer_plugin.rs (1)

105-109: Consider using the plugin's actual name for profiling.

The timer label "plugin" is generic. For more useful profiling data, consider extracting the plugin's name or identifier. However, if all plugin rules should aggregate under one label, this is fine as-is.

crates/biome_cli/tests/cases/reporter_terminal.rs (1)

141-142: Function name may be misleading.

The test is named reports_diagnostics_check_write_command_file but doesn't use the --write flag (unlike reports_diagnostics_check_write_command_verbose above it). Perhaps reports_diagnostics_check_command_reporter_file or similar would better reflect what's being tested?

crates/biome_cli/src/runner/run.rs (1)

6-14: Consider simplifying the mutable rebinding.

The let command = &mut command; on line 12 is redundant since command is already declared as mut. You could call run directly:

♻️ Optional simplification
 pub(crate) fn run_command(
     session: CliSession,
     log_options: &LogOptions,
     cli_options: &CliOptions,
     mut command: impl CommandRunner,
 ) -> Result<(), CliDiagnostic> {
-    let command = &mut command;
     command.run(session, log_options, cli_options)
 }
crates/biome_cli/src/runner/impls/crawlers/default.rs (1)

12-60: Consider using PhantomData instead of storing the value.

DefaultCrawler<P>(P) stores a value of type P, but it's never accessed - P is only used at the type level for the ProcessFile associated type. This could be DefaultCrawler<P>(std::marker::PhantomData<P>) to make the intent clearer and avoid storing an unused value.

That said, this is a minor point and doesn't affect correctness.

Optional refactor
+use std::marker::PhantomData;
+
-pub(crate) struct DefaultCrawler<P>(P);
+pub(crate) struct DefaultCrawler<P>(PhantomData<P>);
crates/biome_cli/src/runner/impls/commands/traversal.rs (1)

35-39: Editorconfig diagnostics are silently discarded.

The _editorconfig_diagnostics variable is captured but never used. If these diagnostics contain parsing warnings or errors, users won't see them.

Consider logging or propagating these diagnostics if they're meaningful.

crates/biome_cli/src/runner/impls/process_file/format.rs (2)

40-42: TODO comment left in code.

The comment // NOTE: probably to revisit on the false parameter suggests uncertainty. Consider clarifying what this boolean controls and whether it needs revisiting.

Would you like me to open an issue to track this for follow-up?


81-104: Consider extracting the HTML handler output logic.

The Astro/Vue/Svelte output transformation is duplicated in both process_file (lines 81-104) and process_std_in (lines 183-192). A helper function could reduce this duplication.

crates/biome_cli/src/commands/migrate.rs (1)

146-147: Clarify the comment - this isn't a Rust SAFETY invariant.

The // SAFETY: convention in Rust typically documents why unsafe code is sound. Here it's documenting logical correctness (that get_execution checks for Some). Consider rephrasing:

📝 Suggested fix
-            // SAFETY: checked during get_execution
+            // Invariant: get_execution returns Err if configuration_file_path is None
             configuration_file_path: self.configuration_file_path.clone().unwrap(),
crates/biome_cli/src/commands/search.rs (1)

152-153: Consider defensive handling for search_pattern.

The // SAFETY comment is reasonable, but relying on the caller always being SearchExecution is fragile if refactoring occurs. A debug assertion or expect() with a descriptive message would make failures more diagnosable.

Suggested improvement
-        // SAFETY: search_pattern is implemented in this file
-        let pattern = execution.search_pattern().unwrap();
+        let pattern = execution
+            .search_pattern()
+            .expect("SearchProcessFile requires an execution with a search pattern");
crates/biome_cli/src/reporter/github.rs (1)

54-62: Simplify redundant condition.

In the else branch, diagnostic.tags().is_verbose() is guaranteed to be true (you wouldn't reach the else otherwise), so the && verbose check can be simplified.

Proposed simplification
         for diagnostic in &diagnostics_payload.diagnostics {
             if diagnostic.severity() >= diagnostics_payload.diagnostic_level {
-                if !diagnostic.tags().is_verbose() {
-                    writer.log(markup! {{PrintGitHubDiagnostic(diagnostic)}});
-                } else if diagnostic.tags().is_verbose() && verbose {
+                let is_verbose_diag = diagnostic.tags().is_verbose();
+                if !is_verbose_diag || verbose {
                     writer.log(markup! {{PrintGitHubDiagnostic(diagnostic)}});
                 }
             }
         }

Comment on lines +1 to +3
---
"@biomejs/biome": minor
---
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Use a patch changeset on main (unless this is staying on next).

The front matter marks this as minor, which conflicts with the guidance for changesets targeting main. If this merge PR is landing on main, please switch to patch, or confirm this changeset remains only on next. As per coding guidelines, “Use 'patch' for bug fixes and non-breaking changes targeting the main branch, 'minor' for new features targeting the next branch, and 'major' for breaking changes targeting the next branch.”

🤖 Prompt for AI Agents
In @.changeset/bright-foxes-glow.md around lines 1 - 3, The changeset front
matter currently marks the release type as "minor" which conflicts with
main-branch guidance; update the front matter in .changeset/bright-foxes-glow.md
to use "patch" if this PR is targeting main, or keep "minor" only if you confirm
this changeset will remain on the next release branch; ensure the YAML key
"@biomejs/biome": minor is changed to "@biomejs/biome": patch when targeting
main and commit that change.

Comment on lines +1 to +5
---
"@biomejs/biome": minor
---

Formatting is now applied when applying safe/unsafe fixes via `biome check`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Please confirm the change type versus target branch.

This PR targets main, yet the changeset declares a minor release. Our guidance says minor is for new features targeting next, while main should be patch only. If this is a user-facing feature (it reads as such), consider keeping it in next or confirming the release policy before landing on main. As per coding guidelines: “Use 'patch' for bug fixes and non-breaking changes targeting the main branch, 'minor' for new features targeting the next branch, and 'major' for breaking changes targeting the next branch.”

🤖 Prompt for AI Agents
In @.changeset/tricky-masks-cry.md around lines 1 - 5, The changeset for
"@biomejs/biome" declares a "minor" release but the PR targets main; either
update the changeset change type from "minor" to "patch" or retarget this PR to
the next branch; confirm whether the change is a user-facing new feature (keep
"minor" and move the branch to next) or a non-breaking bugfix (change the header
to "patch") and update the changeset accordingly so it matches the repository
release policy.

Comment on lines +387 to +407
impl Visit for SuggestionsVisitor {
fn record_log(&mut self, _category: LogCategory, text: &dyn Display) -> std::io::Result<()> {
let message = {
let mut message = MarkupBuf::default();
let mut fmt = Formatter::new(&mut message);
fmt.write_markup(markup!({ { text } }))?;
markup_to_string(&message).expect("Invalid markup")
};
let current_diagnostic_length = self.suggestions.len();

if self.last_diagnostic_length != current_diagnostic_length {
let last_suggestion = self
.suggestions
.last_mut()
.expect("No suggestions to append to");
last_suggestion.text = message;
} else if let Some(current_message) = self.current_message.as_mut() {
current_message.push_str(&message);
} else {
self.current_message = Some(message);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Suggestion logs can overwrite each other. last_diagnostic_length never updates, so any log after the first frame keeps clobbering the last suggestion text and loses earlier messages.

🔧 Suggested fix
         if self.last_diagnostic_length != current_diagnostic_length {
             let last_suggestion = self
                 .suggestions
                 .last_mut()
                 .expect("No suggestions to append to");
             last_suggestion.text = message;
+            self.last_diagnostic_length = current_diagnostic_length;
         } else if let Some(current_message) = self.current_message.as_mut() {
             current_message.push_str(&message);
         } else {
             self.current_message = Some(message);
         }
         if let (Some(span), Some(source_code)) = (location.span, location.source_code) {
             let source = SourceFile::new(source_code);
             let start = source.location(span.start()).expect("Invalid span");
             let end = source.location(span.end()).expect("Invalid span");
 
             self.suggestions.push(JsonSuggestion {
@@
                 text: self.current_message.take().unwrap_or_default(),
             })
+            self.last_diagnostic_length = self.suggestions.len();
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
impl Visit for SuggestionsVisitor {
fn record_log(&mut self, _category: LogCategory, text: &dyn Display) -> std::io::Result<()> {
let message = {
let mut message = MarkupBuf::default();
let mut fmt = Formatter::new(&mut message);
fmt.write_markup(markup!({ { text } }))?;
markup_to_string(&message).expect("Invalid markup")
};
let current_diagnostic_length = self.suggestions.len();
if self.last_diagnostic_length != current_diagnostic_length {
let last_suggestion = self
.suggestions
.last_mut()
.expect("No suggestions to append to");
last_suggestion.text = message;
} else if let Some(current_message) = self.current_message.as_mut() {
current_message.push_str(&message);
} else {
self.current_message = Some(message);
}
impl Visit for SuggestionsVisitor {
fn record_log(&mut self, _category: LogCategory, text: &dyn Display) -> std::io::Result<()> {
let message = {
let mut message = MarkupBuf::default();
let mut fmt = Formatter::new(&mut message);
fmt.write_markup(markup!({ { text } }))?;
markup_to_string(&message).expect("Invalid markup")
};
let current_diagnostic_length = self.suggestions.len();
if self.last_diagnostic_length != current_diagnostic_length {
let last_suggestion = self
.suggestions
.last_mut()
.expect("No suggestions to append to");
last_suggestion.text = message;
self.last_diagnostic_length = current_diagnostic_length;
} else if let Some(current_message) = self.current_message.as_mut() {
current_message.push_str(&message);
} else {
self.current_message = Some(message);
}
🤖 Prompt for AI Agents
In `@crates/biome_cli/src/reporter/json.rs` around lines 387 - 407, The bug is
that SuggestionsVisitor::record_log overwrites previous suggestion text because
self.last_diagnostic_length is never updated; after you append to the last
suggestion or otherwise mutate suggestions you should set
self.last_diagnostic_length to the current number of diagnostics so future logs
branch correctly. In the record_log method (SuggestionsVisitor::record_log)
update self.last_diagnostic_length = current_diagnostic_length immediately after
the block that mutates self.suggestions (i.e., after the last_suggestion.text =
message assignment), and ensure any code path that creates a new suggestion or
consumes current_message also updates last_diagnostic_length accordingly so
subsequent calls don't clobber earlier entries.

Comment on lines 154 to 159
if diagnostic.severity() >= diagnostics_payload.diagnostic_level {
if diagnostic.tags().is_verbose() && verbose {
self.0
.error(markup! {{PrintDiagnostic::verbose(diagnostic)}});
writer.error(markup! {{PrintDiagnostic::verbose(diagnostic)}});
} else {
self.0
.error(markup! {{PrintDiagnostic::simple(diagnostic)}});
writer.error(markup! {{PrintDiagnostic::simple(diagnostic)}});
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Verbose-only diagnostics are always printed. The current branch prints verbose-tagged diagnostics even when verbose is false (falls through to simple). That changes CLI behaviour.

🔧 Suggested fix
-            if diagnostic.tags().is_verbose() && verbose {
-                writer.error(markup! {{PrintDiagnostic::verbose(diagnostic)}});
-            } else {
-                writer.error(markup! {{PrintDiagnostic::simple(diagnostic)}});
-            }
+            if diagnostic.tags().is_verbose() {
+                if verbose {
+                    writer.error(markup! {{PrintDiagnostic::verbose(diagnostic)}});
+                } else {
+                    continue;
+                }
+            } else {
+                writer.error(markup! {{PrintDiagnostic::simple(diagnostic)}});
+            }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if diagnostic.severity() >= diagnostics_payload.diagnostic_level {
if diagnostic.tags().is_verbose() && verbose {
self.0
.error(markup! {{PrintDiagnostic::verbose(diagnostic)}});
writer.error(markup! {{PrintDiagnostic::verbose(diagnostic)}});
} else {
self.0
.error(markup! {{PrintDiagnostic::simple(diagnostic)}});
writer.error(markup! {{PrintDiagnostic::simple(diagnostic)}});
}
if diagnostic.tags().is_verbose() {
if verbose {
writer.error(markup! {{PrintDiagnostic::verbose(diagnostic)}});
} else {
continue;
}
} else {
writer.error(markup! {{PrintDiagnostic::simple(diagnostic)}});
}
🤖 Prompt for AI Agents
In `@crates/biome_cli/src/reporter/terminal.rs` around lines 154 - 159, The code
currently falls through and prints verbose-tagged diagnostics even when verbose
is false; update the conditional in the loop that checks diagnostic.severity()
so that verbose-tagged diagnostics are only printed when verbose is true: if
diagnostic.tags().is_verbose() then print PrintDiagnostic::verbose(diagnostic)
when verbose is true and otherwise skip that diagnostic (do not fall through to
PrintDiagnostic::simple), else print PrintDiagnostic::simple(diagnostic). Locate
the branch around diagnostic.severity(), diagnostic.tags().is_verbose(),
writer.error and PrintDiagnostic::verbose/simple and implement the explicit skip
(e.g., continue) for non-verbose context.

Comment on lines +264 to +303
let configured_workspace = self.configure_workspace(fs, console, workspace, cli_options)?;

// Commands that don't require crawling can implement custom execution logic
if !self.requires_crawling() {
return self.execute_without_crawling(session, configured_workspace);
}

let ConfiguredWorkspace {
execution,
paths,
duration,
configuration_files: _,
project_key,
} = configured_workspace;

if let Some(stdin) = self.get_stdin(console, execution.as_ref())? {
let biome_path = BiomePath::new(stdin.as_path());
if biome_path.extension().is_none() {
console.error(markup! {
{PrintDiagnostic::simple(&CliDiagnostic::from(StdinDiagnostic::new_no_extension()))}
});
console.append(markup! {{stdin.as_content()}});
return Ok(());
}

return Self::ProcessFile::process_std_in(ProcessStdinFilePayload {
biome_path: &biome_path,
project_key,
workspace,
execution: execution.as_ref(),
content: stdin.as_content(),
cli_options,
console,
skip_ignore_check: Self::ProcessFile::should_skip_ignore_check(
&biome_path,
workspace,
),
});
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Stdin is read twice, so the second read may be empty. Line 405 reads stdin for scan-kind derivation, then Line 279 reads it again for processing. If Console::read() consumes the stream, stdin handling silently skips. Consider reading once and threading it through.

🔧 Suggested fix (store stdin in ConfiguredWorkspace)
-        let ConfiguredWorkspace {
+        let ConfiguredWorkspace {
             execution,
             paths,
             duration,
             configuration_files: _,
             project_key,
+            stdin,
         } = configured_workspace;
 
-        if let Some(stdin) = self.get_stdin(console, execution.as_ref())? {
+        if let Some(stdin) = stdin {
             let biome_path = BiomePath::new(stdin.as_path());
             if biome_path.extension().is_none() {
                 console.error(markup! {
                     {PrintDiagnostic::simple(&CliDiagnostic::from(StdinDiagnostic::new_no_extension()))}
                 });
 pub(crate) struct ConfiguredWorkspace {
@@
     /// The unique identifier of the project
     pub project_key: ProjectKey,
+    /// Captured stdin (if any)
+    pub stdin: Option<Stdin>,
 }
-        let stdin = self.get_stdin(console, execution.as_ref())?;
+        let stdin = self.get_stdin(console, execution.as_ref())?;
@@
-        Ok(ConfiguredWorkspace {
+        Ok(ConfiguredWorkspace {
             execution,
             paths,
             duration: Some(result.duration),
             configuration_files: result.configuration_files,
             project_key: open_project_result.project_key,
+            stdin,
         })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let configured_workspace = self.configure_workspace(fs, console, workspace, cli_options)?;
// Commands that don't require crawling can implement custom execution logic
if !self.requires_crawling() {
return self.execute_without_crawling(session, configured_workspace);
}
let ConfiguredWorkspace {
execution,
paths,
duration,
configuration_files: _,
project_key,
} = configured_workspace;
if let Some(stdin) = self.get_stdin(console, execution.as_ref())? {
let biome_path = BiomePath::new(stdin.as_path());
if biome_path.extension().is_none() {
console.error(markup! {
{PrintDiagnostic::simple(&CliDiagnostic::from(StdinDiagnostic::new_no_extension()))}
});
console.append(markup! {{stdin.as_content()}});
return Ok(());
}
return Self::ProcessFile::process_std_in(ProcessStdinFilePayload {
biome_path: &biome_path,
project_key,
workspace,
execution: execution.as_ref(),
content: stdin.as_content(),
cli_options,
console,
skip_ignore_check: Self::ProcessFile::should_skip_ignore_check(
&biome_path,
workspace,
),
});
}
let configured_workspace = self.configure_workspace(fs, console, workspace, cli_options)?;
// Commands that don't require crawling can implement custom execution logic
if !self.requires_crawling() {
return self.execute_without_crawling(session, configured_workspace);
}
let ConfiguredWorkspace {
execution,
paths,
duration,
configuration_files: _,
project_key,
stdin,
} = configured_workspace;
if let Some(stdin) = stdin {
let biome_path = BiomePath::new(stdin.as_path());
if biome_path.extension().is_none() {
console.error(markup! {
{PrintDiagnostic::simple(&CliDiagnostic::from(StdinDiagnostic::new_no_extension()))}
});
console.append(markup! {{stdin.as_content()}});
return Ok(());
}
return Self::ProcessFile::process_std_in(ProcessStdinFilePayload {
biome_path: &biome_path,
project_key,
workspace,
execution: execution.as_ref(),
content: stdin.as_content(),
cli_options,
console,
skip_ignore_check: Self::ProcessFile::should_skip_ignore_check(
&biome_path,
workspace,
),
});
}
🤖 Prompt for AI Agents
In `@crates/biome_cli/src/runner/mod.rs` around lines 264 - 303, The code reads
stdin twice which can consume the stream and make the second read empty; update
configure_workspace/ConfiguredWorkspace to capture and return an optional Stdin
payload (the result of self.get_stdin/Console::read) so the workspace carries
the already-read stdin, then change the branch that currently calls
self.get_stdin(console, execution.as_ref()) to use the stored stdin from
ConfiguredWorkspace; pass that stored stdin into ProcessFile::process_std_in via
ProcessStdinFilePayload (and still call ProcessFile::should_skip_ignore_check
with the biome_path and workspace) so stdin is only read once and reused for
scan-kind derivation and processing.

@ematipico ematipico merged commit fbe460f into main Feb 14, 2026
35 of 36 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-CLI Area: CLI A-Core Area: core A-Diagnostic Area: diagnostocis A-Formatter Area: formatter A-Linter Area: linter A-Parser Area: parser A-Project Area: project L-CSS Language: CSS L-Grit Language: GritQL L-HTML Language: HTML and super languages L-JavaScript Language: JavaScript and super languages L-JSON Language: JSON and super languages

Projects

None yet

Development

Successfully merging this pull request may close these issues.