Skip to content

chore(html): more html benchmarks#8153

Merged
dyc3 merged 1 commit intomainfrom
html-benches
Feb 20, 2026
Merged

chore(html): more html benchmarks#8153
dyc3 merged 1 commit intomainfrom
html-benches

Conversation

@dyc3
Copy link
Contributor

@dyc3 dyc3 commented Nov 18, 2025

Summary

I wanted to expand our HTML benchmarks to get better signals on parsing/formatting perf.

#6623 is preventing me from landing this.

most of the synthetic benchmarks are AI generated.

Test Plan

CI should be green.

Docs

@changeset-bot
Copy link

changeset-bot bot commented Nov 18, 2025

⚠️ No Changeset found

Latest commit: b26c17c

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

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

@github-actions github-actions bot added A-Parser Area: parser A-Formatter Area: formatter L-HTML Language: HTML and super languages labels Nov 18, 2025
@codspeed-hq
Copy link

codspeed-hq bot commented Feb 19, 2026

Merging this PR will not alter performance

✅ 4 untouched benchmarks
🆕 60 new benchmarks
⏩ 152 skipped benchmarks1

Performance Changes

Benchmark BASE HEAD Efficiency
🆕 synthetic/astro-components.astro[format] N/A 4.5 ms N/A
🆕 synthetic/vue-expressions.vue[format] N/A 2 ms N/A
🆕 synthetic/wide-siblings.html[format] N/A 8.3 ms N/A
🆕 synthetic/svelte-expressions.svelte[format] N/A 2.1 ms N/A
🆕 real/wikipedia-JavaScript.html[format] N/A 500.4 ms N/A
🆕 synthetic/vue-directives.vue[format] N/A 2.5 ms N/A
🆕 synthetic/vue-dynamic-args.vue[format] N/A 2.5 ms N/A
🆕 real/wikipedia-fr-Guerre_de_Canudos.html[format] N/A 1.4 s N/A
🆕 synthetic/mixed-content.html[format] N/A 6.9 ms N/A
🆕 synthetic/astro-expressions.astro[format] N/A 3.5 ms N/A
🆕 real/wikipedia-Unix.html[format] N/A 416.9 ms N/A
🆕 synthetic/svelte-snippets.svelte[format] N/A 3.4 ms N/A
🆕 synthetic/high-depth.html[format] N/A 2 ms N/A
🆕 synthetic/attribute-heavy.html[format] N/A 3.6 ms N/A
🆕 synthetic/comments.html[format] N/A 3 ms N/A
🆕 synthetic/svelte-directives.svelte[format] N/A 1.8 ms N/A
🆕 synthetic/svelte-control-flow.svelte[format] N/A 1.7 ms N/A
🆕 synthetic/void-elements.html[format] N/A 5.8 ms N/A
🆕 synthetic/tables.html[format] N/A 10.2 ms N/A
🆕 synthetic/long-attribute-values.html[format] N/A 1.3 ms N/A
... ... ... ... ...

ℹ️ Only the first 20 benchmarks are displayed. Go to the app to view all benchmarks.


Comparing html-benches (ef55847) with main (c0d4b0c)

Open in CodSpeed

Footnotes

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

@dyc3 dyc3 changed the base branch from main to graphite-base/8153 February 19, 2026 21:09
@dyc3 dyc3 force-pushed the graphite-base/8153 branch from 312b6db to 4027bb2 Compare February 19, 2026 21:09
@dyc3 dyc3 changed the base branch from graphite-base/8153 to dyc3/fix-use-keyword-in-html-content February 19, 2026 21:09
@dyc3 dyc3 marked this pull request as ready for review February 19, 2026 21:27
@dyc3 dyc3 changed the base branch from dyc3/fix-use-keyword-in-html-content to graphite-base/8153 February 19, 2026 22:29
@dyc3 dyc3 force-pushed the graphite-base/8153 branch from 4027bb2 to 5ef8c9a Compare February 19, 2026 22:30
@dyc3 dyc3 changed the base branch from graphite-base/8153 to dyc3/fix-use-keyword-in-html-content February 19, 2026 22:30
@dyc3 dyc3 force-pushed the dyc3/fix-use-keyword-in-html-content branch from 5ef8c9a to 5f731a2 Compare February 20, 2026 12:22
@dyc3 dyc3 force-pushed the dyc3/fix-use-keyword-in-html-content branch 2 times, most recently from cb3acb7 to 0cabd62 Compare February 20, 2026 13:01
@dyc3 dyc3 changed the base branch from dyc3/fix-use-keyword-in-html-content to graphite-base/8153 February 20, 2026 13:16
@dyc3 dyc3 force-pushed the graphite-base/8153 branch from 0cabd62 to c0d4b0c Compare February 20, 2026 13:17
@graphite-app graphite-app bot changed the base branch from graphite-base/8153 to main February 20, 2026 13:18
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 20, 2026

Walkthrough

Adds a vendored .gitattributes entry and introduces filesystem-driven benchmark fixtures for the HTML parser and formatter. Seventeen new synthetic fixture files (Astro, Svelte, Vue, and numerous HTML edge-case pages) were added under crates/biome_html_parser/benches/fixtures. Bench code in both parser and formatter now loads fixtures recursively at runtime, skipping Markdown, and runs uncached and cached benchmarks per fixture with diagnostic reporting. Global allocator selection blocks were added to the formatter bench. A dev-dependency on biome_string_case was added for fixture handling.

Suggested labels

A-Tooling

Suggested reviewers

  • ematipico
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'chore(html): more html benchmarks' accurately reflects the primary change—expanding HTML benchmark coverage to improve performance signals.
Description check ✅ Passed The description explains the motivation (expanding HTML benchmarks for better perf signals), discloses AI-generated synthetic benchmarks, and indicates CI validation is the test plan.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch html-benches

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: 2

🧹 Nitpick comments (2)
crates/biome_html_formatter/benches/html_formatter.rs (1)

8-62: Consider sorting fixtures for deterministic benchmark order.
read_dir order varies by platform, so a sort keeps IDs stable across runs.

