Skip to content

chore: abstract html tag matcher (main into next)#9993

Merged
Netail merged 17 commits intonextfrom
chore/rework-is-html-tag-next
Apr 16, 2026
Merged

chore: abstract html tag matcher (main into next)#9993
Netail merged 17 commits intonextfrom
chore/rework-is-html-tag-next

Conversation

@Netail
Copy link
Copy Markdown
Member

@Netail Netail commented Apr 15, 2026

Summary

Merge main into next with #9988 (chore: abstract html tag matcher) changes

See 043b9d7 for changes on the next branch

Test Plan

Docs

siketyan and others added 8 commits April 15, 2026 01:43
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
…tribute (#9743)

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
@Netail Netail marked this pull request as draft April 15, 2026 09:52
@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 15, 2026

🦋 Changeset detected

Latest commit: c7ee874

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

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

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 L-JavaScript Language: JavaScript and super languages L-CSS Language: CSS and super languages L-HTML Language: HTML and super languages L-Markdown Language: Markdown labels Apr 15, 2026
@Netail Netail changed the title fix(parser/css): nested selectors in embedded snippets (#9981) chore: abstract html tag matcher Apr 15, 2026
@Netail Netail force-pushed the chore/rework-is-html-tag-next branch from 92fa6a9 to d1cc208 Compare April 15, 2026 09:55
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq bot commented Apr 15, 2026

Merging this PR will degrade performance by 63.94%

⚡ 3 improved benchmarks
❌ 8 regressed benchmarks
✅ 238 untouched benchmarks

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

Performance Changes

Benchmark BASE HEAD Efficiency
css_analyzer[pure_9395922602181450299.css] 24 ms 27.3 ms -12.16%
css_analyzer[tachyons_11778168428173736564.css] 179.9 ms 199 ms -9.57%
css_analyzer[foundation_11602414662825430680.css] 242.5 ms 280.4 ms -13.52%
html_analyzer[index_1033418810622582172.html] 474.9 µs 967.7 µs -50.93%
html_analyzer[real/wikipedia-fr-Guerre_de_Canudos.html] 468.1 ms 1,054.6 ms -55.61%
html_analyzer[real/wikipedia-JavaScript.html] 192.8 ms 534.7 ms -63.94%
html_analyzer[real/wikipedia-Unix.html] 169.6 ms 441.3 ms -61.56%
execute_optimized[or_pattern] 589.9 µs 547.4 µs +7.77%
execute_optimized[where_clause] 454.3 µs 411.7 µs +10.34%
execute_optimized[code_snippet] 453.3 µs 415.8 µs +9.03%
grit_query_anchor_kinds[metavariable] 1.3 µs 1.4 µs -6.32%

Comparing chore/rework-is-html-tag-next (c7ee874) with next (308b559)

Open in CodSpeed

@Netail Netail force-pushed the chore/rework-is-html-tag-next branch from d1cc208 to 043b9d7 Compare April 15, 2026 09:58
@siketyan
Copy link
Copy Markdown
Member

@Netail Please don't forget to use merge commit instead of squash merge when merging this pr. Using squash merge here will lead us to the conflict resolution hell...

@Netail
Copy link
Copy Markdown
Member Author

Netail commented Apr 15, 2026

@Netail Please don't forget to use merge commit instead of squash merge when merging this pr. Using squash merge here will lead us to the conflict resolution hell...

Yup, I am familiar with that hahaa

@Netail Netail marked this pull request as ready for review April 15, 2026 10:04
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 15, 2026

Walkthrough

This PR applies a broad set of changes across the workspace: many .changeset entries were removed/added and package versions bumped to 2.4.12/2.0.49 where applicable; HTML a11y lints were refactored to use a new AnyHtmlTagElement API and a shared is_html_tag helper; element_ext gained tag-focused helpers; the CSS lexer/parser received a major scan-cursor refactor to handle SCSS interpolation and URL bodies; Markdown formatter/parser gained new trim/inside-list behaviours and re-lexing for fenced-code info strings; Svelte/Vue script-tag regexes were broadened; numerous tests were added.

Possibly related PRs

Suggested reviewers

  • dyc3
  • ematipico
  • chansuke
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/rework-is-html-tag-next

Copy link
Copy Markdown
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: 9

🧹 Nitpick comments (4)
crates/biome_markdown_parser/tests/quick_test.rs (1)

15-18: Use assertion macros directly and drop debug noise in committed tests

Line 15-18 can be simplified to a single assert! with a message; this keeps failures crisp and avoids noisy debug output in test logs.

Suggested tidy-up
-    dbg!(&syntax, root.diagnostics(), root.has_errors());
-    if has_bogus_nodes_or_empty_slots(&syntax) {
-        panic!("modified tree has bogus nodes or empty slots:\n{syntax:#?} \n\n {syntax}")
-    }
+    assert!(
+        !has_bogus_nodes_or_empty_slots(&syntax),
+        "modified tree has bogus nodes or empty slots:\n{syntax:#?}\n\n{syntax}"
+    );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_markdown_parser/tests/quick_test.rs` around lines 15 - 18,
Replace the dbg! and manual panic with a single assertion: call
assert!(!has_bogus_nodes_or_empty_slots(&syntax), "...") so the test fails
cleanly; include a concise failure message that interpolates relevant debugging
info such as syntax, root.diagnostics(), and root.has_errors() to preserve
context. Locate the block using has_bogus_nodes_or_empty_slots and syntax in
quick_test.rs and change the dbg!/panic sequence to one assert! that negates
has_bogus_nodes_or_empty_slots and contains the diagnostic info.
crates/biome_markdown_formatter/src/shared.rs (1)

74-80: Optional: complete the convenience-constructor set.

Now that trim_start() / trim_all() exist, consider adding helpers for the other trim modes too (e.g. auto-link-like, normalise-words) to avoid mixed constructor styles across call sites.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_markdown_formatter/src/shared.rs` around lines 74 - 80, Add the
missing convenience constructors for the remaining Trim modes to match
trim_start() and trim_all(): implement pub(crate) const fn trim_end(),
pub(crate) const fn trim_auto_link_like(), and pub(crate) const fn
trim_normalise_words() (or similarly named) that each return
Self::Trim(TrimMode::End), Self::Trim(TrimMode::AutoLinkLike), and
Self::Trim(TrimMode::NormaliseWords) respectively so call sites can use a
consistent constructor style alongside trim_start() and trim_all().
crates/biome_html_analyze/src/lint/a11y/no_distracting_elements.rs (1)

54-71: Works correctly, though query type could be narrowed for consistency.

The conversion from AnyHtmlElement to AnyHtmlTagElement via as_any_html_tag_element() is functional. Other rules in this PR (e.g., no_header_scope, no_redundant_roles) narrowed their query type directly to Ast<AnyHtmlTagElement>. Consider aligning for uniformity, though this isn't blocking.

♻️ Optional: Narrow query type for consistency
 impl Rule for NoDistractingElements {
-    type Query = Ast<AnyHtmlElement>;
+    type Query = Ast<AnyHtmlTagElement>;
     type State = TokenText;
     type Signals = Option<Self::State>;
     type Options = NoDistractingElementsOptions;

     fn run(ctx: &RuleContext<Self>) -> Self::Signals {
-        let element = ctx.query();
+        let tag_element = ctx.query();
         let source_type = ctx.source_type::<HtmlFileSource>();

-        let tag_element = element.clone().as_any_html_tag_element()?;
         let element_name = tag_element.tag_name()?;

This would also require updating diagnostic and action to use the new query type.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_html_analyze/src/lint/a11y/no_distracting_elements.rs` around
lines 54 - 71, The rule currently uses Query = Ast<AnyHtmlElement> and then
downcasts in run() with as_any_html_tag_element(); change Query to
Ast<AnyHtmlTagElement> to avoid the cast and make the rule consistent with
others (e.g., no_header_scope, no_redundant_roles), and update the
signatures/types for diagnostic and action to accept the new query type as well
(adjust any references to element.clone().as_any_html_tag_element() within
run(), diagnostic, and action to use the tag element directly).
crates/biome_html_analyze/src/lint/nursery/no_inline_styles.rs (1)

112-118: Consider using ? instead of .unwrap().

Per project guidelines, unwrap() should be avoided in rule code. While it's safe here (action only runs after run found the attribute), using ? would be more idiomatic:

♻️ Proposed refactor
         match node {
             AnyHtmlTagElement::HtmlOpeningElement(opening_element) => {
-                mutation.remove_node(find_style_attribute(opening_element.attributes()).unwrap())
+                mutation.remove_node(find_style_attribute(opening_element.attributes())?)
             }
             AnyHtmlTagElement::HtmlSelfClosingElement(self_closing_element) => mutation
-                .remove_node(find_style_attribute(self_closing_element.attributes()).unwrap()),
+                .remove_node(find_style_attribute(self_closing_element.attributes())?),
         }

Based on learnings: "Avoid unwrap() and expect() in rule code that could panic; use combinators like ?, map, or let else instead".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_html_analyze/src/lint/nursery/no_inline_styles.rs` around lines
112 - 118, The code currently calls unwrap() on find_style_attribute(...) inside
the match arms (handling AnyHtmlTagElement::HtmlOpeningElement and
::HtmlSelfClosingElement) which can panic; replace these unwraps with safe
handling—e.g., use if let Some(attr) =
find_style_attribute(opening_element.attributes()) { mutation.remove_node(attr)
} (and similarly for self_closing_element) or propagate the Option/Result from
the surrounding action/run function so you can use the ? operator; specifically
update the match that calls
mutation.remove_node(find_style_attribute(...).unwrap()) to safely handle the
Option returned by find_style_attribute instead of unwrapping.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.changeset/frogs-like-green.md:
- Line 5: Fix the typo in the changeset description by replacing the misspelled
rule name "useIfameSandbox" with the correct "useIframeSandbox" so the visible
rule list matches the actual rule link and improves discoverability; update the
string that lists the rules (the entry currently reading useIfameSandbox) to
useIframeSandbox.
- Line 5: The release note and implementation are out of sync: the rules (e.g.,
files use_alt_text.rs and use_iframe_title.rs) still call text_trimmed_range(),
which reports the full element range; update the rule code to report the opening
tag range instead (replace text_trimmed_range() with the project’s helper that
returns the opening tag range — e.g., opening_tag_range() or the equivalent
range_for_opening_tag helper used elsewhere) for the rules mentioned
(use_alt_text, use_button_type, use_html_lang, use_iframe_title,
use_valid_aria_role, use_iframe_sandbox), or alternatively update the changeset
text to state that diagnostics report on the full element; ensure all diagnostic
creation sites (where text_trimmed_range() is used) are adjusted consistently.

In `@crates/biome_css_parser/src/lib.rs`:
- Around line 336-354: Add a non-styled control assertion alongside the existing
styled case in the
test_styled_embedding_accepts_nested_selectors_without_ampersand test: for each
sample CSS string call parse_css twice — once with
CssFileSource::css().with_embedding_kind(EmbeddingKind::Styled) (existing) and
once with plain CssFileSource::css() (the control) — and assert that
parse.has_errors() is false for both parses; reference the existing test
function name, the parse_css call, CssFileSource::css(), and
EmbeddingKind::Styled so you update the same test to include the plain/css
control to lock the boundary.

In `@crates/biome_html_analyze/src/a11y_tests.rs`:
- Around line 7-15: parse_first_element currently finds the first AnyHtmlElement
descendant and then unwraps it as an AnyHtmlTagElement, which can panic if the
first descendant is text or a non-tag element; update parse_first_element to
filter or map descendants to only tag nodes before unwrapping (e.g. use
descendants().find_map(|n| AnyHtmlTagElement::cast(n)) or call
as_any_html_tag_element() in a filter_map) so that the function only returns a
true AnyHtmlTagElement and skips non-tag AnyHtmlElement variants.

In `@crates/biome_js_analyze/src/lint/nursery/no_jsx_namespace.rs`:
- Line 39: The nursery lint's metadata currently hardcodes version: "2.4.12"
inside the declare_lint_rule! macro for the no_jsx_namespace rule; on the next
branch update that value to "next" so the rule metadata stays current at release
time—locate the declare_lint_rule! invocation for the no_jsx_namespace rule and
change the version field from "2.4.12" to "next".

In `@crates/biome_js_analyze/src/lint/nursery/use_vars_on_top.rs`:
- Line 51: The rule declaration for the nursery lint should use the release-time
placeholder version; find the declare_lint_rule! invocation for the
use_vars_on_top rule (look for declare_lint_rule! and the rule identifier
use_vars_on_top) and change the field from version: "2.4.12" back to version:
"next" so the macro uses the next-release version string.

In `@crates/biome_markdown_parser/tests/quick_test.rs`:
- Around line 4-6: The test quick_test is marked #[ignore] so CI doesn't run it;
either remove the #[ignore] attribute from the quick_test function to enable the
existing test with stable expectations, or instead convert this ad-hoc test into
proper parser fixtures/snapshots under the repo's parser tests (e.g., move its
input/output cases into tests/specs and add stable assertions for valid and
error cases); update the quick_test or new fixture tests to assert deterministic
outputs and remove any flaky behavior so CI covers the parser change.

In `@packages/`@biomejs/biome/CHANGELOG.md:
- Line 42: Update the release note sentence that currently reads "Biome now
emits a `warning` diagnostic when a file exceed the `files.maxSize` limit."
(associated with PR `#9980` / commit `098f1ff`) to correct the grammar by changing
"exceed" to "exceeds" so the sentence reads "Biome now emits a `warning`
diagnostic when a file exceeds the `files.maxSize` limit."
- Line 48: Fix the grammar in the CHANGLEOG entry referencing PR `#9902` / commit
3f4d103 by changing the phrase "files that contains scripts and styles" to
"files that contain scripts and styles" so the subject-verb agreement is correct
in the sentence describing the `lint --write` idempotency fix for HTML-ish files
containing scripts and styles.

---

Nitpick comments:
In `@crates/biome_html_analyze/src/lint/a11y/no_distracting_elements.rs`:
- Around line 54-71: The rule currently uses Query = Ast<AnyHtmlElement> and
then downcasts in run() with as_any_html_tag_element(); change Query to
Ast<AnyHtmlTagElement> to avoid the cast and make the rule consistent with
others (e.g., no_header_scope, no_redundant_roles), and update the
signatures/types for diagnostic and action to accept the new query type as well
(adjust any references to element.clone().as_any_html_tag_element() within
run(), diagnostic, and action to use the tag element directly).

In `@crates/biome_html_analyze/src/lint/nursery/no_inline_styles.rs`:
- Around line 112-118: The code currently calls unwrap() on
find_style_attribute(...) inside the match arms (handling
AnyHtmlTagElement::HtmlOpeningElement and ::HtmlSelfClosingElement) which can
panic; replace these unwraps with safe handling—e.g., use if let Some(attr) =
find_style_attribute(opening_element.attributes()) { mutation.remove_node(attr)
} (and similarly for self_closing_element) or propagate the Option/Result from
the surrounding action/run function so you can use the ? operator; specifically
update the match that calls
mutation.remove_node(find_style_attribute(...).unwrap()) to safely handle the
Option returned by find_style_attribute instead of unwrapping.

In `@crates/biome_markdown_formatter/src/shared.rs`:
- Around line 74-80: Add the missing convenience constructors for the remaining
Trim modes to match trim_start() and trim_all(): implement pub(crate) const fn
trim_end(), pub(crate) const fn trim_auto_link_like(), and pub(crate) const fn
trim_normalise_words() (or similarly named) that each return
Self::Trim(TrimMode::End), Self::Trim(TrimMode::AutoLinkLike), and
Self::Trim(TrimMode::NormaliseWords) respectively so call sites can use a
consistent constructor style alongside trim_start() and trim_all().

In `@crates/biome_markdown_parser/tests/quick_test.rs`:
- Around line 15-18: Replace the dbg! and manual panic with a single assertion:
call assert!(!has_bogus_nodes_or_empty_slots(&syntax), "...") so the test fails
cleanly; include a concise failure message that interpolates relevant debugging
info such as syntax, root.diagnostics(), and root.has_errors() to preserve
context. Locate the block using has_bogus_nodes_or_empty_slots and syntax in
quick_test.rs and change the dbg!/panic sequence to one assert! that negates
has_bogus_nodes_or_empty_slots and contains the diagnostic info.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a50801b3-1d2f-4040-83b6-6831dd32a8f0

📥 Commits

Reviewing files that changed from the base of the PR and between 308b559 and 043b9d7.

⛔ Files ignored due to path filters (49)
  • crates/biome_cli/tests/snapshots/main_cases_handle_astro_files/full_support.snap is excluded by !**/*.snap and included by **
  • crates/biome_cli/tests/snapshots/main_cases_handle_svelte_files/format_svelte_ts_generics_files.snap is excluded by !**/*.snap and included by **
  • crates/biome_cli/tests/snapshots/main_cases_handle_svelte_files/format_svelte_ts_generics_files_write.snap is excluded by !**/*.snap and included by **
  • crates/biome_cli/tests/snapshots/main_cases_handle_svelte_files/full_support.snap is excluded by !**/*.snap and included by **
  • crates/biome_cli/tests/snapshots/main_cases_handle_svelte_files/full_support_ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_cli/tests/snapshots/main_cases_handle_vue_files/full_support.snap is excluded by !**/*.snap and included by **
  • crates/biome_cli/tests/snapshots/main_cases_handle_vue_files/full_support_ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/a11y/noAriaHiddenOnFocusable/astro/invalid.astro.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/a11y/noAriaHiddenOnFocusable/invalid.html.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/a11y/noAriaHiddenOnFocusable/svelte/invalid.svelte.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/a11y/noAriaHiddenOnFocusable/vue/invalid.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/a11y/noAriaUnsupportedElements/astro/invalid.astro.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/a11y/noAriaUnsupportedElements/invalid.html.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/a11y/noAriaUnsupportedElements/svelte/invalid.svelte.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/a11y/noAriaUnsupportedElements/vue/invalid.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/a11y/useAltText/invalid.html.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/a11y/useAriaActivedescendantWithTabindex/invalid.html.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/a11y/useButtonType/invalid.html.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/a11y/useButtonType/invalid.svelte.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/a11y/useButtonType/invalid.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/a11y/useHtmlLang/invalid.html.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/a11y/useIframeTitle/astro/invalid.astro.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/a11y/useIframeTitle/invalid.html.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/a11y/useIframeTitle/svelte/invalid.svelte.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/a11y/useIframeTitle/vue/invalid.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/a11y/useKeyWithMouseEvents/astro/invalid.astro.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/a11y/useKeyWithMouseEvents/invalid.html.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/a11y/useKeyWithMouseEvents/svelte/invalid.svelte.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/a11y/useKeyWithMouseEvents/vue/invalid.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/a11y/useValidAriaRole/invalid.html.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/nursery/useIframeSandbox/invalid.html.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/issue9744.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_markdown_formatter/tests/specs/markdown/fenced_code_block.md.snap is excluded by !**/*.snap and included by **
  • crates/biome_markdown_formatter/tests/specs/markdown/fenced_code_block_info_string.md.snap is excluded by !**/*.snap and included by **
  • crates/biome_markdown_formatter/tests/specs/prettier/markdown/spec/example-104.md.snap is excluded by !**/*.snap and included by **
  • crates/biome_markdown_formatter/tests/specs/prettier/markdown/spec/example-106.md.snap is excluded by !**/*.snap and included by **
  • crates/biome_markdown_formatter/tests/specs/prettier/markdown/spec/example-108.md.snap is excluded by !**/*.snap and included by **
  • crates/biome_markdown_formatter/tests/specs/prettier/markdown/spec/example-110.md.snap is excluded by !**/*.snap and included by **
  • crates/biome_markdown_formatter/tests/specs/prettier/markdown/spec/example-197.md.snap is excluded by !**/*.snap and included by **
  • crates/biome_markdown_formatter/tests/specs/prettier/markdown/spec/example-292.md.snap is excluded by !**/*.snap and included by **
  • crates/biome_markdown_formatter/tests/specs/prettier/markdown/spec/example-297.md.snap is excluded by !**/*.snap and included by **
  • crates/biome_markdown_formatter/tests/specs/prettier/markdown/spec/example-307.md.snap is excluded by !**/*.snap and included by **
  • crates/biome_markdown_formatter/tests/specs/prettier/markdown/spec/example-88.md.snap is excluded by !**/*.snap and included by **
  • crates/biome_markdown_formatter/tests/specs/prettier/markdown/spec/example-90.md.snap is excluded by !**/*.snap and included by **
  • crates/biome_markdown_formatter/tests/specs/prettier/markdown/spec/example-92.md.snap is excluded by !**/*.snap and included by **
  • crates/biome_markdown_formatter/tests/specs/prettier/markdown/spec/example-93.md.snap is excluded by !**/*.snap and included by **
  • crates/biome_markdown_formatter/tests/specs/prettier/markdown/spec/example-94.md.snap is excluded by !**/*.snap and included by **
  • crates/biome_markdown_formatter/tests/specs/prettier/markdown/spec/example-95.md.snap is excluded by !**/*.snap and included by **
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml and included by **
📒 Files selected for processing (139)
  • .changeset/add-no-identical-test-title.md
  • .changeset/clear-bananas-lie.md
  • .changeset/dry-dryers-flash.md
  • .changeset/every-poets-throw.md
  • .changeset/fair-otters-count.md
  • .changeset/fair-snails-applaud.md
  • .changeset/fair-snails-peel.md
  • .changeset/famous-zebras-end.md
  • .changeset/fast-zebras-move.md
  • .changeset/fix-no-floating-promises-generic-wrappers.md
  • .changeset/fluffy-ways-punch.md
  • .changeset/forty-lemons-stick.md
  • .changeset/four-forks-smile.md
  • .changeset/fresh-coins-do.md
  • .changeset/fresh-years-serve.md
  • .changeset/frogs-like-green.md
  • .changeset/giant-bags-wave.md
  • .changeset/khaki-mangos-sparkle.md
  • .changeset/large-buses-stick.md
  • .changeset/loose-adults-show.md
  • .changeset/many-snakes-wonder.md
  • .changeset/mighty-mayflies-rest.md
  • .changeset/mighty-news-taste.md
  • .changeset/odd-wasps-fly.md
  • .changeset/plain-keys-arrive.md
  • .changeset/rare-schools-raise.md
  • .changeset/rude-apples-prove.md
  • .changeset/sad-buttons-kneel.md
  • .changeset/smooth-ideas-write.md
  • .changeset/stale-ties-report.md
  • .changeset/tender-chicken-swim.md
  • .changeset/three-peaches-dress.md
  • .changeset/tidy-bags-draw.md
  • .changeset/use-reduce-type-parameter-1774115911.md
  • .changeset/violet-turtles-smile.md
  • .changeset/wise-frogs-pretend.md
  • .changeset/yellow-fans-glow.md
  • .changeset/young-tips-swim.md
  • crates/biome_cli/tests/cases/handle_svelte_files.rs
  • crates/biome_css_analyze/src/lint/nursery/no_excessive_selector_classes.rs
  • crates/biome_css_parser/src/lib.rs
  • crates/biome_css_parser/src/syntax/block/declaration_or_rule_list_block.rs
  • crates/biome_html_analyze/src/a11y.rs
  • crates/biome_html_analyze/src/a11y_tests.rs
  • crates/biome_html_analyze/src/lib.rs
  • crates/biome_html_analyze/src/lint/a11y/no_aria_hidden_on_focusable.rs
  • crates/biome_html_analyze/src/lint/a11y/no_aria_unsupported_elements.rs
  • crates/biome_html_analyze/src/lint/a11y/no_autofocus.rs
  • crates/biome_html_analyze/src/lint/a11y/no_distracting_elements.rs
  • crates/biome_html_analyze/src/lint/a11y/no_header_scope.rs
  • crates/biome_html_analyze/src/lint/a11y/no_positive_tabindex.rs
  • crates/biome_html_analyze/src/lint/a11y/no_redundant_alt.rs
  • crates/biome_html_analyze/src/lint/a11y/no_redundant_roles.rs
  • crates/biome_html_analyze/src/lint/a11y/no_svg_without_title.rs
  • crates/biome_html_analyze/src/lint/a11y/use_alt_text.rs
  • crates/biome_html_analyze/src/lint/a11y/use_anchor_content.rs
  • crates/biome_html_analyze/src/lint/a11y/use_aria_activedescendant_with_tabindex.rs
  • crates/biome_html_analyze/src/lint/a11y/use_aria_props_for_role.rs
  • crates/biome_html_analyze/src/lint/a11y/use_button_type.rs
  • crates/biome_html_analyze/src/lint/a11y/use_heading_content.rs
  • crates/biome_html_analyze/src/lint/a11y/use_html_lang.rs
  • crates/biome_html_analyze/src/lint/a11y/use_iframe_title.rs
  • crates/biome_html_analyze/src/lint/a11y/use_key_with_mouse_events.rs
  • crates/biome_html_analyze/src/lint/a11y/use_media_caption.rs
  • crates/biome_html_analyze/src/lint/a11y/use_valid_aria_role.rs
  • crates/biome_html_analyze/src/lint/a11y/use_valid_lang.rs
  • crates/biome_html_analyze/src/lint/nursery/no_ambiguous_anchor_text.rs
  • crates/biome_html_analyze/src/lint/nursery/no_inline_styles.rs
  • crates/biome_html_analyze/src/lint/nursery/no_script_url.rs
  • crates/biome_html_analyze/src/lint/nursery/no_sync_scripts.rs
  • crates/biome_html_analyze/src/lint/nursery/use_iframe_sandbox.rs
  • crates/biome_html_analyze/src/lint/nursery/use_scoped_styles.rs
  • crates/biome_html_analyze/src/lint/nursery/use_vue_hyphenated_attributes.rs
  • crates/biome_html_analyze/src/lint/nursery/use_vue_valid_template_root.rs
  • crates/biome_html_analyze/src/lint/nursery/use_vue_vapor.rs
  • crates/biome_html_analyze/src/utils.rs
  • crates/biome_html_syntax/src/element_ext.rs
  • crates/biome_js_analyze/src/assist/source/organize_imports.rs
  • crates/biome_js_analyze/src/lint/correctness/use_exhaustive_dependencies.rs
  • crates/biome_js_analyze/src/lint/nursery/no_component_hook_factories.rs
  • crates/biome_js_analyze/src/lint/nursery/no_identical_test_title.rs
  • crates/biome_js_analyze/src/lint/nursery/no_jsx_namespace.rs
  • crates/biome_js_analyze/src/lint/nursery/use_iframe_sandbox.rs
  • crates/biome_js_analyze/src/lint/nursery/use_react_async_server_function.rs
  • crates/biome_js_analyze/src/lint/nursery/use_reduce_type_parameter.rs
  • crates/biome_js_analyze/src/lint/nursery/use_string_starts_ends_with.rs
  • crates/biome_js_analyze/src/lint/nursery/use_vars_on_top.rs
  • crates/biome_js_analyze/src/react/hooks.rs
  • crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/issue9744.ts
  • crates/biome_markdown_formatter/src/markdown/auxiliary/fenced_code_block.rs
  • crates/biome_markdown_formatter/src/markdown/auxiliary/hard_line.rs
  • crates/biome_markdown_formatter/src/markdown/auxiliary/header.rs
  • crates/biome_markdown_formatter/src/markdown/auxiliary/indent_token.rs
  • crates/biome_markdown_formatter/src/markdown/auxiliary/inline_image.rs
  • crates/biome_markdown_formatter/src/markdown/auxiliary/inline_link.rs
  • crates/biome_markdown_formatter/src/markdown/auxiliary/link_destination.rs
  • crates/biome_markdown_formatter/src/markdown/auxiliary/link_title.rs
  • crates/biome_markdown_formatter/src/markdown/auxiliary/reference_link.rs
  • crates/biome_markdown_formatter/src/markdown/auxiliary/setext_header.rs
  • crates/biome_markdown_formatter/src/markdown/auxiliary/textual.rs
  • crates/biome_markdown_formatter/src/markdown/lists/code_name_list.rs
  • crates/biome_markdown_formatter/src/markdown/lists/inline_item_list.rs
  • crates/biome_markdown_formatter/src/shared.rs
  • crates/biome_markdown_formatter/tests/quick_test.rs
  • crates/biome_markdown_formatter/tests/specs/markdown/fenced_code_block_info_string.md
  • crates/biome_markdown_parser/src/lexer/mod.rs
  • crates/biome_markdown_parser/src/parser.rs
  • crates/biome_markdown_parser/src/syntax/fenced_code_block.rs
  • crates/biome_markdown_parser/tests/quick_test.rs
  • crates/biome_service/src/file_handlers/svelte.rs
  • crates/biome_service/src/file_handlers/vue.rs
  • crates/biome_service/src/workspace/server.tests.rs
  • packages/@biomejs/backend-jsonrpc/CHANGELOG.md
  • packages/@biomejs/backend-jsonrpc/package.json
  • packages/@biomejs/biome/CHANGELOG.md
  • packages/@biomejs/biome/package.json
  • packages/@biomejs/cli-darwin-arm64/CHANGELOG.md
  • packages/@biomejs/cli-darwin-arm64/package.json
  • packages/@biomejs/cli-darwin-x64/CHANGELOG.md
  • packages/@biomejs/cli-darwin-x64/package.json
  • packages/@biomejs/cli-linux-arm64-musl/CHANGELOG.md
  • packages/@biomejs/cli-linux-arm64-musl/package.json
  • packages/@biomejs/cli-linux-arm64/CHANGELOG.md
  • packages/@biomejs/cli-linux-arm64/package.json
  • packages/@biomejs/cli-linux-x64-musl/CHANGELOG.md
  • packages/@biomejs/cli-linux-x64-musl/package.json
  • packages/@biomejs/cli-linux-x64/CHANGELOG.md
  • packages/@biomejs/cli-linux-x64/package.json
  • packages/@biomejs/cli-win32-arm64/CHANGELOG.md
  • packages/@biomejs/cli-win32-arm64/package.json
  • packages/@biomejs/cli-win32-x64/CHANGELOG.md
  • packages/@biomejs/cli-win32-x64/package.json
  • packages/@biomejs/js-api/package.json
  • packages/@biomejs/wasm-bundler/CHANGELOG.md
  • packages/@biomejs/wasm-bundler/package.json
  • packages/@biomejs/wasm-nodejs/CHANGELOG.md
  • packages/@biomejs/wasm-nodejs/package.json
  • packages/@biomejs/wasm-web/CHANGELOG.md
  • packages/@biomejs/wasm-web/package.json
💤 Files with no reviewable changes (36)
  • .changeset/fair-snails-applaud.md
  • .changeset/clear-bananas-lie.md
  • .changeset/fair-otters-count.md
  • .changeset/every-poets-throw.md
  • .changeset/famous-zebras-end.md
  • .changeset/fair-snails-peel.md
  • .changeset/dry-dryers-flash.md
  • .changeset/fix-no-floating-promises-generic-wrappers.md
  • .changeset/forty-lemons-stick.md
  • .changeset/add-no-identical-test-title.md
  • .changeset/fresh-years-serve.md
  • .changeset/khaki-mangos-sparkle.md
  • .changeset/large-buses-stick.md
  • .changeset/mighty-mayflies-rest.md
  • .changeset/fluffy-ways-punch.md
  • .changeset/fresh-coins-do.md
  • .changeset/giant-bags-wave.md
  • .changeset/loose-adults-show.md
  • .changeset/odd-wasps-fly.md
  • .changeset/plain-keys-arrive.md
  • .changeset/four-forks-smile.md
  • .changeset/rare-schools-raise.md
  • .changeset/rude-apples-prove.md
  • .changeset/three-peaches-dress.md
  • .changeset/sad-buttons-kneel.md
  • .changeset/tender-chicken-swim.md
  • .changeset/wise-frogs-pretend.md
  • .changeset/stale-ties-report.md
  • .changeset/smooth-ideas-write.md
  • .changeset/use-reduce-type-parameter-1774115911.md
  • .changeset/tidy-bags-draw.md
  • .changeset/young-tips-swim.md
  • .changeset/violet-turtles-smile.md
  • .changeset/yellow-fans-glow.md
  • .changeset/fast-zebras-move.md
  • .changeset/many-snakes-wonder.md

Comment thread .changeset/frogs-like-green.md
Comment thread crates/biome_css_parser/src/lib.rs
Comment thread crates/biome_html_analyze/src/a11y_tests.rs
Comment thread crates/biome_js_analyze/src/lint/nursery/no_jsx_namespace.rs
Comment thread crates/biome_js_analyze/src/lint/nursery/use_vars_on_top.rs
Comment thread crates/biome_markdown_parser/tests/quick_test.rs
Comment thread packages/@biomejs/biome/CHANGELOG.md
Comment thread packages/@biomejs/biome/CHANGELOG.md
@Netail Netail changed the title chore: abstract html tag matcher chore: abstract html tag matcher (main into next) Apr 15, 2026
@Netail Netail requested review from a team April 15, 2026 13:04
@github-actions github-actions bot added A-Tooling Area: internal tools A-Diagnostic Area: diagnostocis A-Type-Inference Area: type inference labels Apr 16, 2026
Copy link
Copy Markdown
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

Caution

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

⚠️ Outside diff range comments (1)
crates/biome_markdown_formatter/src/markdown/auxiliary/fenced_code_block.rs (1)

37-85: ⚠️ Potential issue | 🟠 Major

The list-indent stripping is using the wrong baseline.

indent on the opener is already post-continuation, so removing indent.len() here strips real fence indentation. Reusing that same count for r_fence_indent then under-strips the closer, because the closer still carries the raw list-item indent. Based on learnings, check_continuation_indent already consumes the list continuation indent for the opening fence, while the closing fence still sees the raw line and must strip that indent itself.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_markdown_formatter/src/markdown/auxiliary/fenced_code_block.rs`
around lines 37 - 85, The current logic uses indent.len() as excess which is
wrong because indent (on the opener) is already post-continuation; instead
determine and use two separate counts: the continuation-consumed count for the
opening-fence removals and the raw list-item indent count for the closing-fence
removals. Concretely, update the code around indent and r_fence_indent so that
when inside_list you (a) remove only the number of continuation indent tokens
that check_continuation_indent consumed when writing the opener (use that
continuation_count instead of indent.len()), and (b) when stripping the closer
use the raw r_fence_indent token count
(r_fence_tokens.iter().take(raw_list_indent_count)) to remove the original
list-item spaces. Reference variables/functions: indent, r_fence_indent,
r_fence_tokens, inside_list, and check_continuation_indent.
🧹 Nitpick comments (4)
.claude/skills/formatter-development/SKILL.md (1)

3-3: Optional: Consider shortening the description field.

The description is comprehensive but quite verbose for YAML front matter. Whilst functional, a more concise version might improve readability. However, the detailed coverage is genuinely helpful, so this is entirely optional.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.claude/skills/formatter-development/SKILL.md at line 3, The YAML
front-matter description field is overly long; shorten the value of the
description key to a concise summary while retaining key scope (implementing
IR-based formatter rules, handling comments, tests, idempotency, and comparisons
to Prettier). Edit the description field in SKILL.md (the description: line) to
a one-sentence, compact phrase that captures those points without the long
explanatory list.
crates/biome_js_analyze/src/lint/nursery/use_regexp_test.rs (1)

164-173: RegExp(...) without new is not detected.

The is_regexp function handles JsNewExpression but not JsCallExpression. In JavaScript, RegExp("pattern") is equivalent to new RegExp("pattern"), so cases like:

if ("str".match(RegExp("hello"))) {}

won't trigger the rule.

🔧 Potential fix to also handle RegExp calls
 fn is_regexp(expr: &AnyJsExpression) -> bool {
     let expr = expr.clone().omit_parentheses();
     match expr {
         AnyJsExpression::AnyJsLiteralExpression(literal) => {
             literal.as_js_regex_literal_expression().is_some()
         }
         AnyJsExpression::JsNewExpression(new_expr) => is_regexp_constructor(&new_expr),
+        AnyJsExpression::JsCallExpression(call_expr) => is_regexp_call(&call_expr),
         _ => false,
     }
 }
+
+fn is_regexp_call(expr: &JsCallExpression) -> bool {
+    let Ok(callee) = expr.callee() else {
+        return false;
+    };
+    // Check for `RegExp(...)`, `window.RegExp(...)`, `globalThis.RegExp(...)`
+    match callee {
+        AnyJsExpression::JsIdentifierExpression(id) => id
+            .name()
+            .ok()
+            .and_then(|n| n.value_token().ok())
+            .is_some_and(|t| t.token_text_trimmed().text() == "RegExp"),
+        AnyJsExpression::JsStaticMemberExpression(member) => {
+            let object_is_global = member
+                .object()
+                .ok()
+                .and_then(|obj| obj.as_js_identifier_expression()?.name().ok()?.value_token().ok())
+                .is_some_and(|t| {
+                    let name = t.token_text_trimmed();
+                    name.text() == "window" || name.text() == "globalThis"
+                });
+            object_is_global
+                && member
+                    .member()
+                    .ok()
+                    .and_then(|m| m.as_js_name()?.value_token().ok())
+                    .is_some_and(|t| t.token_text_trimmed().text() == "RegExp")
+        }
+        _ => false,
+    }
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_js_analyze/src/lint/nursery/use_regexp_test.rs` around lines 164
- 173, The is_regexp function currently checks literals and JsNewExpression but
misses calls like RegExp("pat"); modify is_regexp to also match
AnyJsExpression::JsCallExpression(call) and detect RegExp calls by inspecting
the call.callee — treat simple identifier callee named "RegExp" (and any
equivalent member access you want to support) as a RegExp constructor call, or
reuse/extend the existing is_regexp_constructor logic to encapsulate that check
so is_regexp returns true for both JsNewExpression and JsCallExpression forms.
crates/biome_css_parser/src/syntax/block/declaration_or_rule_list_block.rs (2)

174-179: Subtle but correct logic for inner-block rejection.

The condition works because p.at(self.end_kind) handles reaching our }, whilst !p.at(T!['}']) rejects stopping at an inner block's brace.

For consistency with line 202 (which uses self.end_kind), you might consider:

♻️ Optional consistency tweak
-                let valid =
-                    (matches!(p.last(), Some(T![;])) && !p.at(T!['}'])) || p.at(self.end_kind);
+                let at_end = p.at(self.end_kind);
+                let valid = (matches!(p.last(), Some(T![;])) && !at_end) || at_end;

This makes it explicit that both clauses reference the same end condition.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_css_parser/src/syntax/block/declaration_or_rule_list_block.rs`
around lines 174 - 179, The validity check in declaration_or_rule_list_block.rs
uses !p.at(T!['}']) to reject stopping at an inner block but line 202 uses
self.end_kind; update the condition that defines valid (the expression using
matches!(p.last(), Some(T![;])) && !p.at(T!['}'])) to reference self.end_kind
instead of the hard-coded T!['}'] so both clauses consistently use self.end_kind
(refer to variables p, valid, and self.end_kind in the
declaration_or_rule_list_block logic).

56-61: LGTM!

Clean predicate composition. The three-way gate (!is_nesting_block, Styled, is_at_qualified_rule) neatly captures the styled embedding case.

Consider adding a one-liner doc comment if you'd like to aid future spelunkers:

📝 Optional doc comment
 #[inline]
+/// True when at a top-level qualified rule inside a styled embedding (e.g. `styled.div\`...\``).
 fn is_at_top_level_qualified_rule(p: &mut CssParser) -> bool {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_css_parser/src/syntax/block/declaration_or_rule_list_block.rs`
around lines 56 - 61, Add a concise one-line doc comment to explain the
predicate in is_at_top_level_qualified_rule: describe that it checks whether the
parser (CssParser) is at a qualified rule at the top-level of a styled embedding
by ensuring state().is_nesting_block is false, source_type.as_embedding_kind()
equals EmbeddingKind::Styled, and is_at_qualified_rule(p) is true; place this
comment immediately above the is_at_top_level_qualified_rule function to aid
future readers.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/biome_css_parser/src/lexer/mod.rs`:
- Around line 1353-1358: The EOF detail span can go out of bounds by using
self.position() + 1; change the span passed to ParseDiagnostic::with_detail so
its end is clamped to the file length (e.g. let end = (self.position() +
1).min(self.text_position()); use self.position()..end) so that at EOF you
produce a zero-width span; update the code around
ParseDiagnostic::new("Unterminated block comment",
start..self.text_position()).with_detail(...) to use that clamped span.

In `@crates/biome_css_parser/src/lexer/scan_cursor.rs`:
- Around line 755-765: The helper is_at_identifier_hyphen() currently uses
is_at_identifier_fragment_at(1) which only treats '-' as part of an identifier
when a new fragment starts; change it to check identifier continuation instead
so `-2x` is allowed after an interpolation. In practice, update the return
expression in is_at_identifier_hyphen() to call the continuation check (e.g.
is_at_identifier_continuation_at(1) or the existing continuation helper) rather
than is_at_identifier_fragment_at(1) so hyphen+digit sequences are kept as part
of the interpolated identifier.

In `@crates/biome_css_parser/src/lexer/source_cursor.rs`:
- Around line 24-27: The set_position() method currently only checks position <=
self.source.len(), which can break the UTF-8 boundary invariant relied on by
char_at() and current_char(); update set_position to also require that the new
position is a valid UTF-8 char boundary (e.g.,
self.source.is_char_boundary(position)) and fail fast if not (use assert! or
debug_assert! depending on desired release behavior) so position always points
to a real char boundary; reference the set_position(), char_at(),
current_char(), source, and position symbols when making this change.

In `@crates/biome_markdown_formatter/src/markdown/auxiliary/fenced_code_block.rs`:
- Around line 33-35: The code always builds normalized_fence with backticks,
which can break inputs that used tildes; determine the original fence character
and repeat that instead of hardcoding '`'. Use the existing
longest_fence_char_sequence helper for both ticks and tildes (e.g.,
longest_fence_char_sequence(node, '`') and longest_fence_char_sequence(node,
'~')) or read the opener character from the node if available (e.g.,
node.fence_char/opening_fence) to pick the fence char, compute fence_len =
(max_inner + 1).max(3) as before, and build normalized_fence by repeating the
chosen fence character fence_len times rather than always repeating '`'.

In `@crates/biome_markdown_formatter/src/markdown/lists/block_list.rs`:
- Around line 25-39: The loop only forwards inside_list to MdParagraph but not
to fenced code blocks, so locate the match over AnyMdBlock/AnyMdLeafBlock in
block_list.rs and add a branch for AnyMdLeafBlock::MdFencedCodeBlock that calls
the fenced block's formatter with its options (e.g.,
FormatMdFencedCodeBlockOptions { inside_list }) instead of the generic
node.format(), similar to how MdParagraph uses FormatMdParagraphOptions; ensure
the fenced-block branch uses joiner.entry(&fenced.format().with_options(...)) so
fenced code blocks receive the inside_list flag.

---

Outside diff comments:
In `@crates/biome_markdown_formatter/src/markdown/auxiliary/fenced_code_block.rs`:
- Around line 37-85: The current logic uses indent.len() as excess which is
wrong because indent (on the opener) is already post-continuation; instead
determine and use two separate counts: the continuation-consumed count for the
opening-fence removals and the raw list-item indent count for the closing-fence
removals. Concretely, update the code around indent and r_fence_indent so that
when inside_list you (a) remove only the number of continuation indent tokens
that check_continuation_indent consumed when writing the opener (use that
continuation_count instead of indent.len()), and (b) when stripping the closer
use the raw r_fence_indent token count
(r_fence_tokens.iter().take(raw_list_indent_count)) to remove the original
list-item spaces. Reference variables/functions: indent, r_fence_indent,
r_fence_tokens, inside_list, and check_continuation_indent.

---

Nitpick comments:
In @.claude/skills/formatter-development/SKILL.md:
- Line 3: The YAML front-matter description field is overly long; shorten the
value of the description key to a concise summary while retaining key scope
(implementing IR-based formatter rules, handling comments, tests, idempotency,
and comparisons to Prettier). Edit the description field in SKILL.md (the
description: line) to a one-sentence, compact phrase that captures those points
without the long explanatory list.

In `@crates/biome_css_parser/src/syntax/block/declaration_or_rule_list_block.rs`:
- Around line 174-179: The validity check in declaration_or_rule_list_block.rs
uses !p.at(T!['}']) to reject stopping at an inner block but line 202 uses
self.end_kind; update the condition that defines valid (the expression using
matches!(p.last(), Some(T![;])) && !p.at(T!['}'])) to reference self.end_kind
instead of the hard-coded T!['}'] so both clauses consistently use self.end_kind
(refer to variables p, valid, and self.end_kind in the
declaration_or_rule_list_block logic).
- Around line 56-61: Add a concise one-line doc comment to explain the predicate
in is_at_top_level_qualified_rule: describe that it checks whether the parser
(CssParser) is at a qualified rule at the top-level of a styled embedding by
ensuring state().is_nesting_block is false, source_type.as_embedding_kind()
equals EmbeddingKind::Styled, and is_at_qualified_rule(p) is true; place this
comment immediately above the is_at_top_level_qualified_rule function to aid
future readers.

In `@crates/biome_js_analyze/src/lint/nursery/use_regexp_test.rs`:
- Around line 164-173: The is_regexp function currently checks literals and
JsNewExpression but misses calls like RegExp("pat"); modify is_regexp to also
match AnyJsExpression::JsCallExpression(call) and detect RegExp calls by
inspecting the call.callee — treat simple identifier callee named "RegExp" (and
any equivalent member access you want to support) as a RegExp constructor call,
or reuse/extend the existing is_regexp_constructor logic to encapsulate that
check so is_regexp returns true for both JsNewExpression and JsCallExpression
forms.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 92353811-253c-4bb1-abae-b28078061a7f

📥 Commits

Reviewing files that changed from the base of the PR and between 043b9d7 and c7ee874.

⛔ Files ignored due to path filters (28)
  • Cargo.lock is excluded by !**/*.lock and included by **
  • crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs is excluded by !**/migrate/eslint_any_rule_to_biome.rs and included by **
  • crates/biome_configuration/src/analyzer/linter/rules.rs is excluded by !**/rules.rs and included by **
  • crates/biome_configuration/src/generated/linter_options_check.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_css_formatter/tests/specs/css/scss/at-rule/container-general-enclosed.scss.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_formatter/tests/specs/css/scss/at-rule/supports-general-enclosed.scss.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_formatter/tests/specs/css/scss/declaration/url-interpolated-functions.scss.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_parser/tests/css_test_suite/error/scss/value/url-interpolated-function-name.scss.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_parser/tests/css_test_suite/ok/scss/value/interpolated-minus-line-break.scss.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_parser/tests/css_test_suite/ok/scss/value/url.scss.snap is excluded by !**/*.snap and included by **
  • crates/biome_diagnostics_categories/src/categories.rs is excluded by !**/categories.rs and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useRegexpTest/invalid.js.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useRegexpTest/valid.js.snap is excluded by !**/*.snap and included by **
  • crates/biome_markdown_formatter/tests/specs/markdown/list_continuation.md.snap is excluded by !**/*.snap and included by **
  • crates/biome_markdown_formatter/tests/specs/markdown/ordered_lists.md.snap is excluded by !**/*.snap and included by **
  • crates/biome_markdown_formatter/tests/specs/prettier/markdown/blockquote/code.md.snap is excluded by !**/*.snap and included by **
  • crates/biome_markdown_formatter/tests/specs/prettier/markdown/blockquote/ignore-code.md.snap is excluded by !**/*.snap and included by **
  • crates/biome_markdown_formatter/tests/specs/prettier/markdown/break/simple.md.snap is excluded by !**/*.snap and included by **
  • crates/biome_markdown_formatter/tests/specs/prettier/markdown/code/additional-space.md.snap is excluded by !**/*.snap and included by **
  • crates/biome_markdown_formatter/tests/specs/prettier/markdown/footnoteDefinition/long.md.snap is excluded by !**/*.snap and included by **
  • crates/biome_markdown_formatter/tests/specs/prettier/markdown/list/indent.md.snap is excluded by !**/*.snap and included by **
  • crates/biome_markdown_formatter/tests/specs/prettier/markdown/list/issue-7846.md.snap is excluded by !**/*.snap and included by **
  • crates/biome_markdown_formatter/tests/specs/prettier/markdown/list/nested-checkbox.md.snap is excluded by !**/*.snap and included by **
  • crates/biome_markdown_formatter/tests/specs/prettier/markdown/spec/example-219.md.snap is excluded by !**/*.snap and included by **
  • crates/biome_markdown_formatter/tests/specs/prettier/markdown/spec/example-269.md.snap is excluded by !**/*.snap and included by **
  • packages/@biomejs/backend-jsonrpc/src/workspace.ts is excluded by !**/backend-jsonrpc/src/workspace.ts and included by **
  • packages/@biomejs/biome/configuration_schema.json is excluded by !**/configuration_schema.json and included by **
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml and included by **
📒 Files selected for processing (61)
  • .changeset/fix-issue-9994-styled-css.md
  • .changeset/hungry-meals-wash.md
  • .claude/skills/formatter-development/SKILL.md
  • .gitignore
  • crates/biome_css_formatter/src/css/auxiliary/url_function.rs
  • crates/biome_css_formatter/tests/specs/css/scss/declaration/url-interpolated-functions.scss
  • crates/biome_css_parser/src/lexer/mod.rs
  • crates/biome_css_parser/src/lexer/scan_cursor.rs
  • crates/biome_css_parser/src/lexer/source_cursor.rs
  • crates/biome_css_parser/src/lexer/tests.rs
  • crates/biome_css_parser/src/lib.rs
  • crates/biome_css_parser/src/syntax/block/declaration_or_rule_list_block.rs
  • crates/biome_css_parser/src/syntax/scss/mod.rs
  • crates/biome_css_parser/src/syntax/scss/value/function.rs
  • crates/biome_css_parser/src/syntax/scss/value/mod.rs
  • crates/biome_css_parser/src/syntax/value/function/call.rs
  • crates/biome_css_parser/src/syntax/value/function/mod.rs
  • crates/biome_css_parser/src/syntax/value/url.rs
  • crates/biome_css_parser/src/token_source.rs
  • crates/biome_css_parser/tests/css_test_suite/error/scss/value/url-interpolated-function-name.scss
  • crates/biome_css_parser/tests/css_test_suite/ok/scss/value/interpolated-minus-line-break.scss
  • crates/biome_css_parser/tests/css_test_suite/ok/scss/value/url.scss
  • crates/biome_html_formatter/src/html/any/attribute_initializer.rs
  • crates/biome_html_formatter/src/html/auxiliary/attribute_initializer_clause.rs
  • crates/biome_html_formatter/src/lib.rs
  • crates/biome_html_formatter/src/shared.rs
  • crates/biome_html_formatter/src/svelte/any/binding_property.rs
  • crates/biome_html_formatter/src/svelte/value/directive_value.rs
  • crates/biome_js_analyze/src/lint/nursery/use_regexp_test.rs
  • crates/biome_js_analyze/src/lint/style/use_explicit_length_check.rs
  • crates/biome_js_analyze/tests/specs/nursery/useRegexpTest/invalid.js
  • crates/biome_js_analyze/tests/specs/nursery/useRegexpTest/valid.js
  • crates/biome_js_syntax/src/expr_ext.rs
  • crates/biome_js_type_info/src/type.rs
  • crates/biome_markdown_formatter/src/markdown/auxiliary/autolink.rs
  • crates/biome_markdown_formatter/src/markdown/auxiliary/bullet.rs
  • crates/biome_markdown_formatter/src/markdown/auxiliary/document.rs
  • crates/biome_markdown_formatter/src/markdown/auxiliary/fenced_code_block.rs
  • crates/biome_markdown_formatter/src/markdown/auxiliary/header.rs
  • crates/biome_markdown_formatter/src/markdown/auxiliary/inline_code.rs
  • crates/biome_markdown_formatter/src/markdown/auxiliary/inline_image.rs
  • crates/biome_markdown_formatter/src/markdown/auxiliary/inline_link.rs
  • crates/biome_markdown_formatter/src/markdown/auxiliary/link_destination.rs
  • crates/biome_markdown_formatter/src/markdown/auxiliary/link_label.rs
  • crates/biome_markdown_formatter/src/markdown/auxiliary/link_title.rs
  • crates/biome_markdown_formatter/src/markdown/auxiliary/ordered_list_item.rs
  • crates/biome_markdown_formatter/src/markdown/auxiliary/paragraph.rs
  • crates/biome_markdown_formatter/src/markdown/auxiliary/quote.rs
  • crates/biome_markdown_formatter/src/markdown/auxiliary/reference_link.rs
  • crates/biome_markdown_formatter/src/markdown/auxiliary/setext_header.rs
  • crates/biome_markdown_formatter/src/markdown/auxiliary/textual.rs
  • crates/biome_markdown_formatter/src/markdown/lists/block_list.rs
  • crates/biome_markdown_formatter/src/markdown/lists/inline_item_list.rs
  • crates/biome_markdown_formatter/src/shared.rs
  • crates/biome_markdown_formatter/tests/quick_test.rs
  • crates/biome_markdown_formatter/tests/specs/markdown/list_continuation.md
  • crates/biome_markdown_formatter/tests/specs/markdown/ordered_lists.md
  • crates/biome_rule_options/src/lib.rs
  • crates/biome_rule_options/src/use_regexp_test.rs
  • crates/biome_service/src/workspace/server.tests.rs
  • xtask/codegen/markdown.ungram
💤 Files with no reviewable changes (2)
  • crates/biome_markdown_formatter/src/markdown/auxiliary/quote.rs
  • crates/biome_css_parser/src/syntax/value/function/call.rs
✅ Files skipped from review due to trivial changes (10)
  • .gitignore
  • crates/biome_markdown_formatter/src/markdown/auxiliary/reference_link.rs
  • .changeset/hungry-meals-wash.md
  • crates/biome_css_formatter/tests/specs/css/scss/declaration/url-interpolated-functions.scss
  • crates/biome_js_analyze/tests/specs/nursery/useRegexpTest/invalid.js
  • crates/biome_css_parser/src/syntax/scss/value/mod.rs
  • crates/biome_js_analyze/tests/specs/nursery/useRegexpTest/valid.js
  • xtask/codegen/markdown.ungram
  • .changeset/fix-issue-9994-styled-css.md
  • crates/biome_css_parser/tests/css_test_suite/ok/scss/value/interpolated-minus-line-break.scss
🚧 Files skipped from review as they are similar to previous changes (9)
  • crates/biome_markdown_formatter/src/markdown/auxiliary/inline_link.rs
  • crates/biome_markdown_formatter/src/markdown/auxiliary/setext_header.rs
  • crates/biome_markdown_formatter/src/markdown/auxiliary/link_destination.rs
  • crates/biome_markdown_formatter/src/markdown/auxiliary/inline_image.rs
  • crates/biome_markdown_formatter/src/markdown/auxiliary/header.rs
  • crates/biome_service/src/workspace/server.tests.rs
  • crates/biome_css_parser/src/lib.rs
  • crates/biome_markdown_formatter/tests/quick_test.rs
  • crates/biome_markdown_formatter/src/shared.rs

Comment thread crates/biome_css_parser/src/lexer/mod.rs
Comment thread crates/biome_css_parser/src/lexer/scan_cursor.rs
Comment thread crates/biome_css_parser/src/lexer/source_cursor.rs
Comment thread crates/biome_markdown_formatter/src/markdown/lists/block_list.rs
@Netail
Copy link
Copy Markdown
Member Author

Netail commented Apr 16, 2026

#9993 (comment)

Interesting, the HTML analyzer slowed down quite a bit, but not sure if that's because of my commit or coming from main

@ematipico
Copy link
Copy Markdown
Member

We've added a bunch rules, some are expensive. It's normal

Copy link
Copy Markdown
Member

@ematipico ematipico left a comment

Choose a reason for hiding this comment

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

@Netail do you have the rights to use the merge commit?

@Netail
Copy link
Copy Markdown
Member Author

Netail commented Apr 16, 2026

@Netail do you have the rights to use the merge commit?

oui

@Netail Netail merged commit 8bbf54b into next Apr 16, 2026
36 of 37 checks passed
@Netail Netail deleted the chore/rework-is-html-tag-next branch April 16, 2026 18:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-CLI Area: CLI A-Diagnostic Area: diagnostocis A-Formatter Area: formatter A-Linter Area: linter A-Parser Area: parser A-Project Area: project A-Tooling Area: internal tools A-Type-Inference Area: type inference L-CSS Language: CSS and super languages L-HTML Language: HTML and super languages L-JavaScript Language: JavaScript and super languages L-Markdown Language: Markdown

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants