feat(assist): implement useSortedAttributes for HTML#9547
Conversation
🦋 Changeset detectedLatest commit: 65094ee The changes in this PR will be included in the next version bump. This PR includes changesets to release 14 packages
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 |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds a shared sorting abstraction: a public Suggested reviewers
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
crates/biome_html_analyze/src/assist/source/use_sorted_attributes.rs (1)
102-105: Optional: Consider extracting comparator selection into a helper.The comparator creation is duplicated between
run()andaction(). A small helper function could reduce this duplication.♻️ Suggested helper function
fn get_comparator( sort_order: SortOrder, ) -> fn(&SortableHtmlAttribute, &SortableHtmlAttribute) -> Ordering { match sort_order { SortOrder::Natural => SortableHtmlAttribute::ascii_nat_cmp, SortOrder::Lexicographic => SortableHtmlAttribute::lexicographic_cmp, } }Then use
get_comparator(options.sort_order.unwrap_or_default())in both places.Also applies to: 161-164
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@crates/biome_html_analyze/src/assist/source/use_sorted_attributes.rs` around lines 102 - 105, Extract the duplicated comparator selection into a small helper function (e.g., get_comparator) that takes SortOrder and returns the comparator fn(&SortableHtmlAttribute, &SortableHtmlAttribute) -> Ordering; replace the match blocks in both run() and action() with calls to get_comparator(options.sort_order.unwrap_or_default()) (or the appropriate sort_order variable) so both places use the same helper and remove duplicated match logic over SortOrder::Natural / SortOrder::Lexicographic mapping to SortableHtmlAttribute::ascii_nat_cmp and ::lexicographic_cmp.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@crates/biome_html_analyze/src/assist/source/use_sorted_attributes.rs`:
- Around line 102-105: Extract the duplicated comparator selection into a small
helper function (e.g., get_comparator) that takes SortOrder and returns the
comparator fn(&SortableHtmlAttribute, &SortableHtmlAttribute) -> Ordering;
replace the match blocks in both run() and action() with calls to
get_comparator(options.sort_order.unwrap_or_default()) (or the appropriate
sort_order variable) so both places use the same helper and remove duplicated
match logic over SortOrder::Natural / SortOrder::Lexicographic mapping to
SortableHtmlAttribute::ascii_nat_cmp and ::lexicographic_cmp.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: aa4467ce-c90c-4f70-952c-330f8399b890
⛔ Files ignored due to path filters (13)
Cargo.lockis excluded by!**/*.lockand included by**crates/biome_html_analyze/tests/specs/source/useSortedAttributes/astro/sorted.astro.snapis excluded by!**/*.snapand included by**crates/biome_html_analyze/tests/specs/source/useSortedAttributes/astro/unsorted.astro.snapis excluded by!**/*.snapand included by**crates/biome_html_analyze/tests/specs/source/useSortedAttributes/html/sorted-lexicographic.html.snapis excluded by!**/*.snapand included by**crates/biome_html_analyze/tests/specs/source/useSortedAttributes/html/sorted.html.snapis excluded by!**/*.snapand included by**crates/biome_html_analyze/tests/specs/source/useSortedAttributes/html/unsorted-lexicographic.html.snapis excluded by!**/*.snapand included by**crates/biome_html_analyze/tests/specs/source/useSortedAttributes/html/unsorted.html.snapis excluded by!**/*.snapand included by**crates/biome_html_analyze/tests/specs/source/useSortedAttributes/svelte/sorted.svelte.snapis excluded by!**/*.snapand included by**crates/biome_html_analyze/tests/specs/source/useSortedAttributes/svelte/unsorted.svelte.snapis excluded by!**/*.snapand included by**crates/biome_html_analyze/tests/specs/source/useSortedAttributes/vue/sorted.vue.snapis excluded by!**/*.snapand included by**crates/biome_html_analyze/tests/specs/source/useSortedAttributes/vue/unsorted.vue.snapis excluded by!**/*.snapand included by**packages/@biomejs/backend-jsonrpc/src/workspace.tsis excluded by!**/backend-jsonrpc/src/workspace.tsand included by**packages/@biomejs/biome/configuration_schema.jsonis excluded by!**/configuration_schema.jsonand included by**
📒 Files selected for processing (18)
.changeset/eleven-baths-wave.mdcrates/biome_analyze/Cargo.tomlcrates/biome_analyze/src/shared/mod.rscrates/biome_analyze/src/shared/sort_attributes.rscrates/biome_html_analyze/src/assist/source/use_sorted_attributes.rscrates/biome_html_analyze/tests/specs/source/useSortedAttributes/astro/sorted.astrocrates/biome_html_analyze/tests/specs/source/useSortedAttributes/astro/unsorted.astrocrates/biome_html_analyze/tests/specs/source/useSortedAttributes/html/sorted-lexicographic.htmlcrates/biome_html_analyze/tests/specs/source/useSortedAttributes/html/sorted-lexicographic.options.jsoncrates/biome_html_analyze/tests/specs/source/useSortedAttributes/html/sorted.htmlcrates/biome_html_analyze/tests/specs/source/useSortedAttributes/html/unsorted-lexicographic.htmlcrates/biome_html_analyze/tests/specs/source/useSortedAttributes/html/unsorted-lexicographic.options.jsoncrates/biome_html_analyze/tests/specs/source/useSortedAttributes/html/unsorted.htmlcrates/biome_html_analyze/tests/specs/source/useSortedAttributes/svelte/sorted.sveltecrates/biome_html_analyze/tests/specs/source/useSortedAttributes/svelte/unsorted.sveltecrates/biome_html_analyze/tests/specs/source/useSortedAttributes/vue/sorted.vuecrates/biome_html_analyze/tests/specs/source/useSortedAttributes/vue/unsorted.vuecrates/biome_js_analyze/src/assist/source/use_sorted_attributes.rs
Merging this PR will not alter performance
Comparing Footnotes
|
| i Safe fix: Sort the HTML attributes. | ||
|
|
||
| 1 │ - <p·v-text="msg"·v-show="ok"·dir="auto"·v-foo:bar.baz·id="hello"·class="flex"></p> | ||
| 1 │ + <p·v-text="msg"·v-show="ok"·dir="auto"·v-foo:bar.baz·class="flex"id="hello"·></p> | ||
| 2 2 │ <div v-slot:default v-on:click="doThis" v-once v-bind="{ id: someProp, 'other-attr': otherProp } " | ||
| 3 3 │ v-bind:src="'/path/to/images/' + fileName" |
There was a problem hiding this comment.
we should discuss the order of vue directives. I personally prefer vue directives to generally be after all normal attributes, but there might be some prior art out there.
| 9 9 │ onswiperight={prev} | ||
| 10 10 │ {@attach myAttachment} | ||
| 11 │ - ····spellcheck="true" | ||
| 12 │ - ····tabindex="-1" | ||
| 13 │ - ····dir="auto" | ||
| 11 │ + ····dir="auto" | ||
| 12 │ + ····spellcheck="true" | ||
| 13 │ + ····tabindex="-1" | ||
| 14 14 │ animate:flip | ||
| 15 15 │ style:color="red" |
There was a problem hiding this comment.
same here, there might be prior art or popular conventions to consider here for svelte directives
There was a problem hiding this comment.
Hi! For prior art on Svelte directive ordering, the official eslint-plugin-svelte has a well-established sort-attributes rule with a default order that groups Svelte-specific directives by type.
It might make sense to align with this convention since it's what the Svelte ecosystem already uses.
There was a problem hiding this comment.
What if the alphabetical sorting of HTML attributes conflicts with eslint-plugin-svelte's ordering? For instance, eslint-plugin-svelte's sort order puts id before class.
There was a problem hiding this comment.
For now I would say let's ignore conflicting logic for base html attributes, and just focus on svelte specific directives
| client:load | ||
| class:list={classes} | ||
| set:text={text} | ||
| is:raw | ||
| dir="auto" | ||
| spellcheck="true" | ||
| tabindex="-1" | ||
| define:vars={vars} | ||
| server:defer | ||
| id="myid" |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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_analyze/src/assist/source/use_sorted_attributes.rs`:
- Around line 145-151: text_range currently ignores the provided _state and
returns the whole element range, causing duplicate diagnostics; update
text_range(ctx: &RuleContext<Self>, state: &Self::State) to compute and return a
narrower TextRange covering only the current attribute group (use state.attrs:
get the first and last attribute nodes from state.attrs, compute their combined
span from first.range().start() to last.range().end(), and return that
TextRange) instead of the HtmlOpeningElement/HtmlSelfClosingElement
element.range(); this ensures the diagnostic is tied to the specific group
rather than the entire element.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: d5a4de83-7c63-4acb-bc99-3a8bba45b1be
📒 Files selected for processing (1)
crates/biome_html_analyze/src/assist/source/use_sorted_attributes.rs
| fn text_range(ctx: &RuleContext<Self>, _state: &Self::State) -> Option<TextRange> { | ||
| ctx.query().syntax().ancestors().skip(1).find_map(|node| { | ||
| HtmlOpeningElement::cast_ref(&node) | ||
| .map(|element| element.range()) | ||
| .or_else(|| HtmlSelfClosingElement::cast_ref(&node).map(|element| element.range())) | ||
| }) | ||
| } |
There was a problem hiding this comment.
Use the state-specific range to avoid duplicate diagnostics.
text_range currently ignores state, so two unsorted groups in the same element can emit duplicate diagnostics on the same element-wide span. Please narrow the range to the current group (for example, from first to last attribute in state.attrs).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@crates/biome_html_analyze/src/assist/source/use_sorted_attributes.rs` around
lines 145 - 151, text_range currently ignores the provided _state and returns
the whole element range, causing duplicate diagnostics; update text_range(ctx:
&RuleContext<Self>, state: &Self::State) to compute and return a narrower
TextRange covering only the current attribute group (use state.attrs: get the
first and last attribute nodes from state.attrs, compute their combined span
from first.range().start() to last.range().end(), and return that TextRange)
instead of the HtmlOpeningElement/HtmlSelfClosingElement element.range(); this
ensures the diagnostic is tied to the specific group rather than the entire
element.
There was a problem hiding this comment.
🧹 Nitpick comments (2)
crates/biome_html_analyze/src/assist/source/use_sorted_attributes.rs (1)
211-215: Address TODO: trailing whitespace may be lost after sorting.The
replace_node_discard_triviacall could result in attributes merging together without separating whitespace. The commented-out logic suggests this is on your radar—worth tracking to ensure the fix is applied before release.Would you like me to open an issue to track this, or do you plan to address it in this PR?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@crates/biome_html_analyze/src/assist/source/use_sorted_attributes.rs` around lines 211 - 215, The TODO warns that using mutation.replace_node_discard_trivia(attr.clone(), sorted_attr) can drop separating whitespace so ensure each sorted_attr keeps trailing whitespace unless it's the group's last attribute: before calling mutation.replace_node_discard_trivia, inspect sorted_attr.syntax().last_trailing_trivia() and if None (and attr is not last in group) append a whitespace trivia via sorted_attr = sorted_attr.append_trivia_pieces([TriviaPiece::whitespace(1)]); then replace; use the same check for attributes you generate to avoid merging tokens when replacing nodes (refer to sorted_attr, TriviaPiece, append_trivia_pieces, attr, and mutation.replace_node_discard_trivia).crates/biome_js_analyze/src/assist/source/use_sorted_attributes.rs (1)
143-149: Same text_range concern as the HTML counterpart.This function also ignores
_stateand returns the full element range. If an element has multiple unsorted groups (split by spreads), diagnostics may overlap on the same span. Consider narrowing to the group's range for consistency with the HTML fix (if applied).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@crates/biome_js_analyze/src/assist/source/use_sorted_attributes.rs` around lines 143 - 149, The text_range function currently ignores the rule State and returns the full JSX element range, causing overlapping diagnostics when multiple unsorted attribute groups (separated by spreads) exist; update text_range to use the provided State (remove the underscore) and return the TextRange for the specific attribute group that triggered the diagnostic instead of the whole element. Concretely, inside text_range (and using RuleContext<Self>), locate the same JsxOpeningElement/JsxSelfClosingElement as now but then compute and return the contiguous group's range (e.g., derive from the State passed into the rule which should contain the group's start/end/span or compute it by iterating the element's attributes and detecting the span between the group's first and last attribute, stopping at spread attributes), so diagnostics are emitted only for that group's TextRange rather than element.range().
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@crates/biome_html_analyze/src/assist/source/use_sorted_attributes.rs`:
- Around line 211-215: The TODO warns that using
mutation.replace_node_discard_trivia(attr.clone(), sorted_attr) can drop
separating whitespace so ensure each sorted_attr keeps trailing whitespace
unless it's the group's last attribute: before calling
mutation.replace_node_discard_trivia, inspect
sorted_attr.syntax().last_trailing_trivia() and if None (and attr is not last in
group) append a whitespace trivia via sorted_attr =
sorted_attr.append_trivia_pieces([TriviaPiece::whitespace(1)]); then replace;
use the same check for attributes you generate to avoid merging tokens when
replacing nodes (refer to sorted_attr, TriviaPiece, append_trivia_pieces, attr,
and mutation.replace_node_discard_trivia).
In `@crates/biome_js_analyze/src/assist/source/use_sorted_attributes.rs`:
- Around line 143-149: The text_range function currently ignores the rule State
and returns the full JSX element range, causing overlapping diagnostics when
multiple unsorted attribute groups (separated by spreads) exist; update
text_range to use the provided State (remove the underscore) and return the
TextRange for the specific attribute group that triggered the diagnostic instead
of the whole element. Concretely, inside text_range (and using
RuleContext<Self>), locate the same JsxOpeningElement/JsxSelfClosingElement as
now but then compute and return the contiguous group's range (e.g., derive from
the State passed into the rule which should contain the group's start/end/span
or compute it by iterating the element's attributes and detecting the span
between the group's first and last attribute, stopping at spread attributes), so
diagnostics are emitted only for that group's TextRange rather than
element.range().
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: a16ae846-9370-44ba-ab8d-f18026d545fa
⛔ Files ignored due to path filters (9)
crates/biome_html_analyze/tests/specs/source/useSortedAttributes/astro/sorted.astro.snapis excluded by!**/*.snapand included by**crates/biome_html_analyze/tests/specs/source/useSortedAttributes/astro/unsorted.astro.snapis excluded by!**/*.snapand included by**crates/biome_html_analyze/tests/specs/source/useSortedAttributes/html/sorted.html.snapis excluded by!**/*.snapand included by**crates/biome_html_analyze/tests/specs/source/useSortedAttributes/html/unsorted.html.snapis excluded by!**/*.snapand included by**crates/biome_html_analyze/tests/specs/source/useSortedAttributes/svelte/sorted.svelte.snapis excluded by!**/*.snapand included by**crates/biome_html_analyze/tests/specs/source/useSortedAttributes/svelte/unsorted.svelte.snapis excluded by!**/*.snapand included by**crates/biome_html_analyze/tests/specs/source/useSortedAttributes/vue/sorted.vue.snapis excluded by!**/*.snapand included by**crates/biome_html_analyze/tests/specs/source/useSortedAttributes/vue/unsorted.vue.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/source/useSortedAttributes/unsorted.jsx.snapis excluded by!**/*.snapand included by**
📒 Files selected for processing (12)
crates/biome_analyze/src/shared/sort_attributes.rscrates/biome_html_analyze/src/assist/source/use_sorted_attributes.rscrates/biome_html_analyze/tests/specs/source/useSortedAttributes/astro/sorted.astrocrates/biome_html_analyze/tests/specs/source/useSortedAttributes/astro/unsorted.astrocrates/biome_html_analyze/tests/specs/source/useSortedAttributes/html/sorted.htmlcrates/biome_html_analyze/tests/specs/source/useSortedAttributes/html/unsorted.htmlcrates/biome_html_analyze/tests/specs/source/useSortedAttributes/svelte/sorted.sveltecrates/biome_html_analyze/tests/specs/source/useSortedAttributes/svelte/unsorted.sveltecrates/biome_html_analyze/tests/specs/source/useSortedAttributes/vue/sorted.vuecrates/biome_html_analyze/tests/specs/source/useSortedAttributes/vue/unsorted.vuecrates/biome_js_analyze/src/assist/source/use_sorted_attributes.rscrates/biome_js_analyze/tests/specs/source/useSortedAttributes/unsorted.jsx
✅ Files skipped from review due to trivial changes (6)
- crates/biome_js_analyze/tests/specs/source/useSortedAttributes/unsorted.jsx
- crates/biome_html_analyze/tests/specs/source/useSortedAttributes/astro/unsorted.astro
- crates/biome_html_analyze/tests/specs/source/useSortedAttributes/vue/unsorted.vue
- crates/biome_html_analyze/tests/specs/source/useSortedAttributes/svelte/unsorted.svelte
- crates/biome_html_analyze/tests/specs/source/useSortedAttributes/vue/sorted.vue
- crates/biome_analyze/src/shared/sort_attributes.rs
🚧 Files skipped from review as they are similar to previous changes (2)
- crates/biome_html_analyze/tests/specs/source/useSortedAttributes/astro/sorted.astro
- crates/biome_html_analyze/tests/specs/source/useSortedAttributes/svelte/sorted.svelte
|
I've added sorting for the Astro, Svelte, and Vue directives. biome/crates/biome_html_analyze/src/assist/source/use_sorted_attributes.rs Lines 208 to 216 in e2af7cf |
|
Should new rule sources be added for eslint-plugin-astro and eslint-plugin-svelte? |
sure, that would make sense |
crates/biome_html_analyze/src/assist/source/use_sorted_attributes.rs
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
crates/biome_html_analyze/src/assist/source/use_sorted_attributes.rs (1)
193-199:⚠️ Potential issue | 🟡 MinorUse the current group for the diagnostic span.
run()can return more than oneAttributeGroupfor a single tag, buttext_range()still reports the whole element. If one element has two unsorted groups split by{...spread}orv-bind="object", you’ll emit duplicate diagnostics on the same range.🩹 Suggested fix
- fn text_range(ctx: &RuleContext<Self>, _state: &Self::State) -> Option<TextRange> { - ctx.query().syntax().ancestors().skip(1).find_map(|node| { - HtmlOpeningElement::cast_ref(&node) - .map(|element| element.range()) - .or_else(|| HtmlSelfClosingElement::cast_ref(&node).map(|element| element.range())) - }) + fn text_range(_ctx: &RuleContext<Self>, state: &Self::State) -> Option<TextRange> { + let first = state.attrs.first()?.0.range(); + let last = state.attrs.last()?.0.range(); + Some(TextRange::new(first.start(), last.end())) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@crates/biome_html_analyze/src/assist/source/use_sorted_attributes.rs` around lines 193 - 199, text_range currently finds the element ancestor and returns the whole element range, causing duplicate diagnostics when run() yields multiple AttributeGroup ranges; change text_range to prefer the current node from the query (ctx.query().current()) and, if that node is an HtmlAttributeGroup (or whatever AttributeGroup type your rule yields), return that group's range, falling back to the HtmlOpeningElement/HtmlSelfClosingElement element.range() only if no current AttributeGroup is found; update references in text_range to check AttributeGroup::cast_ref(¤t_node) first, then the existing element checks.
🤖 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_analyze/src/assist/source/use_sorted_attributes.rs`:
- Around line 235-293: The SortableHtmlAttribute::category currently buckets
every plain attribute (except "ref") as HtmlAttribute which leaves Vue
directives like "v-if" or ":" categorized incorrectly; update
SortableHtmlAttribute::category to inspect the attribute name (from
attr.name().and_then(...).map(|t| t.text_trimmed())) and if it matches Vue
patterns map them to the appropriate SortCategory (e.g. exact "v-for" ->
SortCategory::VueListRendering, "v-if"/"v-else-if"/"v-else" -> VueConditional,
"v-model" -> VueTwoWayBinding, names starting with "v-" -> VueOtherAttribute,
names starting with ":" -> VueOtherAttribute (v-bind), names starting with "@"
or "v-on" -> VueEvent, "v-slot" -> VueSlot, and keep "ref" -> VueUnique) instead
of falling through to HtmlAttribute; make this change inside the
SortableHtmlAttribute::category match arm for AnyHtmlAttribute::HtmlAttribute so
Vue-specific attributes are categorized before defaulting to
SortCategory::HtmlAttribute.
---
Duplicate comments:
In `@crates/biome_html_analyze/src/assist/source/use_sorted_attributes.rs`:
- Around line 193-199: text_range currently finds the element ancestor and
returns the whole element range, causing duplicate diagnostics when run() yields
multiple AttributeGroup ranges; change text_range to prefer the current node
from the query (ctx.query().current()) and, if that node is an
HtmlAttributeGroup (or whatever AttributeGroup type your rule yields), return
that group's range, falling back to the
HtmlOpeningElement/HtmlSelfClosingElement element.range() only if no current
AttributeGroup is found; update references in text_range to check
AttributeGroup::cast_ref(¤t_node) first, then the existing element checks.
🪄 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: ab1499c9-beb7-4ef4-8c25-44eafd1a350f
⛔ Files ignored due to path filters (4)
crates/biome_html_analyze/tests/specs/source/useSortedAttributes/html/unsorted-lexicographic.html.snapis excluded by!**/*.snapand included by**crates/biome_html_analyze/tests/specs/source/useSortedAttributes/html/unsorted.html.snapis excluded by!**/*.snapand included by**crates/biome_html_analyze/tests/specs/source/useSortedAttributes/svelte/unsorted.svelte.snapis excluded by!**/*.snapand included by**crates/biome_html_analyze/tests/specs/source/useSortedAttributes/vue/unsorted.vue.snapis excluded by!**/*.snapand included by**
📒 Files selected for processing (6)
crates/biome_analyze/src/rule.rscrates/biome_analyze/src/shared/sort_attributes.rscrates/biome_html_analyze/src/assist/source/use_sorted_attributes.rscrates/biome_html_analyze/tests/specs/source/useSortedAttributes/html/unsorted.htmlcrates/biome_html_analyze/tests/specs/source/useSortedAttributes/vue/unsorted.vuecrates/biome_js_analyze/src/assist/source/use_sorted_attributes.rs
✅ Files skipped from review due to trivial changes (1)
- crates/biome_html_analyze/tests/specs/source/useSortedAttributes/vue/unsorted.vue
| #[derive(PartialEq, Eq, Clone, PartialOrd, Ord)] | ||
| enum SortCategory { | ||
| HtmlAttribute, | ||
| AstroClassDirective, | ||
| AstroClientDirective, | ||
| AstroDefineDirective, | ||
| AstroIsDirective, | ||
| AstroServerDirective, | ||
| AstroSetDirective, | ||
| SvelteBindThisDirective, | ||
| SvelteStyleDirective, | ||
| SvelteClassDirective, | ||
| SvelteBindDirective, | ||
| SvelteUseDirective, | ||
| SvelteTransitionDirective, | ||
| SvelteInDirective, | ||
| SvelteOutDirective, | ||
| SvelteAnimateDirective, | ||
| SvelteAttachAttribute, | ||
|
|
||
| VueDefinition, | ||
| VueListRendering, | ||
| VueConditional, | ||
| VueRenderModifier, | ||
| VueUnique, | ||
| VueSlot, | ||
| VueTwoWayBinding, | ||
| VueCustomDirective, | ||
| // `v-bind`, etc. | ||
| VueOtherAttribute, | ||
| VueEvent, | ||
| VueContent, | ||
|
|
||
| Unknown, | ||
| } | ||
|
|
||
| #[derive(PartialEq, Eq, Clone)] | ||
| pub struct SortableHtmlAttribute(AnyHtmlAttribute); | ||
|
|
||
| impl SortableHtmlAttribute { | ||
| fn category(&self) -> SortCategory { | ||
| match &self.0 { | ||
| AnyHtmlAttribute::HtmlAttribute(attr) => { | ||
| if let Ok(attr_name) = attr | ||
| .name() | ||
| .and_then(|name| name.value_token()) | ||
| .as_ref() | ||
| .map(|token| token.text_trimmed()) | ||
| { | ||
| match attr_name { | ||
| // Vue ref attribute | ||
| "ref" => SortCategory::VueUnique, | ||
| _ => SortCategory::HtmlAttribute, | ||
| } | ||
| } else { | ||
| SortCategory::HtmlAttribute | ||
| } | ||
| } | ||
| AnyHtmlAttribute::HtmlAttributeSingleTextExpression(_) => SortCategory::HtmlAttribute, |
There was a problem hiding this comment.
Vue templates are still being sorted as “HTML first”.
Every plain attribute except ref is bucketed as HtmlAttribute, and that bucket sits before all the Vue-specific categories. In a .vue file, mixed cases like <div v-if="ok" id="x"> will therefore be normalised with id before v-if, so the new assist does not actually apply a full Vue ordering yet. A tiny Vue fixture mixing a plain attribute with v-if/v-for would lock this down once fixed.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@crates/biome_html_analyze/src/assist/source/use_sorted_attributes.rs` around
lines 235 - 293, The SortableHtmlAttribute::category currently buckets every
plain attribute (except "ref") as HtmlAttribute which leaves Vue directives like
"v-if" or ":" categorized incorrectly; update SortableHtmlAttribute::category to
inspect the attribute name (from attr.name().and_then(...).map(|t|
t.text_trimmed())) and if it matches Vue patterns map them to the appropriate
SortCategory (e.g. exact "v-for" -> SortCategory::VueListRendering,
"v-if"/"v-else-if"/"v-else" -> VueConditional, "v-model" -> VueTwoWayBinding,
names starting with "v-" -> VueOtherAttribute, names starting with ":" ->
VueOtherAttribute (v-bind), names starting with "@" or "v-on" -> VueEvent,
"v-slot" -> VueSlot, and keep "ref" -> VueUnique) instead of falling through to
HtmlAttribute; make this change inside the SortableHtmlAttribute::category match
arm for AnyHtmlAttribute::HtmlAttribute so Vue-specific attributes are
categorized before defaulting to SortCategory::HtmlAttribute.
dyc3
left a comment
There was a problem hiding this comment.
Looks good, just need to resolve the merge conflict
|
gtg @dyc3 |
Summary
Adds assist rule
useSortedAttributesfor HTML. Also refactors some of the code from the JSX version ofuseSortedAttributesto reduce code duplication.Closes #9334
Test Plan
Snapshots tests in
crates/biome_html_analyze/tests/specs/source/useSortedAttributesDocs
There is documentation in
crates/biome_html_analyze/src/assist/source/use_sorted_attributes.rs