♻️ Suggested tweak
     visit(&fixtures_root, &fixtures_root, &mut cases);
+    cases.sort_by(|a, b| a.0.cmp(&b.0).then_with(|| a.1.cmp(&b.1)));
     cases
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_html_formatter/benches/html_formatter.rs` around lines 8 - 62,
The benchmark fixture loading is non-deterministic because fs::read_dir
iteration order varies; in load_fixtures (and its nested visit function) sort
directory entries before processing and sort the final cases vector to ensure
stable ordering: after collecting entries in visit sort by path/file name (or
collect into a Vec, sort by path.display() or (group,name)), and before
returning from load_fixtures sort cases (e.g., by group then name) so benchmark
IDs remain stable across runs.
crates/biome_html_parser/benches/html_parser.rs (1)

133-149: Move diagnostics collection out of the timed loop.
Collecting diagnostics inside b.iter skews timings and can balloon if any fixture emits diagnostics. Pre‑parse once and log once, then benchmark clean parses.

♻️ Suggested refactor
-        let mut diagnostics = vec![];
         group.throughput(Throughput::Bytes(code.len() as u64));
@@
-        group.bench_with_input(BenchmarkId::new(&id, "uncached"), &code, |b, _| {
-            b.iter(|| {
-                let result = black_box(parse_html(code, HtmlParseOptions::from(&file_source)));
-                diagnostics.extend(result.into_diagnostics());
-            })
-        });
-
-        for diagnostic in diagnostics {
-            let diagnostic = diagnostic.with_file_source_code(code).with_file_path(&id);
-            println!("{}", print_diagnostic_to_string(&diagnostic));
-        }
+        let diagnostics = parse_html(code, HtmlParseOptions::from(&file_source)).into_diagnostics();
+        for diagnostic in diagnostics {
+            let diagnostic = diagnostic.with_file_source_code(code).with_file_path(&id);
+            println!("{}", print_diagnostic_to_string(&diagnostic));
+        }
+        group.bench_with_input(BenchmarkId::new(&id, "uncached"), &code, |b, _| {
+            b.iter(|| {
+                black_box(parse_html(code, HtmlParseOptions::from(&file_source)));
+            })
+        });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_html_parser/benches/html_parser.rs` around lines 133 - 149, The
benchmark currently extends diagnostics inside the timed closure (in the b.iter
|b, _| { ... } block) which skews measurements and can grow unbounded; instead,
call parse_html once before group.bench_with_input to collect and store
diagnostics (using HtmlFileSource::try_from_extension, HtmlParseOptions::from,
parse_html and result.into_diagnostics()), then use group.bench_with_input's
b.iter to repeatedly call parse_html for timing only and discard or black_box
the parse result without extending the diagnostics vec; ensure the pre-parse
uses the same code and options so the timed loop measures clean parses only.
🤖 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_html_parser/benches/fixtures/synthetic/svelte-directives.svelte`:
- Around line 1-156: The template references undeclared symbols actionParam,
actionConfig, and handleLongPress; add declarations to the <script> block:
declare sensible default values for actionParam and actionConfig (e.g., let
actionParam = null; let actionConfig = {}; or appropriate types used by
action2/action3) and implement a handleLongPress function (e.g., function
handleLongPress(event, params) { /* noop or set state like longpress handling */
}) so that actionParam, actionConfig, and handleLongPress are defined and used
by use:action2, use:action3, and use:longpress respectively; ensure names match
exactly as referenced in the template.

In `@crates/biome_html_parser/benches/fixtures/synthetic/vue-dynamic-args.vue`:
- Around line 39-167: There is a duplicate const declaration for clickEvent;
rename the second script variable (the one used for the table) to a unique name
(e.g., tableClickEvent) and update the template binding that uses it (the table
element with @[clickEvent].stop="onTableClick") to use the new name
@[tableClickEvent]. Ensure you change the declaration (const clickEvent =
ref('click') → const tableClickEvent = ref('click')) and any template references
that pointed to the second clickEvent.

---

Nitpick comments:
In `@crates/biome_html_formatter/benches/html_formatter.rs`:
- Around line 8-62: The benchmark fixture loading is non-deterministic because
fs::read_dir iteration order varies; in load_fixtures (and its nested visit
function) sort directory entries before processing and sort the final cases
vector to ensure stable ordering: after collecting entries in visit sort by
path/file name (or collect into a Vec, sort by path.display() or (group,name)),
and before returning from load_fixtures sort cases (e.g., by group then name) so
benchmark IDs remain stable across runs.

In `@crates/biome_html_parser/benches/html_parser.rs`:
- Around line 133-149: The benchmark currently extends diagnostics inside the
timed closure (in the b.iter |b, _| { ... } block) which skews measurements and
can grow unbounded; instead, call parse_html once before group.bench_with_input
to collect and store diagnostics (using HtmlFileSource::try_from_extension,
HtmlParseOptions::from, parse_html and result.into_diagnostics()), then use
group.bench_with_input's b.iter to repeatedly call parse_html for timing only
and discard or black_box the parse result without extending the diagnostics vec;
ensure the pre-parse uses the same code and options so the timed loop measures
clean parses only.

Comment on lines +1 to +156
<script>
let value = '';
let checked = false;
let selected = '';
let files = null;
let group = [];
let innerWidth = 0;
let innerHeight = 0;
let online = true;
let visible = true;
let fullscreenElement = null;
let className = 'default';
let color = '#ff0000';
let fontSize = '16px';
let opacity = 1;
let transform = 'none';
let mounted = false;
let hovered = false;
let focused = false;
let validated = false;
let inputRef;
let selectRef;
let boxRef;
let boxWidth;
let boxHeight;
let offsetWidth;
let offsetHeight;
let scrollY;
let isIndeterminate = false;
let currentTime = 0;
let duration = 0;
let paused = true;
let volume = 1;
let muted = false;
let playbackRate = 1;
let active = false;
let items = [
{ id: 1, name: 'A', visible: false },
{ id: 2, name: 'B', visible: false },
{ id: 3, name: 'C', visible: false },
];

function handleInput() {}
function handleChange() {}
function handleKeyPress() {}
function handleFirstKey() {}
function handleKeyUp() {}
function action1(node) {}
function action2(node, param) {}
function action3(node, config) {}
function toggleCheck() {}
function handleSelect() {}
function handleFileSelect() {}
function handleGroupChange() {}
function fade(node, params) {}
function fly(node, params) {}
function slide(node, params) {}
function flip(node, params) {}
function tooltip(node, params) {}
function clickOutside(node, handler) {}
function longpress(node, params) {}
function handleBoxClick() {}
function handleDoubleClick() {}
function scale(node, params) {}
function blur(node, params) {}
function inView(node, params) {}
function videoAction(node) {}
function audioEnhancer(node) {}
function handleTimeUpdate() {}
function handleAudioPlay() {}
function handleAudioPause() {}
</script>

<svelte:window bind:innerWidth bind:innerHeight bind:online />
<svelte:document bind:visibilityState={visible} bind:fullscreenElement />
<svelte:body bind:scrollY />

<div class="directives-container">
<input
type="text"
bind:value
bind:this={inputRef}
class:active={focused}
class:invalid={!validated}
style:color
style:font-size={fontSize}
style:opacity={`${opacity}%`}
style:transform
on:input={handleInput}
on:change={handleChange}
on:focus={() => focused = true}
on:blur={() => focused = false}
on:keypress|preventDefault|stopPropagation={handleKeyPress}
on:keydown|once={handleFirstKey}
on:keyup|self={handleKeyUp}
use:action1
use:action2={actionParam}
use:action3|param1|param2={actionConfig}
/>

<input
type="checkbox"
bind:checked
bind:indeterminate={isIndeterminate}
on:click|preventDefault={toggleCheck}
class:checked
class:indeterminate={isIndeterminate}
/>

<select
bind:value={selected}
bind:this={selectRef}
on:change|stopPropagation={handleSelect}
class:has-value={selected !== ''}
>
<option value="">Select...</option>
<option value="a">Option A</option>
<option value="b">Option B</option>
</select>

<input
type="file"
bind:files
multiple
accept="image/*"
on:change|preventDefault={handleFileSelect}
/>

{#each ['a', 'b', 'c', 'd', 'e'] as option}
<label>
<input
type="checkbox"
bind:group
value={option}
on:change|stopPropagation={handleGroupChange}
/>
<span class:selected={group.includes(option)}>{option}</span>
</label>
{/each}

<div
class="animated-box"
class:mounted
class:hovered
class:visible
style:background-color={color}
style:border-color={checked ? 'green' : 'red'}
style:display={visible ? 'block' : 'none'}
in:fade={{ duration: 300 }}
out:fly={{ y: 20, duration: 200 }}
transition:slide|local={{ duration: 400 }}
animate:flip={{ duration: 250 }}
use:tooltip={{ text: 'This is a tooltip' }}
use:clickOutside={() => (active = false)}
use:longpress|preventDefault={handleLongPress}
on:mouseenter={() => hovered = true}
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

🧩 Analysis chain

🏁 Script executed:

# Check for definitions and references of the three identifiers
echo "=== Searching for actionParam, actionConfig, handleLongPress ==="
rg -n 'actionParam|actionConfig|handleLongPress' crates/biome_html_parser/benches/fixtures/synthetic/svelte-directives.svelte

Repository: biomejs/biome

Length of output: 257


Add the missing variable and function declarations in the script block.

actionParam, actionConfig, and handleLongPress are referenced in the template but not defined in the script section. Even though this is a synthetic benchmark fixture, keeping it clean and compilable makes it a better example.

Suggested fix
   let items = [
     { id: 1, name: 'A', visible: false },
     { id: 2, name: 'B', visible: false },
     { id: 3, name: 'C', visible: false },
   ];
+  let actionParam = null;
+  let actionConfig = {};

   function longpress(node, params) {}
+  function handleLongPress() {}
📝 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
<script>
let value = '';
let checked = false;
let selected = '';
let files = null;
let group = [];
let innerWidth = 0;
let innerHeight = 0;
let online = true;
let visible = true;
let fullscreenElement = null;
let className = 'default';
let color = '#ff0000';
let fontSize = '16px';
let opacity = 1;
let transform = 'none';
let mounted = false;
let hovered = false;
let focused = false;
let validated = false;
let inputRef;
let selectRef;
let boxRef;
let boxWidth;
let boxHeight;
let offsetWidth;
let offsetHeight;
let scrollY;
let isIndeterminate = false;
let currentTime = 0;
let duration = 0;
let paused = true;
let volume = 1;
let muted = false;
let playbackRate = 1;
let active = false;
let items = [
{ id: 1, name: 'A', visible: false },
{ id: 2, name: 'B', visible: false },
{ id: 3, name: 'C', visible: false },
];
function handleInput() {}
function handleChange() {}
function handleKeyPress() {}
function handleFirstKey() {}
function handleKeyUp() {}
function action1(node) {}
function action2(node, param) {}
function action3(node, config) {}
function toggleCheck() {}
function handleSelect() {}
function handleFileSelect() {}
function handleGroupChange() {}
function fade(node, params) {}
function fly(node, params) {}
function slide(node, params) {}
function flip(node, params) {}
function tooltip(node, params) {}
function clickOutside(node, handler) {}
function longpress(node, params) {}
function handleBoxClick() {}
function handleDoubleClick() {}
function scale(node, params) {}
function blur(node, params) {}
function inView(node, params) {}
function videoAction(node) {}
function audioEnhancer(node) {}
function handleTimeUpdate() {}
function handleAudioPlay() {}
function handleAudioPause() {}
</script>
<svelte:window bind:innerWidth bind:innerHeight bind:online />
<svelte:document bind:visibilityState={visible} bind:fullscreenElement />
<svelte:body bind:scrollY />
<div class="directives-container">
<input
type="text"
bind:value
bind:this={inputRef}
class:active={focused}
class:invalid={!validated}
style:color
style:font-size={fontSize}
style:opacity={`${opacity}%`}
style:transform
on:input={handleInput}
on:change={handleChange}
on:focus={() => focused = true}
on:blur={() => focused = false}
on:keypress|preventDefault|stopPropagation={handleKeyPress}
on:keydown|once={handleFirstKey}
on:keyup|self={handleKeyUp}
use:action1
use:action2={actionParam}
use:action3|param1|param2={actionConfig}
/>
<input
type="checkbox"
bind:checked
bind:indeterminate={isIndeterminate}
on:click|preventDefault={toggleCheck}
class:checked
class:indeterminate={isIndeterminate}
/>
<select
bind:value={selected}
bind:this={selectRef}
on:change|stopPropagation={handleSelect}
class:has-value={selected !== ''}
>
<option value="">Select...</option>
<option value="a">Option A</option>
<option value="b">Option B</option>
</select>
<input
type="file"
bind:files
multiple
accept="image/*"
on:change|preventDefault={handleFileSelect}
/>
{#each ['a', 'b', 'c', 'd', 'e'] as option}
<label>
<input
type="checkbox"
bind:group
value={option}
on:change|stopPropagation={handleGroupChange}
/>
<span class:selected={group.includes(option)}>{option}</span>
</label>
{/each}
<div
class="animated-box"
class:mounted
class:hovered
class:visible
style:background-color={color}
style:border-color={checked ? 'green' : 'red'}
style:display={visible ? 'block' : 'none'}
in:fade={{ duration: 300 }}
out:fly={{ y: 20, duration: 200 }}
transition:slide|local={{ duration: 400 }}
animate:flip={{ duration: 250 }}
use:tooltip={{ text: 'This is a tooltip' }}
use:clickOutside={() => (active = false)}
use:longpress|preventDefault={handleLongPress}
on:mouseenter={() => hovered = true}
<script>
let value = '';
let checked = false;
let selected = '';
let files = null;
let group = [];
let innerWidth = 0;
let innerHeight = 0;
let online = true;
let visible = true;
let fullscreenElement = null;
let className = 'default';
let color = '#ff0000';
let fontSize = '16px';
let opacity = 1;
let transform = 'none';
let mounted = false;
let hovered = false;
let focused = false;
let validated = false;
let inputRef;
let selectRef;
let boxRef;
let boxWidth;
let boxHeight;
let offsetWidth;
let offsetHeight;
let scrollY;
let isIndeterminate = false;
let currentTime = 0;
let duration = 0;
let paused = true;
let volume = 1;
let muted = false;
let playbackRate = 1;
let active = false;
let items = [
{ id: 1, name: 'A', visible: false },
{ id: 2, name: 'B', visible: false },
{ id: 3, name: 'C', visible: false },
];
let actionParam = null;
let actionConfig = {};
function handleInput() {}
function handleChange() {}
function handleKeyPress() {}
function handleFirstKey() {}
function handleKeyUp() {}
function action1(node) {}
function action2(node, param) {}
function action3(node, config) {}
function toggleCheck() {}
function handleSelect() {}
function handleFileSelect() {}
function handleGroupChange() {}
function fade(node, params) {}
function fly(node, params) {}
function slide(node, params) {}
function flip(node, params) {}
function tooltip(node, params) {}
function clickOutside(node, handler) {}
function longpress(node, params) {}
function handleLongPress() {}
function handleBoxClick() {}
function handleDoubleClick() {}
function scale(node, params) {}
function blur(node, params) {}
function inView(node, params) {}
function videoAction(node) {}
function audioEnhancer(node) {}
function handleTimeUpdate() {}
function handleAudioPlay() {}
function handleAudioPause() {}
</script>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_html_parser/benches/fixtures/synthetic/svelte-directives.svelte`
around lines 1 - 156, The template references undeclared symbols actionParam,
actionConfig, and handleLongPress; add declarations to the <script> block:
declare sensible default values for actionParam and actionConfig (e.g., let
actionParam = null; let actionConfig = {}; or appropriate types used by
action2/action3) and implement a handleLongPress function (e.g., function
handleLongPress(event, params) { /* noop or set state like longpress handling */
}) so that actionParam, actionConfig, and handleLongPress are defined and used
by use:action2, use:action3, and use:longpress respectively; ensure names match
exactly as referenced in the template.

