Skip to content

Conversation

@ematipico
Copy link
Member

Summary

Test Plan

Docs

arendjr and others added 30 commits August 19, 2025 16:47
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Carson McManus <[email protected]>
Co-authored-by: Arend van Beelen jr. <[email protected]>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
# Conflicts:
#	crates/biome_resolver/tests/spec_tests.rs
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Emanuele Stoppa <[email protected]>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Emanuele Stoppa <[email protected]>
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: Emanuele Stoppa <[email protected]>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Emanuele Stoppa <[email protected]>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@pkg-pr-new
Copy link

pkg-pr-new bot commented Oct 9, 2025

Open in StackBlitz

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

commit: 27a5838

ematipico and others added 15 commits October 10, 2025 16:40
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: mdevils <[email protected]>
Co-authored-by: dyc3 <[email protected]>
Co-authored-by: arendjr <[email protected]>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Naoki Ikeguchi <[email protected]>
Co-authored-by: Emanuele Stoppa <[email protected]>
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: Conaclos <[email protected]>
Co-authored-by: Netail <[email protected]>
Co-authored-by: dyc3 <[email protected]>
@ematipico ematipico changed the title chore: v2.3.0 chore: release v2.3.0 Oct 22, 2025
@ematipico ematipico marked this pull request as ready for review October 22, 2025 16:22
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 22, 2025

Walkthrough

This pull request delivers substantial feature additions across multiple systems. It implements Tailwind CSS 4.0 directive parsing and formatting, adds Checkstyle and RdJson reporters for CLI output, expands parser options via CLI flags for CSS and JSON configuration, and introduces domain-based rule filtering for the linter. Additionally, it implements experimental full HTML support with enhanced file handling, deprecates experimentalScannerIgnores in favour of force-ignore syntax, promotes several lint rules to specific groups, adds new React domain rules, and refines diagnostic offset handling for embedded languages.

Possibly related PRs

Suggested reviewers

  • dyc3

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 63.95% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title "chore: release v2.3.0" is clear, concise, and directly describes the primary purpose of this changeset. The PR encompasses numerous release notes (23 changeset files), configuration updates, CLI enhancements, reporter implementations, and supporting infrastructure—all aligned with delivering version 2.3.0. The title accurately captures the essence of these changes without unnecessary verbosity.
Description Check ✅ Passed The description consists of the standard PR template with empty sections (Summary, Test Plan, Docs), which provides minimal detail. However, it remains related to the changeset because the PR is explicitly a release—all changes are release notes, version configurations, and CLI/formatter enhancements supporting the v2.3.0 release. While more substantive documentation would strengthen the PR, the description is not off-topic.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 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: 23

Caution

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

⚠️ Outside diff range comments (3)
crates/biome_cli/src/execute/process_file.rs (2)

184-201: Borrowed temporary of SharedTraversalOptions causes a dangling reference; pass a stable value.

let shared_context = &SharedTraversalOptions::new(ctx); takes a reference to a temporary. This won’t live long enough. Store the value and borrow at call sites. While here, keep the traversal_mode access consistent.

Apply:

-    let shared_context = &SharedTraversalOptions::new(ctx);
+    let shared_context = SharedTraversalOptions::new(ctx);