Comment on lines +39 to +167
<table @[clickEvent].stop="onTableClick">
<tr v-for="row in rows" :key="row.id"
:[row.attr]="row.value"
@[row.event].prevent="row.handler"
@[row.event2].capture.once="row.handler2">
<td v-for="cell in row.cells" :key="cell.id"
:[cell.prop]="cell.value"
@[cell.event].stop.prevent="cell.handler">
{{ cell.content }}
</td>
</tr>
</table>

<nav @[navEvent].prevent="onNav">
<a v-for="link in links" :key="link.id"
:[link.href]="link.url"
:[link.target]="link.targetVal"
@[link.click].prevent.stop="link.onClick"
@[link.mouseenter].once="link.onEnter">
{{ link.text }}
</a>
</nav>

<modal :[modalProp]="modalValue"
@[modalShow]="onShow"
@[modalHide].once="onHide"
@[modalClose].prevent.stop="onClose">
<header :[headerProp]="headerValue" @[headerEvent]="onHeaderEvent">
<h2>{{ modalTitle }}</h2>
</header>
<section :[contentProp]="contentValue" @[contentEvent].capture="onContentEvent">
<p>{{ modalContent }}</p>
</section>
<footer :[footerProp]="footerValue" @[footerEvent].stop="onFooterEvent">
<button :[actionProp]="actionValue" @[actionEvent].prevent="onAction">Close</button>
</footer>
</modal>

<card v-for="card in cards" :key="card.id"
:[card.titleProp]="card.title"
:[card.imageProp]="card.image"
:[card.classProp]="card.class"
@[card.click].prevent.stop.once="card.onClick"
@[card.hover].capture="card.onHover">
<template #[card.slotName]>
{{ card.slotContent }}
</template>
</card>

<tabs :[activeTabProp]="activeTab" @[tabChange]="onTabChange">
<tab v-for="tab in tabs" :key="tab.id"
:[tab.nameProp]="tab.name"
:[tab.disabledProp]="tab.disabled"
@[tab.select].prevent="tab.onSelect"
@[tab.deselect].once="tab.onDeselect">
{{ tab.content }}
</tab>
</tabs>

<dropdown :[visibleProp]="dropdownVisible"
@[toggleEvent].prevent="toggleDropdown"
@[selectEvent].stop="onSelect"
@[outsideEvent].capture.once="onOutsideClick">
<item v-for="item in dropdownItems" :key="item.id"
:[item.valueProp]="item.value"
:[item.disabledProp]="item.disabled"
@[item.click].prevent.stop="item.onClick"
@[item.hover]="item.onHover">
{{ item.label }}
</item>
</dropdown>

<tooltip :[tooltipProp]="tooltipContent"
:[positionProp]="tooltipPosition"
@[showEvent].once="onTooltipShow"
@[hideEvent].prevent="onTooltipHide">
<span @[triggerEvent]="onTrigger">Hover me</span>
</tooltip>
</div>
</template>