-    match ctx.execution.traversal_mode {
+    match ctx.execution.traversal_mode() {
         TraversalMode::Lint { ref suppression_reason, suppress, .. } => {
             let categories = RuleCategoriesBuilder::default().with_lint().with_syntax();
             // the unsupported case should be handled already at this point
             lint_and_assist(
-                shared_context,
+                &shared_context,
                 biome_path.clone(),
                 suppress,
                 suppression_reason.as_deref(),
                 categories.build(),
                 &file_features,
             )
         }
         TraversalMode::Format { .. } => {
             // the unsupported case should be handled already at this point
-            format(shared_context, biome_path.clone(), &file_features)
+            format(&shared_context, biome_path.clone(), &file_features)
         }
         TraversalMode::Check { .. } | TraversalMode::CI { .. } => {
-            check_file(shared_context, biome_path.clone(), &file_features)
+            check_file(&shared_context, biome_path.clone(), &file_features)
         }

Also applies to: 205-209


156-165: Use or not and when aggregating unsupported feature reasons for Check/CI.

For Check/CI, any missing feature should trigger the early path. Chaining Option::and drops later failures when the first is None (enabled), masking unsupported states.

Apply:

-        TraversalMode::Check { .. } | TraversalMode::CI { .. } => file_features
-            .support_kind_if_not_enabled(FeatureKind::Lint)
-            .and(file_features.support_kind_if_not_enabled(FeatureKind::Format))
-            .and(file_features.support_kind_if_not_enabled(FeatureKind::Assist)),
+        TraversalMode::Check { .. } | TraversalMode::CI { .. } => file_features
+            .support_kind_if_not_enabled(FeatureKind::Lint)
+            .or(file_features.support_kind_if_not_enabled(FeatureKind::Format))
+            .or(file_features.support_kind_if_not_enabled(FeatureKind::Assist)),
crates/biome_cli/src/execute/traverse.rs (1)

289-332: Error counts aren’t incremented for Message::Error.

This under‑reports errors in the summary and can affect exit codes. Increment counters for error/info here too.

                 Message::Error(mut err) => {
                     let location = err.location();
                     if self.should_skip_diagnostic(err.severity(), err.tags()) {
                         continue;
                     }
-                    if err.severity() == Severity::Warning {
+                    if err.severity() == Severity::Error {
+                        self.errors.fetch_add(1, Ordering::Relaxed);
+                    }
+                    if err.severity() == Severity::Warning {
                         self.warnings.fetch_add(1, Ordering::Relaxed);
                     }
-                    // if err.severity() == Severity::Information {
-                    //     self.infos.fetch_add(1, Ordering::Relaxed);
-                    // }
+                    if err.severity() == Severity::Information {
+                        self.infos.fetch_add(1, Ordering::Relaxed);
+                    }
🧹 Nitpick comments (34)
.changeset/upset-impalas-grab.md (1)

8-8: Minor wording improvement for clarity.

Consider using "can" instead of "able to" for a more concise phrasing.

-This means that now Biome is able to lint and format the JavaScript (TypeScript), HTML and CSS code that is contained in these files.
+This means that now Biome can lint and format the JavaScript (TypeScript), HTML and CSS code that is contained in these files.
.changeset/upset-apes-reply.md (1)

5-5: Simplify redundant phrasing.

"support is now supported" is slightly redundant. Consider using just "is now available in Biome" or similar.

-React 19.2 support is now supported in Biome.
+React 19.2 is now supported in Biome.
.changeset/breezy-suns-leave.md (1)

5-5: Consider enhancing the changeset with documentation link and code example.

Per coding guidelines, when referencing a rule in a changeset, include a link to its page on the website. Additionally, including a code example would help illustrate the change.

Apply this diff:

-Added `ignore` option to `noUnknownAtRules`. If an unknown at-rule matches any of the items provided in `ignore`, a diagnostic won't be emitted.
+Added `ignore` option to [noUnknownAtRules](https://biomejs.dev/linter/rules/no-unknown-at-rules). If an unknown at-rule matches any of the items provided in `ignore`, a diagnostic won't be emitted.
+
+```json
+{
+  "linter": {
+    "rules": {
+      "suspicious": {
+        "noUnknownAtRules": {
+          "options": {
+            "ignore": ["custom-at-rule"]
+          }
+        }
+      }
+    }
+  }
+}
+```

Based on coding guidelines.

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

3542-3575: Nice test coverage for the new format-with-errors flag!

The test correctly validates that formatting proceeds and writes output even with syntax errors, whilst still returning an error code. This is sensible behaviour for CI/CD workflows.

Optional: Consider adding a brief comment at line 3564 explaining why is_err() is expected when using --format-with-errors=true. Whilst the behaviour makes sense (signal failure but still write formatted output), a comment would help future maintainers understand the intentional error return.

+    assert!(result.is_err(), "run_cli returned {result:?}");

Could optionally become:

+    // Still returns an error to signal syntax issues exist, even though formatting proceeds
+    assert!(result.is_err(), "run_cli returned {result:?}");
.changeset/max-managers-mandate.md (1)

5-5: Consider adding an issue link.

Per the coding guidelines, when fixing a bug in a changeset, start with an issue link (e.g., "Fixed #1234: …").

As per coding guidelines.

.changeset/stupid-groups-grow.md (1)

1-9: Consider linking the rule name.

As per coding guidelines, "when referencing a rule or assist in a changeset, include a link to its page on the website." Consider linking noHeaderScope to its documentation page if available.

Otherwise, the changeset correctly documents the new HTML linting and assist configurations using proper tense and formatting.

Based on coding guidelines.

crates/biome_css_parser/src/syntax/property/mod.rs (1)

175-184: Defensive guard: check for ‘*’ before rewriting to TW_VALUE_THEME_REFERENCE

Even if upstream lookahead is fixed, add a local star check to prevent creating a TW_VALUE_THEME_REFERENCE when only “-” is present.

-        if let Some(ident) = ident
-            && p.options().is_tailwind_directives_enabled()
-            && p.at(T![-])
-        {
+        if let Some(ident) = ident
+            && p.options().is_tailwind_directives_enabled()
+            && p.at(T![-])
+            && p.nth_at(1, T![*])
+        {
             let m = ident.precede(p);
             p.expect(T![-]);
             p.expect(T![*]);
             m.complete(p, TW_VALUE_THEME_REFERENCE);
         }
crates/biome_cli/src/execute/std_in.rs (1)

81-90: DRY the HTML wrapper selection

Logic’s sound; it’s triplicated. Extract a tiny helper to keep it consistent and future‑proof (and make reviewers slightly less cross‑eyed).

Example:

fn maybe_wrap_html_output(ext: Option<&str>, input: &str, code: String, full_html: bool) -> String {
    if full_html {
        code
    } else {
        match ext {
            Some("astro") => AstroFileHandler::output(input, &code),
            Some("vue") => VueFileHandler::output(input, &code),
            Some("svelte") => SvelteFileHandler::output(input, &code),
            _ => code,
        }
    }
}

Then replace each block with:

- let output = if !file_features.supports_full_html_support() { /* match ... */ } else { code };
+ let output = maybe_wrap_html_output(biome_path.extension(), /* input */, code, file_features.supports_full_html_support());

Use content or &new_content as appropriate for the input argument.

Also applies to: 181-190, 209-218

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

8-26: Consider consolidating duplicate constants.

MAIN_1 and MAIN_2 are identical. If the intent is to test multiple files with the same content, a single constant would suffice. If different content was intended to improve test coverage, they should be differentiated.

Apply this diff to consolidate:

-const MAIN_1: &str = r#"import { z} from "z"
-import { z, b , a} from "lodash"
-
-a ==b
-
-debugger
-
-let f;
-		let f;"#;
-
-const MAIN_2: &str = r#"import { z} from "z"
+const TEST_FILE_CONTENT: &str = r#"import { z} from "z"
 import { z, b , a} from "lodash"
 
 a ==b
@@ -33,7 +26,7 @@
 let f;
 		let f;"#;

Then replace usages of MAIN_1 and MAIN_2 with TEST_FILE_CONTENT.

crates/biome_cli/tests/cases/json_parsing.rs (6)

214-243: Add an explicit success assertion to stabilise the test

Formatting with trailing commas allowed should succeed; assert it so snapshots don’t carry exit‑code ambiguity.

     let (fs, result) = run_cli(
         fs,
         &mut console,
         Args::from(
             [
                 "format",
                 "--json-parse-allow-trailing-commas=true",
                 file_path.as_str(),
             ]
             .as_slice(),
         ),
     );
 
-    assert_cli_snapshot(SnapshotPayload::new(
+    assert!(result.is_ok(), "run_cli returned {result:?}");
+    assert_cli_snapshot(SnapshotPayload::new(
         module_path!(),
         "format_json_parse_allow_trailing_commas_true",
         fs,
         console,
         result,
     ));

247-280: Lock in expected OK exit for combined flags

Both flags enabled on valid input should return success.

     let (fs, result) = run_cli(
         fs,
         &mut console,
         Args::from(
             [
                 "check",
                 "--json-parse-allow-comments=true",
                 "--json-parse-allow-trailing-commas=true",
                 file_path.as_str(),
             ]
             .as_slice(),
         ),
     );
 
-    assert_cli_snapshot(SnapshotPayload::new(
+    assert!(result.is_ok(), "run_cli returned {result:?}");
+    assert_cli_snapshot(SnapshotPayload::new(
         module_path!(),
         "check_combined_json_parser_flags",
         fs,
         console,
         result,
     ));

311-356: Assert config precedence over CLI

Config disallows comments; even with the CLI flag set, parsing should fail if config wins. Assert the error to codify precedence.

     let (fs, result) = run_cli(
         fs,
         &mut console,
         Args::from(
             [
                 "check",
                 "--json-parse-allow-comments=true",
                 file_path.as_str(),
             ]
             .as_slice(),
         ),
     );
 
-    assert_cli_snapshot(SnapshotPayload::new(
+    assert!(result.is_err(), "run_cli returned {result:?}");
+    assert_cli_snapshot(SnapshotPayload::new(
         module_path!(),
         "check_json_parser_flags_override_config",
         fs,
         console,
         result,
     ));

358-396: Assert OK when config allows comments

With allowComments: true in config, this should succeed; add an assertion.

     let (fs, result) = run_cli(
         fs,
         &mut console,
         Args::from(["check", file_path.as_str()].as_slice()),
     );
 
-    assert_cli_snapshot(SnapshotPayload::new(
+    assert!(result.is_ok(), "run_cli returned {result:?}");
+    assert_cli_snapshot(SnapshotPayload::new(
         module_path!(),
         "check_json_parse_respects_config_allow_comments",
         fs,
         console,
         result,
     ));

398-433: Assert OK when config allows trailing commas

Similarly, guarantee success when allowTrailingCommas: true.

     let (fs, result) = run_cli(
         fs,
         &mut console,
         Args::from(["check", file_path.as_str()].as_slice()),
     );
 
-    assert_cli_snapshot(SnapshotPayload::new(
+    assert!(result.is_ok(), "run_cli returned {result:?}");
+    assert_cli_snapshot(SnapshotPayload::new(
         module_path!(),
         "check_json_parse_respects_config_allow_trailing_commas",
         fs,
         console,
         result,
     ));

10-433: Trim duplication with a tiny helper (optional)

There’s repeated setup for MemoryFileSystem, file.json content, and Args. A small local helper would cut noise and make intent pop.

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

94-97: Make JSON merge consistent (semicolon)

Style nit: mirror the CSS block and add the missing semicolon; avoids clippy grumbles and keeps symmetry.

-        if self.json_parser.is_some() {
-            json.parser.merge_with(self.json_parser.clone())
-        }
+        if self.json_parser.is_some() {
+            json.parser.merge_with(self.json_parser.clone());
+        }
crates/biome_cli/src/execute/process_file/format.rs (1)

95-118: Verify the capability flag and deduplicate empty-output branches.

  • Method name supports_full_html_support() reads a tad tautological; confirm it exists as-is across the workspace API and isn’t a typo (e.g. supports_full_html()).
  • The if output.is_empty() { return Ok(Unchanged) } blocks repeat per extension.

Consider:

-    if !features_supported.supports_full_html_support() {
-        match workspace_file.as_extension() {
-            Some("astro") => {
-                if output.is_empty() { return Ok(FileStatus::Unchanged); }
-                output = AstroFileHandler::output(input.as_str(), output.as_str());
-            }
-            Some("vue") => {
-                if output.is_empty() { return Ok(FileStatus::Unchanged); }
-                output = VueFileHandler::output(input.as_str(), output.as_str());
-            }
-            Some("svelte") => {
-                if output.is_empty() { return Ok(FileStatus::Unchanged); }
-                output = SvelteFileHandler::output(input.as_str(), output.as_str());
-            }
-            _ => {}
-        }
-    }
+    if !features_supported.supports_full_html_support() {
+        if output.is_empty() {
+            return Ok(FileStatus::Unchanged);
+        }
+        match workspace_file.as_extension() {
+            Some("astro") => {
+                output = AstroFileHandler::output(input.as_str(), output.as_str());
+            }
+            Some("vue") => {
+                output = VueFileHandler::output(input.as_str(), output.as_str());
+            }
+            Some("svelte") => {
+                output = SvelteFileHandler::output(input.as_str(), output.as_str());
+            }
+            _ => {}
+        }
+    }

Please confirm the intended behaviour when full HTML is supported vs. per‑extension mode with Astro/Vue/Svelte – a quick e2e check over representative fixtures would be great.

crates/biome_css_parser/src/syntax/mod.rs (2)

340-348: Tailwind --tw-* value path: good guard; add tests for both modes.

The -- + -* lookahead is precise. Please add parser tests for:

  • with Tailwind enabled: parses --tw-* as TW_VALUE_THEME_REFERENCE;
  • with Tailwind disabled: emits tailwind_disabled.

Happy to draft test cases if you want.


590-600: Remapping * to an identifier inside bracketed values – double‑check token remap and context.

This is Tailwind‑only and fine conceptually, but consider using the regular lexing context for consistency.

-                    let m = p.start();
-                    p.bump_remap(T![ident]);
+                    let m = p.start();
+                    p.bump_remap_with_context(T![ident], CssLexContext::Regular);
                     Present(m.complete(p, CSS_CUSTOM_IDENTIFIER))

Also add a negative test to ensure * here triggers tailwind_disabled when the flag is off.

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

382-393: Welcome format_node_with_offset for CSS.

Good addition; mirrors core formatter API and unblocks range/offset consumers.

Add a brief rustdoc example showing how to build a CssSyntaxNodeWithOffset and call this function.

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

77-89: Simplify Option assignment.

clone_from works but is noisy; direct assignment is clearer.

-        if self.json_parser.is_some() {
-            json.parser.clone_from(&self.json_parser)
-        }
+        if let Some(p) = self.json_parser.clone() {
+            json.parser = Some(p);
+        }
@@
-        if self.css_parser.is_some() {
-            css.parser.clone_from(&self.css_parser);
-        }
+        if let Some(p) = self.css_parser.clone() {
+            css.parser = Some(p);
+        }
crates/biome_cli/src/execute/process_file/lint_and_assist.rs (1)

100-113: Tiny tidy‑up: extract per‑extension splice to a helper.

Keeps analyse_with_guard lean and reduces repetition.

-        if !features_supported.supports_full_html_support() {
-            match workspace_file.as_extension() {
-                Some("astro") => {
-                    output = AstroFileHandler::output(input.as_str(), output.as_str());
-                }
-                Some("vue") => {
-                    output = VueFileHandler::output(input.as_str(), output.as_str());
-                }
-                Some("svelte") => {
-                    output = SvelteFileHandler::output(input.as_str(), output.as_str());
-                }
-                _ => {}
-            }
-        }
+        if !features_supported.supports_full_html_support() {
+            output = splice_framework_output(workspace_file.as_extension(), &input, &output);
+        }

New helper:

fn splice_framework_output(ext: Option<&str>, input: &str, output: &str) -> String {
    match ext {
        Some("astro") => AstroFileHandler::output(input, output),
        Some("vue") => VueFileHandler::output(input, output),
        Some("svelte") => SvelteFileHandler::output(input, output),
        _ => output.to_string(),
    }
}
crates/biome_cli/src/execute/mod.rs (1)

251-255: Update the RDJSON documentation link to the official reviewdog repository.

Line 253: Replace the deepwiki mirror link with the canonical reviewdog specification at https://github.com/reviewdog/reviewdog/tree/master/proto/rdf to guard against link rot and direct users to the authoritative source.

crates/biome_css_parser/src/syntax/at_rule/mod.rs (1)

117-165: Reduce repetition in Tailwind match arms

Nine near-identical branches differ only by parser fn. Extract a tiny helper to centralise the gating + fallback; keeps the match tidy and harder to get wrong next time.

Example:

+#[inline]
+fn parse_tailwind_exclusive(
+    p: &mut CssParser,
+    f: fn(&mut CssParser) -> ParsedSyntax,
+) -> ParsedSyntax {
+    CssSyntaxFeatures::Tailwind
+        .parse_exclusive_syntax(p, f, |p, m| tailwind_disabled(p, m.range(p)))
+        .or_else(|| parse_unknown_at_rule(p))
+}
@@
-        T![theme] => CssSyntaxFeatures::Tailwind
-            .parse_exclusive_syntax(p, parse_theme_at_rule, |p, m| {
-                tailwind_disabled(p, m.range(p))
-            })
-            .or_else(|| parse_unknown_at_rule(p)),
+        T![theme] => parse_tailwind_exclusive(p, parse_theme_at_rule),
@@
-        T![utility] => CssSyntaxFeatures::Tailwind
-            .parse_exclusive_syntax(p, parse_utility_at_rule, |p, m| {
-                tailwind_disabled(p, m.range(p))
-            })
-            .or_else(|| parse_unknown_at_rule(p)),
+        T![utility] => parse_tailwind_exclusive(p, parse_utility_at_rule),

…and similarly for variant/custom_variant/apply/source/reference/config/plugin.

crates/biome_configuration/src/lib.rs (2)

572-639: Custom deserialiser: unknown keys + deprecation handling

Solid field-by-field handling with explicit allowed keys and tailored diagnostics. Small nit: consider storing DEFAULT_SCANNER_IGNORE_ENTRIES as a HashSet<&[u8]> for O(1) membership if this list grows, but it’s fine as-is.


641-672: Visitor implementation and key synchronisation verified

The TS interface and Rust ALLOWED_KEYS list are in perfect sync (all four fields present: maxSize, ignoreUnknown, includes, experimentalScannerIgnores). The unknown-key diagnostic handling is sound. The suggested unit test to lock these in sync remains a sensible optional improvement.

crates/biome_configuration/src/html.rs (1)

84-91: Line endings: ‘auto’ doc matches behaviour

Nice addition; aligns with other formatters. Consider adding a short rustdoc example for Windows vs Unix to avoid ambiguity.

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

136-159: Avoid panicking in reporters

expect("Invalid markup") will abort the run on malformed advice. Prefer a graceful fallback to the raw Display text.

Example:

-            markup_to_string(&message).expect("Invalid markup")
+            markup_to_string(&message).unwrap_or_else(|| text.to_string())
crates/biome_cli/src/commands/mod.rs (3)

159-168: Expose --format-with-errors on format for parity

You added the flag for Check/Ci; consider adding it to Format too so users don’t need to craft a formatter config just for this one toggle.

@@
     Format {
         #[bpaf(external(formatter_configuration), optional, hide_usage)]
         formatter_configuration: Option<FormatterConfiguration>,
+        /// Whether formatting should be allowed to proceed if a given file
+        /// has syntax errors
+        #[bpaf(long("format-with-errors"), argument("true|false"))]
+        format_with_errors: Option<FormatWithErrorsEnabled>,

263-274: Tweak arg metavariable for clarity

Using argument("SELECTOR") (and explaining it accepts group/rule/domain) reads cleaner than GROUP|RULE|DOMAIN in the metavariable itself.

-        #[bpaf(long("only"), argument("GROUP|RULE|DOMAIN"))]
+        #[bpaf(long("only"), argument("SELECTOR"))]

275-285: Same for --skip

Mirror the metavariable change here for consistency.

-        #[bpaf(long("skip"), argument("GROUP|RULE|DOMAIN"))]
+        #[bpaf(long("skip"), argument("SELECTOR"))]
crates/biome_configuration/src/analyzer/mod.rs (1)

719-750: DomainSelector invariants and schema

  • Public tuple field allows DomainSelector("bogus") at compile time. If feasible post-release, consider making the field private and exposing as_str(), updating codegen to use that.
  • Optional: derive JsonSchema here for symmetry with AnalyzerSelector.
-#[derive(Clone, Copy, Eq, PartialEq, Hash)]
-pub struct DomainSelector(pub &'static str);
+#[derive(Clone, Copy, Eq, PartialEq, Hash)]
+pub struct DomainSelector(&'static str);
+
+#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
 // keep FromStr/Display/Deref as-is; add:
 impl DomainSelector {
     pub const fn as_str(&self) -> &'static str { self.0 }
 }

Note: this requires updating any codegen that currently reads self.0.

crates/biome_analyze/src/rule.rs (1)

1265-1283: Diagnostic/advices refactor reads clean

Manual Diagnostic/Advices impls with advice_offset mirror parser behaviour; location handling looks correct.

Consider a builder-style helper for ergonomics:

 impl RuleDiagnostic {
+    pub fn with_advice_offset(mut self, offset: TextSize) -> Self {
+        self.advice_offset = Some(offset);
+        self
+    }
 }

Also applies to: 1285-1313, 1315-1351, 1374-1389

crates/biome_css_parser/src/syntax/at_rule/tailwind.rs (1)

33-51: Consider Tailwind lexing for @utility names

If utility names can include extended tokens in future (similar to @apply), parse the name under CssLexContext::TailwindUtility for consistency.

-    // Parse utility name - can be simple or functional
-    if !is_at_identifier(p) {
+    // Parse utility name - can be simple or functional
+    if !is_at_identifier(p) {
         p.error(expected_identifier(p, p.cur_range()));
         return Present(m.complete(p, CSS_BOGUS_AT_RULE));
     }
 
-    parse_utility_name(p).ok();
+    // Use Tailwind context to allow extended identifiers if needed
+    parse_identifier(p, CssLexContext::TailwindUtility).or_else(|| parse_utility_name(p)).ok();

Also applies to: 53-68

Comment on lines +12 to +18
```json
{
"compilerOptions": {
"baseUrl": "./src",
}
}
```
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 the invalid JSON syntax in the example.

The JSON example has a trailing comma before the closing brace on line 16, which is not valid JSON. This will confuse users trying to replicate the example.

Apply this diff to fix the JSON:

 **`tsconfig.json`**
 ```json
 {
     "compilerOptions": {
-        "baseUrl": "./src",
+        "baseUrl": "./src"
     }
 }

<details>
<summary>🤖 Prompt for AI Agents</summary>

In .changeset/based-bears-brawl.md around lines 12 to 18 the JSON example
contains an invalid trailing comma after "baseUrl": "./src",; remove the
trailing comma so the object closes properly (i.e., change "baseUrl": "./src",
to "baseUrl": "./src") and ensure the JSON block is valid before committing.


</details>

<!-- This is an auto-generated comment by CodeRabbit -->

Comment on lines +5 to +26
Enhanced the `init` command. The `init` command now checks if the existing project contains known ignore files and known generated folders.

If Biome finds `.gitignore` or `.ignore` files, it will add the following configuration to `biome.json`:
```diff
{
+ "vcs": {
+ "enabled": true,
+ "clientKind": "git",
+ "useIgnoreFile": true
+ }
}
```

If Biome finds a `dist/` folder, it will exclude it automatically using the double-exclude syntax:

```diff
{
+ "files": {
+ "includes": ["**", "!!**/dist"]
+ }
}
```
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

Add periods to sentences.

Per the coding guidelines, every sentence in a changeset must end with a period. Lines 5, 7, and 18 are missing periods.

As per coding guidelines.

Apply this diff:

-Enhanced the `init` command. The `init` command now checks if the existing project contains known ignore files and known generated folders
+Enhanced the `init` command. The `init` command now checks if the existing project contains known ignore files and known generated folders.

-If Biome finds `.gitignore` or `.ignore` files, it will add the following configuration to `biome.json`:
+If Biome finds `.gitignore` or `.ignore` files, it will add the following configuration to `biome.json`:

-If Biome finds a `dist/` folder, it will exclude it automatically using the double-exclude syntax:
+If Biome finds a `dist/` folder, it will exclude it automatically using the double-exclude syntax:

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
.changeset/busy-pens-send.md around lines 5 to 26: several sentences in the
changeset lack terminal periods (specifically lines 5, 7, and 18); edit those
lines to add a trailing period to each sentence so every sentence in the file
ends with a period, preserving existing wording and spacing.

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

Updated the formatting of `.svelte` and `.vue` files. Now the indentation of the JavaScript blocks matches Prettier's:

```diff
<script>
- import Component from "./Component"
+ import Component from "./Component"
</script>
```
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

Add a header and fix sentence punctuation.

Changesets require #### or ##### headers and must end all sentences with periods. Adjust the colon after "Prettier's" to a period.

 ---
 "@biomejs/biome": minor
 ---

+#### Updates
+
-Updated the formatting of `.svelte` and `.vue` files. Now the indentation of the JavaScript blocks matches Prettier's:
+Updated the formatting of `.svelte` and `.vue` files. Now the indentation of the JavaScript blocks matches Prettier's.

 ```diff
 <script>
 - import Component from "./Component"
 +   import Component from "./Component"
 </script>

As per coding guidelines.

<details>
<summary>🤖 Prompt for AI Agents</summary>

In .changeset/dull-drinks-switch.md around lines 1 to 12, the changeset body
lacks the required header and has punctuation issues: add a header line starting
with "####" or "#####", replace the colon after "Prettier's" with a period,
ensure every sentence ends with a period, remove the stray standalone "diff"
token, and make sure the fenced code block is properly opened and closed so the
file conforms to changeset formatting rules.


</details>

<!-- This is an auto-generated comment by CodeRabbit -->

"@biomejs/biome": minor
---

Added a new reporter named `rdjson`. This reporter prints diagnostics following the [RDJSON format](https://deepwiki.com/reviewdog/reviewdog/3.2-reviewdog-diagnostic-format):
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

End sentences with a period.

This line should end with a period per the changeset guidelines.

Apply this diff:

-Added a new reporter named `rdjson`. This reporter prints diagnostics following the [RDJSON format](https://deepwiki.com/reviewdog/reviewdog/3.2-reviewdog-diagnostic-format):
+Added a new reporter named `rdjson`. This reporter prints diagnostics following the [RDJSON format](https://deepwiki.com/reviewdog/reviewdog/3.2-reviewdog-diagnostic-format).
📝 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 a new reporter named `rdjson`. This reporter prints diagnostics following the [RDJSON format](https://deepwiki.com/reviewdog/reviewdog/3.2-reviewdog-diagnostic-format):
Added a new reporter named `rdjson`. This reporter prints diagnostics following the [RDJSON format](https://deepwiki.com/reviewdog/reviewdog/3.2-reviewdog-diagnostic-format).
🤖 Prompt for AI Agents
In .changeset/gentle-pots-hunt.md around line 5, the sentence "Added a new
reporter named `rdjson`. This reporter prints diagnostics following the [RDJSON
format](https://deepwiki.com/reviewdog/reviewdog/3.2-reviewdog-diagnostic-format):"
is missing a terminal period; append a period to the end of the line so it ends
with a period per changeset guidelines.

Comment on lines +5 to +13
Promoted new lint rules:
- Promoted `noNonNullAssertedOptionalChain` to the suspicious group
- Promoted `useReactFunctionComponents` to the `style` group
- Promoted `useImageSize` to the `correctness` group
- Promoted `useConsistentTypeDefinitions` to the `style` group
- Promoted `useQwikClasslist` to the `correctness` group
- Promoted `noSecrets` to the `security` group

Removed the lint rule `useAnchorHref`, because its use case is covered by `useValidAnchor`.
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

Add links to rule documentation for each referenced rule.

Per the coding guidelines, when referencing a rule in a changeset, include a link to its page on the website. Each rule should be linked to its documentation page, for example: [useValidAnchor](https://biomejs.dev/linter/rules/use-valid-anchor/).

Consider updating the rules to include their documentation links:

-Promoted new lint rules:
-- Promoted `noNonNullAssertedOptionalChain` to the suspicious group.
-- Promoted `useReactFunctionComponents` to the `style` group.
-- Promoted `useImageSize` to the `correctness` group.
-- Promoted `useConsistentTypeDefinitions` to the `style` group.
-- Promoted `useQwikClasslist` to the `correctness` group.
-- Promoted `noSecrets` to the `security` group.
-
-Removed the lint rule `useAnchorHref`, because its use case is covered by `useValidAnchor`.
+Promoted new lint rules:
+- Promoted [`noNonNullAssertedOptionalChain`](https://biomejs.dev/linter/rules/no-non-null-asserted-optional-chain/) to the suspicious group.
+- Promoted [`useReactFunctionComponents`](https://biomejs.dev/linter/rules/use-react-function-components/) to the `style` group.
+- Promoted [`useImageSize`](https://biomejs.dev/linter/rules/use-image-size/) to the `correctness` group.
+- Promoted [`useConsistentTypeDefinitions`](https://biomejs.dev/linter/rules/use-consistent-type-definitions/) to the `style` group.
+- Promoted [`useQwikClasslist`](https://biomejs.dev/linter/rules/use-qwik-classlist/) to the `correctness` group.
+- Promoted [`noSecrets`](https://biomejs.dev/linter/rules/no-secrets/) to the `security` group.
+
+Removed the lint rule [`useAnchorHref`](https://biomejs.dev/linter/rules/use-anchor-href/), as its use case is covered by [`useValidAnchor`](https://biomejs.dev/linter/rules/use-valid-anchor/).
🧰 Tools
🪛 LanguageTool

[formatting] ~13-~13: If the ‘because’ clause is essential to the meaning, do not use a comma before the clause.
Context: ...p Removed the lint rule useAnchorHref, because its use case is covered by `useValidAnc...

(COMMA_BEFORE_BECAUSE)


pub type JsonAssistEnabled = Bool<true>;
/// Linter options specific to the JSON linter
/// Assist options specific to the JSON linter
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

Doc nit: “Assist options specific to the JSON linter”.

Assist ≠ linter. Suggest rewording to avoid confusion.

-/// Assist options specific to the JSON linter
+/// Assist options specific to the JSON language
📝 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
/// Assist options specific to the JSON linter
/// Assist options specific to the JSON language
🤖 Prompt for AI Agents
In crates/biome_configuration/src/json.rs around line 137, the doc comment
"Assist options specific to the JSON linter" misuses "linter" and may confuse
readers; change the doc comment to accurately describe the feature (for example:
"Assist options specific to JSON tooling" or "Options for JSON assist features")
so it does not refer to a linter, and update any adjacent documentation wording
to match this corrected terminology.

Comment on lines +5505 to +5944
impl FormatRule<biome_css_syntax::TwApplyAtRule>
for crate::tailwind::statements::apply_at_rule::FormatTwApplyAtRule
{
type Context = CssFormatContext;
#[inline(always)]
fn fmt(
&self,
node: &biome_css_syntax::TwApplyAtRule,
f: &mut CssFormatter,
) -> FormatResult<()> {
FormatNodeRule::<biome_css_syntax::TwApplyAtRule>::fmt(self, node, f)
}
}
impl AsFormat<CssFormatContext> for biome_css_syntax::TwApplyAtRule {
type Format<'a> = FormatRefWithRule<
'a,
biome_css_syntax::TwApplyAtRule,
crate::tailwind::statements::apply_at_rule::FormatTwApplyAtRule,
>;
fn format(&self) -> Self::Format<'_> {
FormatRefWithRule::new(
self,
crate::tailwind::statements::apply_at_rule::FormatTwApplyAtRule::default(),
)
}
}
impl IntoFormat<CssFormatContext> for biome_css_syntax::TwApplyAtRule {
type Format = FormatOwnedWithRule<
biome_css_syntax::TwApplyAtRule,
crate::tailwind::statements::apply_at_rule::FormatTwApplyAtRule,
>;
fn into_format(self) -> Self::Format {
FormatOwnedWithRule::new(
self,
crate::tailwind::statements::apply_at_rule::FormatTwApplyAtRule::default(),
)
}
}
impl FormatRule<biome_css_syntax::TwConfigAtRule>
for crate::tailwind::statements::config_at_rule::FormatTwConfigAtRule
{
type Context = CssFormatContext;
#[inline(always)]
fn fmt(
&self,
node: &biome_css_syntax::TwConfigAtRule,
f: &mut CssFormatter,
) -> FormatResult<()> {
FormatNodeRule::<biome_css_syntax::TwConfigAtRule>::fmt(self, node, f)
}
}
impl AsFormat<CssFormatContext> for biome_css_syntax::TwConfigAtRule {
type Format<'a> = FormatRefWithRule<
'a,
biome_css_syntax::TwConfigAtRule,
crate::tailwind::statements::config_at_rule::FormatTwConfigAtRule,
>;
fn format(&self) -> Self::Format<'_> {
FormatRefWithRule::new(
self,
crate::tailwind::statements::config_at_rule::FormatTwConfigAtRule::default(),
)
}
}
impl IntoFormat<CssFormatContext> for biome_css_syntax::TwConfigAtRule {
type Format = FormatOwnedWithRule<
biome_css_syntax::TwConfigAtRule,
crate::tailwind::statements::config_at_rule::FormatTwConfigAtRule,
>;
fn into_format(self) -> Self::Format {
FormatOwnedWithRule::new(
self,
crate::tailwind::statements::config_at_rule::FormatTwConfigAtRule::default(),
)
}
}
impl FormatRule<biome_css_syntax::TwCustomVariantAtRule>
for crate::tailwind::statements::custom_variant_at_rule::FormatTwCustomVariantAtRule
{
type Context = CssFormatContext;
#[inline(always)]
fn fmt(
&self,
node: &biome_css_syntax::TwCustomVariantAtRule,
f: &mut CssFormatter,
) -> FormatResult<()> {
FormatNodeRule::<biome_css_syntax::TwCustomVariantAtRule>::fmt(self, node, f)
}
}
impl AsFormat<CssFormatContext> for biome_css_syntax::TwCustomVariantAtRule {
type Format<'a> = FormatRefWithRule<
'a,
biome_css_syntax::TwCustomVariantAtRule,
crate::tailwind::statements::custom_variant_at_rule::FormatTwCustomVariantAtRule,
>;
fn format(&self) -> Self::Format<'_> {
FormatRefWithRule :: new (self , crate :: tailwind :: statements :: custom_variant_at_rule :: FormatTwCustomVariantAtRule :: default ())
}
}
impl IntoFormat<CssFormatContext> for biome_css_syntax::TwCustomVariantAtRule {
type Format = FormatOwnedWithRule<
biome_css_syntax::TwCustomVariantAtRule,
crate::tailwind::statements::custom_variant_at_rule::FormatTwCustomVariantAtRule,
>;
fn into_format(self) -> Self::Format {
FormatOwnedWithRule :: new (self , crate :: tailwind :: statements :: custom_variant_at_rule :: FormatTwCustomVariantAtRule :: default ())
}
}
impl FormatRule<biome_css_syntax::TwCustomVariantShorthand>
for crate::tailwind::auxiliary::custom_variant_shorthand::FormatTwCustomVariantShorthand
{
type Context = CssFormatContext;
#[inline(always)]
fn fmt(
&self,
node: &biome_css_syntax::TwCustomVariantShorthand,
f: &mut CssFormatter,
) -> FormatResult<()> {
FormatNodeRule::<biome_css_syntax::TwCustomVariantShorthand>::fmt(self, node, f)
}
}
impl AsFormat<CssFormatContext> for biome_css_syntax::TwCustomVariantShorthand {
type Format<'a> = FormatRefWithRule<
'a,
biome_css_syntax::TwCustomVariantShorthand,
crate::tailwind::auxiliary::custom_variant_shorthand::FormatTwCustomVariantShorthand,
>;
fn format(&self) -> Self::Format<'_> {
FormatRefWithRule :: new (self , crate :: tailwind :: auxiliary :: custom_variant_shorthand :: FormatTwCustomVariantShorthand :: default ())
}
}
impl IntoFormat<CssFormatContext> for biome_css_syntax::TwCustomVariantShorthand {
type Format = FormatOwnedWithRule<
biome_css_syntax::TwCustomVariantShorthand,
crate::tailwind::auxiliary::custom_variant_shorthand::FormatTwCustomVariantShorthand,
>;
fn into_format(self) -> Self::Format {
FormatOwnedWithRule :: new (self , crate :: tailwind :: auxiliary :: custom_variant_shorthand :: FormatTwCustomVariantShorthand :: default ())
}
}
impl FormatRule<biome_css_syntax::TwFunctionalUtilityName>
for crate::tailwind::auxiliary::functional_utility_name::FormatTwFunctionalUtilityName
{
type Context = CssFormatContext;
#[inline(always)]
fn fmt(
&self,
node: &biome_css_syntax::TwFunctionalUtilityName,
f: &mut CssFormatter,
) -> FormatResult<()> {
FormatNodeRule::<biome_css_syntax::TwFunctionalUtilityName>::fmt(self, node, f)
}
}
impl AsFormat<CssFormatContext> for biome_css_syntax::TwFunctionalUtilityName {
type Format<'a> = FormatRefWithRule<
'a,
biome_css_syntax::TwFunctionalUtilityName,
crate::tailwind::auxiliary::functional_utility_name::FormatTwFunctionalUtilityName,
>;
fn format(&self) -> Self::Format<'_> {
FormatRefWithRule :: new (self , crate :: tailwind :: auxiliary :: functional_utility_name :: FormatTwFunctionalUtilityName :: default ())
}
}
impl IntoFormat<CssFormatContext> for biome_css_syntax::TwFunctionalUtilityName {
type Format = FormatOwnedWithRule<
biome_css_syntax::TwFunctionalUtilityName,
crate::tailwind::auxiliary::functional_utility_name::FormatTwFunctionalUtilityName,
>;
fn into_format(self) -> Self::Format {
FormatOwnedWithRule :: new (self , crate :: tailwind :: auxiliary :: functional_utility_name :: FormatTwFunctionalUtilityName :: default ())
}
}
impl FormatRule<biome_css_syntax::TwPluginAtRule>
for crate::tailwind::statements::plugin_at_rule::FormatTwPluginAtRule
{
type Context = CssFormatContext;
#[inline(always)]
fn fmt(
&self,
node: &biome_css_syntax::TwPluginAtRule,
f: &mut CssFormatter,
) -> FormatResult<()> {
FormatNodeRule::<biome_css_syntax::TwPluginAtRule>::fmt(self, node, f)
}
}
impl AsFormat<CssFormatContext> for biome_css_syntax::TwPluginAtRule {
type Format<'a> = FormatRefWithRule<
'a,
biome_css_syntax::TwPluginAtRule,
crate::tailwind::statements::plugin_at_rule::FormatTwPluginAtRule,
>;
fn format(&self) -> Self::Format<'_> {
FormatRefWithRule::new(
self,
crate::tailwind::statements::plugin_at_rule::FormatTwPluginAtRule::default(),
)
}
}
impl IntoFormat<CssFormatContext> for biome_css_syntax::TwPluginAtRule {
type Format = FormatOwnedWithRule<
biome_css_syntax::TwPluginAtRule,
crate::tailwind::statements::plugin_at_rule::FormatTwPluginAtRule,
>;
fn into_format(self) -> Self::Format {
FormatOwnedWithRule::new(
self,
crate::tailwind::statements::plugin_at_rule::FormatTwPluginAtRule::default(),
)
}
}
impl FormatRule<biome_css_syntax::TwReferenceAtRule>
for crate::tailwind::statements::reference_at_rule::FormatTwReferenceAtRule
{
type Context = CssFormatContext;
#[inline(always)]
fn fmt(
&self,
node: &biome_css_syntax::TwReferenceAtRule,
f: &mut CssFormatter,
) -> FormatResult<()> {
FormatNodeRule::<biome_css_syntax::TwReferenceAtRule>::fmt(self, node, f)
}
}
impl AsFormat<CssFormatContext> for biome_css_syntax::TwReferenceAtRule {
type Format<'a> = FormatRefWithRule<
'a,
biome_css_syntax::TwReferenceAtRule,
crate::tailwind::statements::reference_at_rule::FormatTwReferenceAtRule,
>;
fn format(&self) -> Self::Format<'_> {
FormatRefWithRule::new(
self,
crate::tailwind::statements::reference_at_rule::FormatTwReferenceAtRule::default(),
)
}
}
impl IntoFormat<CssFormatContext> for biome_css_syntax::TwReferenceAtRule {
type Format = FormatOwnedWithRule<
biome_css_syntax::TwReferenceAtRule,
crate::tailwind::statements::reference_at_rule::FormatTwReferenceAtRule,
>;
fn into_format(self) -> Self::Format {
FormatOwnedWithRule::new(
self,
crate::tailwind::statements::reference_at_rule::FormatTwReferenceAtRule::default(),
)
}
}
impl FormatRule<biome_css_syntax::TwSourceAtRule>
for crate::tailwind::statements::source_at_rule::FormatTwSourceAtRule
{
type Context = CssFormatContext;
#[inline(always)]
fn fmt(
&self,
node: &biome_css_syntax::TwSourceAtRule,
f: &mut CssFormatter,
) -> FormatResult<()> {
FormatNodeRule::<biome_css_syntax::TwSourceAtRule>::fmt(self, node, f)
}
}
impl AsFormat<CssFormatContext> for biome_css_syntax::TwSourceAtRule {
type Format<'a> = FormatRefWithRule<
'a,
biome_css_syntax::TwSourceAtRule,
crate::tailwind::statements::source_at_rule::FormatTwSourceAtRule,
>;
fn format(&self) -> Self::Format<'_> {
FormatRefWithRule::new(
self,
crate::tailwind::statements::source_at_rule::FormatTwSourceAtRule::default(),
)
}
}
impl IntoFormat<CssFormatContext> for biome_css_syntax::TwSourceAtRule {
type Format = FormatOwnedWithRule<
biome_css_syntax::TwSourceAtRule,
crate::tailwind::statements::source_at_rule::FormatTwSourceAtRule,
>;
fn into_format(self) -> Self::Format {
FormatOwnedWithRule::new(
self,
crate::tailwind::statements::source_at_rule::FormatTwSourceAtRule::default(),
)
}
}
impl FormatRule<biome_css_syntax::TwThemeAtRule>
for crate::tailwind::statements::theme_at_rule::FormatTwThemeAtRule
{
type Context = CssFormatContext;
#[inline(always)]
fn fmt(
&self,
node: &biome_css_syntax::TwThemeAtRule,
f: &mut CssFormatter,
) -> FormatResult<()> {
FormatNodeRule::<biome_css_syntax::TwThemeAtRule>::fmt(self, node, f)
}
}
impl AsFormat<CssFormatContext> for biome_css_syntax::TwThemeAtRule {
type Format<'a> = FormatRefWithRule<
'a,
biome_css_syntax::TwThemeAtRule,
crate::tailwind::statements::theme_at_rule::FormatTwThemeAtRule,
>;
fn format(&self) -> Self::Format<'_> {
FormatRefWithRule::new(
self,
crate::tailwind::statements::theme_at_rule::FormatTwThemeAtRule::default(),
)
}
}
impl IntoFormat<CssFormatContext> for biome_css_syntax::TwThemeAtRule {
type Format = FormatOwnedWithRule<
biome_css_syntax::TwThemeAtRule,
crate::tailwind::statements::theme_at_rule::FormatTwThemeAtRule,
>;
fn into_format(self) -> Self::Format {
FormatOwnedWithRule::new(
self,
crate::tailwind::statements::theme_at_rule::FormatTwThemeAtRule::default(),
)
}
}
impl FormatRule<biome_css_syntax::TwUtilityAtRule>
for crate::tailwind::statements::utility_at_rule::FormatTwUtilityAtRule
{
type Context = CssFormatContext;
#[inline(always)]
fn fmt(
&self,
node: &biome_css_syntax::TwUtilityAtRule,
f: &mut CssFormatter,
) -> FormatResult<()> {
FormatNodeRule::<biome_css_syntax::TwUtilityAtRule>::fmt(self, node, f)
}
}
impl AsFormat<CssFormatContext> for biome_css_syntax::TwUtilityAtRule {
type Format<'a> = FormatRefWithRule<
'a,
biome_css_syntax::TwUtilityAtRule,
crate::tailwind::statements::utility_at_rule::FormatTwUtilityAtRule,
>;
fn format(&self) -> Self::Format<'_> {
FormatRefWithRule::new(
self,
crate::tailwind::statements::utility_at_rule::FormatTwUtilityAtRule::default(),
)
}
}
impl IntoFormat<CssFormatContext> for biome_css_syntax::TwUtilityAtRule {
type Format = FormatOwnedWithRule<
biome_css_syntax::TwUtilityAtRule,
crate::tailwind::statements::utility_at_rule::FormatTwUtilityAtRule,
>;
fn into_format(self) -> Self::Format {
FormatOwnedWithRule::new(
self,
crate::tailwind::statements::utility_at_rule::FormatTwUtilityAtRule::default(),
)
}
}
impl FormatRule<biome_css_syntax::TwValueThemeReference>
for crate::tailwind::auxiliary::value_theme_reference::FormatTwValueThemeReference
{
type Context = CssFormatContext;
#[inline(always)]
fn fmt(
&self,
node: &biome_css_syntax::TwValueThemeReference,
f: &mut CssFormatter,
) -> FormatResult<()> {
FormatNodeRule::<biome_css_syntax::TwValueThemeReference>::fmt(self, node, f)
}
}
impl AsFormat<CssFormatContext> for biome_css_syntax::TwValueThemeReference {
type Format<'a> = FormatRefWithRule<
'a,
biome_css_syntax::TwValueThemeReference,
crate::tailwind::auxiliary::value_theme_reference::FormatTwValueThemeReference,
>;
fn format(&self) -> Self::Format<'_> {
FormatRefWithRule::new(
self,
crate::tailwind::auxiliary::value_theme_reference::FormatTwValueThemeReference::default(
),
)
}
}
impl IntoFormat<CssFormatContext> for biome_css_syntax::TwValueThemeReference {
type Format = FormatOwnedWithRule<
biome_css_syntax::TwValueThemeReference,
crate::tailwind::auxiliary::value_theme_reference::FormatTwValueThemeReference,
>;
fn into_format(self) -> Self::Format {
FormatOwnedWithRule::new(
self,
crate::tailwind::auxiliary::value_theme_reference::FormatTwValueThemeReference::default(
),
)
}
}
impl FormatRule<biome_css_syntax::TwVariantAtRule>
for crate::tailwind::statements::variant_at_rule::FormatTwVariantAtRule
{
type Context = CssFormatContext;
#[inline(always)]
fn fmt(
&self,
node: &biome_css_syntax::TwVariantAtRule,
f: &mut CssFormatter,
) -> FormatResult<()> {
FormatNodeRule::<biome_css_syntax::TwVariantAtRule>::fmt(self, node, f)
}
}
impl AsFormat<CssFormatContext> for biome_css_syntax::TwVariantAtRule {
type Format<'a> = FormatRefWithRule<
'a,
biome_css_syntax::TwVariantAtRule,
crate::tailwind::statements::variant_at_rule::FormatTwVariantAtRule,
>;
fn format(&self) -> Self::Format<'_> {
FormatRefWithRule::new(
self,
crate::tailwind::statements::variant_at_rule::FormatTwVariantAtRule::default(),
)
}
}
impl IntoFormat<CssFormatContext> for biome_css_syntax::TwVariantAtRule {
type Format = FormatOwnedWithRule<
biome_css_syntax::TwVariantAtRule,
crate::tailwind::statements::variant_at_rule::FormatTwVariantAtRule,
>;
fn into_format(self) -> Self::Format {
FormatOwnedWithRule::new(
self,
crate::tailwind::statements::variant_at_rule::FormatTwVariantAtRule::default(),
)
}
}
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

🧩 Analysis chain

Tailwind at‑rules wiring looks consistent; please verify formatter types + Default.

All good to me. Given this is generated, just make sure:

  • each crate::tailwind::…::FormatTw* exists and implements Default;
  • codegen output is run through rustfmt (per repo guidelines), or the generator formats on emit.

If rustfmt isn’t already applied to generated outputs, consider running it post‑generation to keep diffs tidy.

Run to sanity‑check the formatter types:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Verify Tailwind formatter structs/types exist"
rg -nP --type=rust \
  '(\bstruct|\btype)\s+FormatTw(?:ApplyAtRule|ConfigAtRule|CustomVariantAtRule|PluginAtRule|ReferenceAtRule|SourceAtRule|ThemeAtRule|UtilityAtRule)\b' \
  -C2 crates/biome_css_formatter

echo "Verify Default impls (expect matches)"
rg -nP --type=rust \
  'impl\s+Default\s+for\s+FormatTw(?:ApplyAtRule|ConfigAtRule|CustomVariantAtRule|PluginAtRule|ReferenceAtRule|SourceAtRule|ThemeAtRule|UtilityAtRule)\b' \
  crates/biome_css_formatter || true

echo "Verify auxiliary Tailwind formatters"
rg -nP --type=rust \
  'FormatTw(?:CustomVariantShorthand|FunctionalUtilityName|ValueThemeReference|VariantAtRule)\b' \
  -C1 crates/biome_css_formatter

echo "Verify list/any formatters referenced here"
rg -nP --type=rust \
  'FormatTwApplyClassList|FormatAnyTwCustomVariantSelector|FormatAnyTwUtilityName' \
  -C1 crates/biome_css_formatter

Length of output: 17964


Generated file needs a rustfmt pass for formatting consistency.

The wiring is sound: all FormatTw* and FormatAnyTw* types exist and implement Default correctly across the tailwind module structure. However, the generated code at lines 5633, 5642, 5665, 5674, 9552–9585 has inconsistent spacing around :: and function calls (e.g., FormatRefWithRule :: new (self , …)), which differs from properly formatted blocks elsewhere.

Run rustfmt on crates/biome_css_formatter/src/generated.rs to normalise the formatting.

🤖 Prompt for AI Agents
In crates/biome_css_formatter/src/generated.rs around lines 5505-5944
(specifically address the misformatted spots at ~5633, 5642, 5665, 5674 and
9552–9585), the generated code contains inconsistent spacing around :: and
function call tokens (e.g., "FormatRefWithRule :: new (self , ...)" ) which
breaks formatting consistency; run rustfmt on this file (cargo fmt or rustfmt
crates/biome_css_formatter/src/generated.rs) to reformat the entire file so the
spacing around ::, function calls, and commas matches the project's rustfmt
rules and remove the stray spaces.

Comment on lines +141 to +151
if !p.at(T![apply]) {
return Absent;
}

let m = p.start();
p.bump_with_context(T![apply], CssLexContext::TailwindUtility);
ApplyClassList.parse_list(p);
p.expect(T![;]);

Present(m.complete(p, TW_APPLY_AT_RULE))
}
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

Reject empty @apply lists

Currently @apply; parses. Enforce at least one class and raise a diagnostic on empty lists.

 pub(crate) fn parse_apply_at_rule(p: &mut CssParser) -> ParsedSyntax {
@@
-    ApplyClassList.parse_list(p);
+    let list = ApplyClassList.parse_list(p);
+    if list.kind() == Absent {
+        p.error(expected_identifier(p, p.cur_range()));
+        // keep parsing to recover at the following ';'
+    }
     p.expect(T![;]);

Also applies to: 153-181

Comment on lines 161 to 164
fn is_at_generic_property(p: &mut CssParser) -> bool {
is_at_identifier(p) && p.nth_at(1, T![:])
is_at_identifier(p)
&& (p.nth_at(1, T![:]) || (p.nth_at(1, T![-]) && p.nth_at(2, T![*]) && p.nth_at(3, T![:])))
}
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

Limit the “-*:” lookahead to dashed identifiers to avoid false positives

As written, non‑dashed names like “color-*:” satisfy the lookahead, but the parser then expects “:” immediately after the identifier and errors. Tighten the branch to dashed identifiers only.

Apply:

 fn is_at_generic_property(p: &mut CssParser) -> bool {
-    is_at_identifier(p)
-        && (p.nth_at(1, T![:]) || (p.nth_at(1, T![-]) && p.nth_at(2, T![*]) && p.nth_at(3, T![:])))
+    is_at_identifier(p)
+        && (p.nth_at(1, T![:])
+            || (is_at_dashed_identifier(p)
+                && p.nth_at(1, T![-])
+                && p.nth_at(2, T![*])
+                && p.nth_at(3, T![:])))
 }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In crates/biome_css_parser/src/syntax/property/mod.rs around lines 161–164, the
second lookahead branch currently matches sequences like "color-*:" and produces
false positives; change it so the "-*:" lookahead only runs for dashed
identifiers. Concretely, require that the identifier token begins with a leading
'-' (a dash-ident) before checking for the subsequent '-' '*' ':' pattern (or
equivalently check nth tokens for a leading T![-] then an ident and then the '*'
and ':' at the correct offsets), then perform the existing nth_at checks with
the adjusted indices so only dashed identifiers trigger the "-*:" path.

Comment on lines +263 to +276
/// Parses theme references: --tab-size-*
pub(crate) fn parse_tailwind_value_theme_reference(p: &mut CssParser) -> ParsedSyntax {
if !is_at_dashed_identifier(p) {
return Absent;
}

let m = p.start();

parse_dashed_identifier(p).ok();
p.expect(T![-]);
p.expect(T![*]);

Present(m.complete(p, TW_VALUE_THEME_REFERENCE))
}
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

Avoid bogus TW_VALUE_THEME_REFERENCE on partial matches; add lookahead (+ optional feature gate)

Current code emits diagnostics and still completes a theme reference when only a dashed ident is present. Add a precise lookahead for “- *” (and consider gating on Tailwind being enabled).

Apply:

-pub(crate) fn parse_tailwind_value_theme_reference(p: &mut CssParser) -> ParsedSyntax {
-    if !is_at_dashed_identifier(p) {
-        return Absent;
-    }
-
-    let m = p.start();
-
-    parse_dashed_identifier(p).ok();
-    p.expect(T![-]);
-    p.expect(T![*]);
-
-    Present(m.complete(p, TW_VALUE_THEME_REFERENCE))
-}
+pub(crate) fn parse_tailwind_value_theme_reference(p: &mut CssParser) -> ParsedSyntax {
+    if !(/* optional: */ p.options().is_tailwind_directives_enabled()
+        && is_at_dashed_identifier(p)
+        && p.nth_at(1, T![-])
+        && p.nth_at(2, T![*]))
+    {
+        return Absent;
+    }
+
+    let m = p.start();
+    parse_dashed_identifier(p).ok();
+    // Pre-checked: consume without emitting diagnostics
+    p.bump(T![-]);
+    p.bump(T![*]);
+    Present(m.complete(p, TW_VALUE_THEME_REFERENCE))
+}

Committable suggestion skipped: line range outside the PR's diff.

@ematipico ematipico merged commit 3ea8cbd into main Oct 23, 2025
28 of 30 checks passed
@ematipico ematipico deleted the next branch October 23, 2025 10:03
@ematipico ematipico restored the next branch October 23, 2025 10:35
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-LSP Area: language server protocol A-Parser Area: parser A-Project Area: project A-Resolver Area: resolver A-Tooling Area: internal tools L-CSS Language: CSS L-Grit Language: GritQL L-HTML Language: HTML 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.