<script setup>
import { ref } from 'vue'

const dynamicProp = ref('value')
const dynamicEvent = ref('input')
const value = ref('test')
const handler = () => {}

const typeProp = ref('type')
const buttonType = ref('button')
const clickEvent = ref('click')
const hoverEvent = ref('mouseenter')
const buttonText = ref('Click me')
const onClick = () => {}
const onHover = () => {}

const classProp = ref('class')
const styleProp = ref('style')
const dynamicClass = ref('dynamic')
const dynamicStyle = ref({ color: 'red' })
const focusEvent = ref('focus')
const onFocus = () => {}

const items = ref([
{ id: 1, attr: 'data-foo', value: 'bar', event: 'click', event2: 'dblclick', handler: () => {}, handler2: () => {}, label: 'Item 1' },
{ id: 2, attr: 'data-baz', value: 'qux', event: 'mouseover', event2: 'mouseout', handler: () => {}, handler2: () => {}, label: 'Item 2' }
])

const submitEvent = ref('submit')
const onSubmit = () => {}

const fields = ref([
{ name: 'email', prop: 'type', value: 'email', model: 'v-model', data: '', input: 'input', onInput: () => {}, blur: 'blur', onBlur: () => {}, focus: 'focus', onFocus: () => {} },
{ name: 'password', prop: 'type', value: 'password', model: 'v-model', data: '', input: 'input', onInput: () => {}, blur: 'blur', onBlur: () => {}, focus: 'focus', onFocus: () => {} }
])

const selectProp = ref('value')
const selectValue = ref('')
const changeEvent = ref('change')
const onChange = () => {}

const options = ref([
{ value: 'a', prop: 'value', selected: 'selected', isSelected: false, label: 'Option A' },
{ value: 'b', prop: 'value', selected: 'selected', isSelected: true, label: 'Option B' }
])

const clickEvent = ref('click')
const onTableClick = () => {}
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

🏁 Script executed:

#!/bin/bash
# Verify duplicate declarations and usages of clickEvent in the fixture
rg -n 'const clickEvent' crates/biome_html_parser/benches/fixtures/synthetic/vue-dynamic-args.vue
rg -n '@\[(clickEvent|tableClickEvent)\]' crates/biome_html_parser/benches/fixtures/synthetic/vue-dynamic-args.vue

Repository: biomejs/biome

Length of output: 278


Fix duplicate clickEvent const declaration.
const clickEvent is declared twice (lines 130 and 166) in the same script scope, which breaks the fixture.

🛠️ Suggested fix
-    <table @[clickEvent].stop="onTableClick">
+    <table @[tableClickEvent].stop="onTableClick">
@@
-const clickEvent = ref('click')
+const tableClickEvent = ref('click')
 const onTableClick = () => {}

This renames the second declaration and updates its template binding at line 39.

📝 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
<table @[clickEvent].stop="onTableClick">
<tr v-for="row in rows" :key="row.id"
:[row.attr]="row.value"
@[row.event].prevent="row.handler"
@[row.event2].capture.once="row.handler2">
<td v-for="cell in row.cells" :key="cell.id"
:[cell.prop]="cell.value"
@[cell.event].stop.prevent="cell.handler">
{{ cell.content }}
</td>
</tr>
</table>
<nav @[navEvent].prevent="onNav">
<a v-for="link in links" :key="link.id"
:[link.href]="link.url"
:[link.target]="link.targetVal"
@[link.click].prevent.stop="link.onClick"
@[link.mouseenter].once="link.onEnter">
{{ link.text }}
</a>
</nav>
<modal :[modalProp]="modalValue"
@[modalShow]="onShow"
@[modalHide].once="onHide"
@[modalClose].prevent.stop="onClose">
<header :[headerProp]="headerValue" @[headerEvent]="onHeaderEvent">
<h2>{{ modalTitle }}</h2>
</header>
<section :[contentProp]="contentValue" @[contentEvent].capture="onContentEvent">
<p>{{ modalContent }}</p>
</section>
<footer :[footerProp]="footerValue" @[footerEvent].stop="onFooterEvent">
<button :[actionProp]="actionValue" @[actionEvent].prevent="onAction">Close</button>
</footer>
</modal>
<card v-for="card in cards" :key="card.id"
:[card.titleProp]="card.title"
:[card.imageProp]="card.image"
:[card.classProp]="card.class"
@[card.click].prevent.stop.once="card.onClick"
@[card.hover].capture="card.onHover">
<template #[card.slotName]>
{{ card.slotContent }}
</template>
</card>
<tabs :[activeTabProp]="activeTab" @[tabChange]="onTabChange">
<tab v-for="tab in tabs" :key="tab.id"
:[tab.nameProp]="tab.name"
:[tab.disabledProp]="tab.disabled"
@[tab.select].prevent="tab.onSelect"
@[tab.deselect].once="tab.onDeselect">
{{ tab.content }}
</tab>
</tabs>
<dropdown :[visibleProp]="dropdownVisible"
@[toggleEvent].prevent="toggleDropdown"
@[selectEvent].stop="onSelect"
@[outsideEvent].capture.once="onOutsideClick">
<item v-for="item in dropdownItems" :key="item.id"
:[item.valueProp]="item.value"
:[item.disabledProp]="item.disabled"
@[item.click].prevent.stop="item.onClick"
@[item.hover]="item.onHover">
{{ item.label }}
</item>
</dropdown>
<tooltip :[tooltipProp]="tooltipContent"
:[positionProp]="tooltipPosition"
@[showEvent].once="onTooltipShow"
@[hideEvent].prevent="onTooltipHide">
<span @[triggerEvent]="onTrigger">Hover me</span>
</tooltip>
</div>
</template>
<script setup>
import { ref } from 'vue'
const dynamicProp = ref('value')
const dynamicEvent = ref('input')
const value = ref('test')
const handler = () => {}
const typeProp = ref('type')
const buttonType = ref('button')
const clickEvent = ref('click')
const hoverEvent = ref('mouseenter')
const buttonText = ref('Click me')
const onClick = () => {}
const onHover = () => {}
const classProp = ref('class')
const styleProp = ref('style')
const dynamicClass = ref('dynamic')
const dynamicStyle = ref({ color: 'red' })
const focusEvent = ref('focus')
const onFocus = () => {}
const items = ref([
{ id: 1, attr: 'data-foo', value: 'bar', event: 'click', event2: 'dblclick', handler: () => {}, handler2: () => {}, label: 'Item 1' },
{ id: 2, attr: 'data-baz', value: 'qux', event: 'mouseover', event2: 'mouseout', handler: () => {}, handler2: () => {}, label: 'Item 2' }
])
const submitEvent = ref('submit')
const onSubmit = () => {}
const fields = ref([
{ name: 'email', prop: 'type', value: 'email', model: 'v-model', data: '', input: 'input', onInput: () => {}, blur: 'blur', onBlur: () => {}, focus: 'focus', onFocus: () => {} },
{ name: 'password', prop: 'type', value: 'password', model: 'v-model', data: '', input: 'input', onInput: () => {}, blur: 'blur', onBlur: () => {}, focus: 'focus', onFocus: () => {} }
])
const selectProp = ref('value')
const selectValue = ref('')
const changeEvent = ref('change')
const onChange = () => {}
const options = ref([
{ value: 'a', prop: 'value', selected: 'selected', isSelected: false, label: 'Option A' },
{ value: 'b', prop: 'value', selected: 'selected', isSelected: true, label: 'Option B' }
])
const clickEvent = ref('click')
const onTableClick = () => {}
<table @[tableClickEvent].stop="onTableClick">
<tr v-for="row in rows" :key="row.id"
:[row.attr]="row.value"
@[row.event].prevent="row.handler"
@[row.event2].capture.once="row.handler2">
<td v-for="cell in row.cells" :key="cell.id"
:[cell.prop]="cell.value"
@[cell.event].stop.prevent="cell.handler">
{{ cell.content }}
</td>
</tr>
</table>
<nav @[navEvent].prevent="onNav">
<a v-for="link in links" :key="link.id"
:[link.href]="link.url"
:[link.target]="link.targetVal"
@[link.click].prevent.stop="link.onClick"
@[link.mouseenter].once="link.onEnter">
{{ link.text }}
</a>
</nav>
<modal :[modalProp]="modalValue"
@[modalShow]="onShow"
@[modalHide].once="onHide"
@[modalClose].prevent.stop="onClose">
<header :[headerProp]="headerValue" @[headerEvent]="onHeaderEvent">
<h2>{{ modalTitle }}</h2>
</header>
<section :[contentProp]="contentValue" @[contentEvent].capture="onContentEvent">
<p>{{ modalContent }}</p>
</section>
<footer :[footerProp]="footerValue" @[footerEvent].stop="onFooterEvent">
<button :[actionProp]="actionValue" @[actionEvent].prevent="onAction">Close</button>
</footer>
</modal>
<card v-for="card in cards" :key="card.id"
:[card.titleProp]="card.title"
:[card.imageProp]="card.image"
:[card.classProp]="card.class"
@[card.click].prevent.stop.once="card.onClick"
@[card.hover].capture="card.onHover">
<template #[card.slotName]>
{{ card.slotContent }}
</template>
</card>
<tabs :[activeTabProp]="activeTab" @[tabChange]="onTabChange">
<tab v-for="tab in tabs" :key="tab.id"
:[tab.nameProp]="tab.name"
:[tab.disabledProp]="tab.disabled"
@[tab.select].prevent="tab.onSelect"
@[tab.deselect].once="tab.onDeselect">
{{ tab.content }}
</tab>
</tabs>
<dropdown :[visibleProp]="dropdownVisible"
@[toggleEvent].prevent="toggleDropdown"
@[selectEvent].stop="onSelect"
@[outsideEvent].capture.once="onOutsideClick">
<item v-for="item in dropdownItems" :key="item.id"
:[item.valueProp]="item.value"
:[item.disabledProp]="item.disabled"
@[item.click].prevent.stop="item.onClick"
@[item.hover]="item.onHover">
{{ item.label }}
</item>
</dropdown>
<tooltip :[tooltipProp]="tooltipContent"
:[positionProp]="tooltipPosition"
@[showEvent].once="onTooltipShow"
@[hideEvent].prevent="onTooltipHide">
<span @[triggerEvent]="onTrigger">Hover me</span>
</tooltip>
</div>
</template>
<script setup>
import { ref } from 'vue'
const dynamicProp = ref('value')
const dynamicEvent = ref('input')
const value = ref('test')
const handler = () => {}
const typeProp = ref('type')
const buttonType = ref('button')
const clickEvent = ref('click')
const hoverEvent = ref('mouseenter')
const buttonText = ref('Click me')
const onClick = () => {}
const onHover = () => {}
const classProp = ref('class')
const styleProp = ref('style')
const dynamicClass = ref('dynamic')
const dynamicStyle = ref({ color: 'red' })
const focusEvent = ref('focus')
const onFocus = () => {}
const items = ref([
{ id: 1, attr: 'data-foo', value: 'bar', event: 'click', event2: 'dblclick', handler: () => {}, handler2: () => {}, label: 'Item 1' },
{ id: 2, attr: 'data-baz', value: 'qux', event: 'mouseover', event2: 'mouseout', handler: () => {}, handler2: () => {}, label: 'Item 2' }
])
const submitEvent = ref('submit')
const onSubmit = () => {}
const fields = ref([
{ name: 'email', prop: 'type', value: 'email', model: 'v-model', data: '', input: 'input', onInput: () => {}, blur: 'blur', onBlur: () => {}, focus: 'focus', onFocus: () => {} },
{ name: 'password', prop: 'type', value: 'password', model: 'v-model', data: '', input: 'input', onInput: () => {}, blur: 'blur', onBlur: () => {}, focus: 'focus', onFocus: () => {} }
])
const selectProp = ref('value')
const selectValue = ref('')
const changeEvent = ref('change')
const onChange = () => {}
const options = ref([
{ value: 'a', prop: 'value', selected: 'selected', isSelected: false, label: 'Option A' },
{ value: 'b', prop: 'value', selected: 'selected', isSelected: true, label: 'Option B' }
])
const tableClickEvent = ref('click')
const onTableClick = () => {}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_html_parser/benches/fixtures/synthetic/vue-dynamic-args.vue`
around lines 39 - 167, There is a duplicate const declaration for clickEvent;
rename the second script variable (the one used for the table) to a unique name
(e.g., tableClickEvent) and update the template binding that uses it (the table
element with @[clickEvent].stop="onTableClick") to use the new name
@[tableClickEvent]. Ensure you change the declaration (const clickEvent =
ref('click') → const tableClickEvent = ref('click')) and any template references
that pointed to the second clickEvent.

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: 2

🧹 Nitpick comments (1)
crates/biome_html_formatter/benches/html_formatter.rs (1)

26-55: Surface fixture I/O failures rather than silently skipping.
load_fixtures drops unreadable dirs/files without signalling, which can quietly reduce benchmark coverage. Failing fast keeps CI honest.

Proposed tweak
-    fn visit(dir: &Path, root: &Path, cases: &mut Vec<(String, String, String)>) {
-        if let Ok(entries) = fs::read_dir(dir) {
-            for entry in entries.flatten() {
+    fn visit(dir: &Path, root: &Path, cases: &mut Vec<(String, String, String)>) {
+        let entries = fs::read_dir(dir)
+            .unwrap_or_else(|err| panic!("Failed to read fixtures dir {dir:?}: {err}"));
+        for entry in entries {
+            let entry = entry
+                .unwrap_or_else(|err| panic!("Failed to read fixtures entry in {dir:?}: {err}"));
                 let path = entry.path();
                 if path.is_dir() {
                     visit(&path, root, cases);
                 } else if path.is_file() {
                     if matches!(path.extension().and_then(|e| e.to_str()), Some(ext) if ext.eq_ignore_ascii_case("md"))
                     {
                         continue;
                     }
                     let rel = path.strip_prefix(root).unwrap_or(&path);
                     let group = rel
                         .iter()
                         .next()
                         .and_then(|s| s.to_str())
                         .unwrap_or("root")
                         .to_string();
                     let name = path
                         .file_name()
                         .and_then(|s| s.to_str())
                         .unwrap_or_default()
                         .to_string();
-                    if let Ok(content) = fs::read_to_string(&path) {
-                        cases.push((group, name, content));
-                    }
+                    let content = fs::read_to_string(&path)
+                        .unwrap_or_else(|err| panic!("Failed to read fixture {path:?}: {err}"));
+                    cases.push((group, name, content));
                 }
             }
-        }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_html_formatter/benches/html_formatter.rs` around lines 26 - 55,
The helper load_fixtures currently swallows I/O errors inside the inner visit
function (calls to fs::read_dir and fs::read_to_string) which silently drops
fixtures; change load_fixtures (and its inner visit) to surface failures instead
of continuing—either by returning Result<Vec<(String,String,String)>,
std::io::Error> from load_fixtures and propagating errors from fs::read_dir and
fs::read_to_string with ? (adjust visit signature accordingly), or by explicitly
panicking with a descriptive message when read_dir/read_to_string returns Err;
update all callers to handle the Result if you choose the Result approach.
🤖 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_html_parser/benches/html_parser.rs`:
- Around line 134-157: Pre-validate each fixture before benchmarking by calling
parse_html once (using HtmlParseOptions::from(&file_source) and parse_html) and
collect its diagnostics into a local vec; if diagnostics are non-empty, print a
capped number (e.g., up to 10) using with_file_source_code/from with_file_path
and print_diagnostic_to_string and then skip or fail the benchmark for that
fixture instead of proceeding; then change the closure passed to
group.bench_with_input (the one that currently extends diagnostics) to discard
or black_box only the parse result (do not push into the diagnostics vec) so the
benchmarking loop does not accumulate diagnostics unboundedly.

In `@crates/biome_html_parser/Cargo.toml`:
- Around line 28-31: Update the dev-dependency entry for biome_string_case in
the crate's Cargo.toml to use a path-based reference instead of workspace =
true: replace the existing biome_string_case = { workspace = true } with a
path-based specification (e.g., biome_string_case = { path =
"../biome_string_case" }) so the dev-dependency points at the local crate; leave
the other dev-dependencies (biome_test_utils, camino) unchanged.

---

Nitpick comments:
In `@crates/biome_html_formatter/benches/html_formatter.rs`:
- Around line 26-55: The helper load_fixtures currently swallows I/O errors
inside the inner visit function (calls to fs::read_dir and fs::read_to_string)
which silently drops fixtures; change load_fixtures (and its inner visit) to
surface failures instead of continuing—either by returning
Result<Vec<(String,String,String)>, std::io::Error> from load_fixtures and
propagating errors from fs::read_dir and fs::read_to_string with ? (adjust visit
signature accordingly), or by explicitly panicking with a descriptive message
when read_dir/read_to_string returns Err; update all callers to handle the
Result if you choose the Result approach.

Comment on lines +134 to +157
// Benchmark local fixtures (recursively discovered), including their group names
for (group_name, name, content) in fixtures {
let code = content.as_str();
let mut diagnostics = vec![];
group.throughput(Throughput::Bytes(code.len() as u64));
let ext = name
.rsplit('.')
.next()
.unwrap_or_default()
.to_ascii_lowercase_cow();
let file_source = HtmlFileSource::try_from_extension(&ext).unwrap_or_default();

let id = format!("{}/{}", group_name, name);
group.bench_with_input(BenchmarkId::new(&id, "uncached"), &code, |b, _| {
b.iter(|| {
let result = black_box(parse_html(code, HtmlParseOptions::from(&file_source)));
diagnostics.extend(result.into_diagnostics());
})
});

for diagnostic in diagnostics {
let diagnostic = diagnostic.with_file_source_code(code).with_file_path(&id);
println!("{}", print_diagnostic_to_string(&diagnostic));
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fail fast on fixture diagnostics to avoid unbounded accumulation.

Lines 147–151 append diagnostics on every iteration, so a faulty fixture will inflate memory and distort timings. Pre-validate once, print a capped set of diagnostics, then benchmark without collecting.

Suggested change
-        let mut diagnostics = vec![];
         group.throughput(Throughput::Bytes(code.len() as u64));
         let ext = name
             .rsplit('.')
             .next()
             .unwrap_or_default()
             .to_ascii_lowercase_cow();
         let file_source = HtmlFileSource::try_from_extension(&ext).unwrap_or_default();
 
-        let id = format!("{}/{}", group_name, name);
+        let id = format!("{}/{}", group_name, name);
+        let result = parse_html(code, HtmlParseOptions::from(&file_source));
+        if !result.diagnostics().is_empty() {
+            let truncated = result
+                .into_diagnostics()
+                .into_iter()
+                .take(20)
+                .collect::<Vec<_>>();
+            for diagnostic in truncated {
+                let diagnostic = diagnostic.with_file_source_code(code).with_file_path(&id);
+                println!("{}", print_diagnostic_to_string(&diagnostic));
+            }
+            panic!(
+                "Parsing errors detected in benchmark. Only some of the diagnostics are printed above."
+            );
+        }
         group.bench_with_input(BenchmarkId::new(&id, "uncached"), &code, |b, _| {
             b.iter(|| {
-                let result = black_box(parse_html(code, HtmlParseOptions::from(&file_source)));
-                diagnostics.extend(result.into_diagnostics());
+                black_box(parse_html(code, HtmlParseOptions::from(&file_source)));
             })
         });
-
-        for diagnostic in diagnostics {
-            let diagnostic = diagnostic.with_file_source_code(code).with_file_path(&id);
-            println!("{}", print_diagnostic_to_string(&diagnostic));
-        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_html_parser/benches/html_parser.rs` around lines 134 - 157,
Pre-validate each fixture before benchmarking by calling parse_html once (using
HtmlParseOptions::from(&file_source) and parse_html) and collect its diagnostics
into a local vec; if diagnostics are non-empty, print a capped number (e.g., up
to 10) using with_file_source_code/from with_file_path and
print_diagnostic_to_string and then skip or fail the benchmark for that fixture
instead of proceeding; then change the closure passed to group.bench_with_input
(the one that currently extends diagnostics) to discard or black_box only the
parse result (do not push into the diagnostics vec) so the benchmarking loop
does not accumulate diagnostics unboundedly.

Comment on lines 28 to 31
[dev-dependencies]
biome_string_case = { workspace = true }
biome_test_utils = { workspace = true }
camino = { workspace = true }
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

🧩 Analysis chain

🏁 Script executed:

# Find biome_string_case location and check workspace configuration
find . -name "Cargo.toml" -path "*/biome_string_case/*" | head -5

# Check if biome_string_case is defined in workspace
rg "biome_string_case" -g 'Cargo.toml' -B 2 -A 2 | head -40

# Check the workspace root Cargo.toml for dependency definitions
head -100 Cargo.toml | grep -A 50 "dependencies"

# See how other crates handle dev-dependencies
rg "\[dev-dependencies\]" -A 10 -g 'Cargo.toml' crates/ | head -60

Repository: biomejs/biome

Length of output: 10350


Switch biome_string_case to a path dev-dependency.

Dev-dependencies should use path-based references to avoid requiring published versions. Change line 29 from workspace to path.

Suggested change
-biome_string_case = { workspace = true }
+biome_string_case = { path = "../biome_string_case" }
📝 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
[dev-dependencies]
biome_string_case = { workspace = true }
biome_test_utils = { workspace = true }
camino = { workspace = true }
[dev-dependencies]
biome_string_case = { path = "../biome_string_case" }
biome_test_utils = { workspace = true }
camino = { workspace = true }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_html_parser/Cargo.toml` around lines 28 - 31, Update the
dev-dependency entry for biome_string_case in the crate's Cargo.toml to use a
path-based reference instead of workspace = true: replace the existing
biome_string_case = { workspace = true } with a path-based specification (e.g.,
biome_string_case = { path = "../biome_string_case" }) so the dev-dependency
points at the local crate; leave the other dev-dependencies (biome_test_utils,
camino) unchanged.

Copy link
Contributor Author

dyc3 commented Feb 20, 2026

Merge activity

  • Feb 20, 3:21 PM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Feb 20, 3:22 PM UTC: @dyc3 merged this pull request with Graphite.

@dyc3 dyc3 merged commit a794cd9 into main Feb 20, 2026
33 checks passed
@dyc3 dyc3 deleted the html-benches branch February 20, 2026 15:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Formatter Area: formatter A-Parser Area: parser L-HTML Language: HTML and super languages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants