- 
          
- 
                Notifications
    You must be signed in to change notification settings 
- Fork 735
fix(formatter): preserve component tag casing in Svelte/Astro/Vue files #7869
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(formatter): preserve component tag casing in Svelte/Astro/Vue files #7869
Conversation
| 🦋 Changeset detectedLatest commit: cfd4cb1 The changes in this PR will be included in the next version bump. This PR includes changesets to release 13 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 | 
| WalkthroughAdds file-source awareness to the HTML formatter and gates HTML-tag lowercasing on that context.  Possibly related PRs
 Suggested reviewers
 Pre-merge checks and finishing touches✅ Passed checks (5 passed)
 ✨ 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  | 
…and Vue Fixed issue where the HTML formatter incorrectly lowercased component names like <Button> to <button> when those names matched HTML element tag names in component framework files. The formatter now: - Preserves tag casing in Svelte, Astro, and Vue files - Maintains lowercasing behavior for pure HTML files - Passes file source context through formatting pipeline Fixes biomejs#7864
e3f8fa6    to
    80ba973      
    Compare
  
    There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (2)
- crates/biome_html_formatter/tests/specs/html/component-frameworks/html-canonical-lowercasing.html.snapis excluded by- !**/*.snapand included by- **
- crates/biome_html_formatter/tests/specs/html/component-frameworks/vue-component-casing.vue.snapis excluded by- !**/*.snapand included by- **
📒 Files selected for processing (11)
- .changeset/fix-html-component-tag-casing.md(1 hunks)
- crates/biome_html_formatter/src/context.rs(2 hunks)
- crates/biome_html_formatter/src/html/auxiliary/opening_element.rs(1 hunks)
- crates/biome_html_formatter/src/html/auxiliary/self_closing_element.rs(1 hunks)
- crates/biome_html_formatter/src/html/auxiliary/tag_name.rs(1 hunks)
- crates/biome_html_formatter/src/utils/metadata.rs(2 hunks)
- crates/biome_html_formatter/tests/spec_test.rs(1 hunks)
- crates/biome_html_formatter/tests/specs/html/component-frameworks/astro-component-casing.astro(1 hunks)
- crates/biome_html_formatter/tests/specs/html/component-frameworks/html-canonical-lowercasing.html(1 hunks)
- crates/biome_html_formatter/tests/specs/html/component-frameworks/svelte-component-casing.svelte(1 hunks)
- crates/biome_html_formatter/tests/specs/html/component-frameworks/vue-component-casing.vue(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (7)
- crates/biome_html_formatter/tests/specs/html/component-frameworks/vue-component-casing.vue
- crates/biome_html_formatter/tests/specs/html/component-frameworks/svelte-component-casing.svelte
- crates/biome_html_formatter/tests/specs/html/component-frameworks/html-canonical-lowercasing.html
- crates/biome_html_formatter/tests/spec_test.rs
- crates/biome_html_formatter/tests/specs/html/component-frameworks/astro-component-casing.astro
- crates/biome_html_formatter/src/utils/metadata.rs
- crates/biome_html_formatter/src/context.rs
🧰 Additional context used
📓 Path-based instructions (4)
crates/biome_*_{syntax,parser,formatter,analyze,factory,semantic}/**
📄 CodeRabbit inference engine (CLAUDE.md)
Maintain the per-language crate structure: biome_{lang}_{syntax,parser,formatter,analyze,factory,semantic}
Files:
- crates/biome_html_formatter/src/html/auxiliary/tag_name.rs
- crates/biome_html_formatter/src/html/auxiliary/opening_element.rs
- crates/biome_html_formatter/src/html/auxiliary/self_closing_element.rs
crates/biome_*/**
📄 CodeRabbit inference engine (CLAUDE.md)
Place core crates under /crates/biome_*/
Files:
- crates/biome_html_formatter/src/html/auxiliary/tag_name.rs
- crates/biome_html_formatter/src/html/auxiliary/opening_element.rs
- crates/biome_html_formatter/src/html/auxiliary/self_closing_element.rs
**/*.rs
📄 CodeRabbit inference engine (CONTRIBUTING.md)
**/*.rs: Format Rust files before committing (e.g., viajust fwhich formats Rust)
Document rules, assists, and options with inline rustdoc in source
Files:
- crates/biome_html_formatter/src/html/auxiliary/tag_name.rs
- crates/biome_html_formatter/src/html/auxiliary/opening_element.rs
- crates/biome_html_formatter/src/html/auxiliary/self_closing_element.rs
.changeset/*.md
📄 CodeRabbit inference engine (CONTRIBUTING.md)
.changeset/*.md: In changesets, only use #### or ##### headers; other header levels are not allowed
Changesets should cover user-facing changes only; internal changes do not need changesets
Use past tense for what you did and present tense for current Biome behavior in changesets
When fixing a bug in a changeset, start with an issue link (e.g., “Fixed #1234: …”)
When referencing a rule or assist in a changeset, include a link to its page on the website
Include code blocks in changesets when applicable to illustrate changes
End every sentence in a changeset with a period
Files:
- .changeset/fix-html-component-tag-casing.md
🧠 Learnings (2)
📚 Learning: 2025-10-15T09:22:15.851Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-10-15T09:22:15.851Z
Learning: Applies to crates/biome_formatter/src/lib.rs : Implement FormatLanguage for HtmlFormatLanguage with associated types: SyntaxLanguage=HtmlLanguage, Context=HtmlFormatContext, FormatRule=FormatHtmlSyntaxNode
Applied to files:
- crates/biome_html_formatter/src/html/auxiliary/tag_name.rs
📚 Learning: 2025-10-15T09:22:15.851Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-10-15T09:22:15.851Z
Learning: Applies to crates/biome_formatter/src/cst.rs : Create FormatHtmlSyntaxNode in cst.rs implementing FormatRule<HtmlSyntaxNode> and AsFormat/IntoFormat for HtmlSyntaxNode using the provided plumbing
Applied to files:
- crates/biome_html_formatter/src/html/auxiliary/tag_name.rs
🧬 Code graph analysis (3)
crates/biome_html_formatter/src/html/auxiliary/tag_name.rs (2)
crates/biome_html_formatter/src/context.rs (1)
file_source(77-79)crates/biome_html_formatter/src/utils/metadata.rs (1)
is_canonical_html_tag(726-736)
crates/biome_html_formatter/src/html/auxiliary/opening_element.rs (2)
crates/biome_html_formatter/src/context.rs (1)
file_source(77-79)crates/biome_html_formatter/src/utils/metadata.rs (1)
is_canonical_html_tag(726-736)
crates/biome_html_formatter/src/html/auxiliary/self_closing_element.rs (2)
crates/biome_html_formatter/src/context.rs (1)
file_source(77-79)crates/biome_html_formatter/src/utils/metadata.rs (1)
is_canonical_html_tag(726-736)
🔇 Additional comments (3)
crates/biome_html_formatter/src/html/auxiliary/tag_name.rs (1)
12-13: LGTM!The file-source context is correctly retrieved and passed to
is_canonical_html_tag. This change properly enables file-type-aware canonical tag detection.crates/biome_html_formatter/src/html/auxiliary/self_closing_element.rs (1)
21-22: LGTM!Correctly retrieves and propagates the file source to canonical tag detection, consistent with the changes in other formatter modules.
crates/biome_html_formatter/src/html/auxiliary/opening_element.rs (1)
51-52: LGTM!File-source context is correctly threaded through to canonical tag detection, maintaining consistency with the other formatting modules.
| @matanshavit did you use some AI tool? If so, please disclose it | 
| Yes, I used Claude. Would you like me to include that in the PR description and commits? I have mostly seen people other repositories asking to take out that information from commit messages, but if you'd prefer to leave it in, I can be sure to do that. update - I added this line to the end of the PR description - 
 | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A good first draft
        
          
                ...es/biome_html_formatter/tests/specs/html/component-frameworks/svelte-component-casing.svelte
          
            Show resolved
            Hide resolved
        
              
          
                crates/biome_html_formatter/tests/specs/html/component-frameworks/astro-component-casing.astro
          
            Show resolved
            Hide resolved
        
      Add test snapshots for Svelte and Astro component tag name casing preservation. Update spec_tests.rs to include .svelte and .astro file extensions in the test glob pattern.
Extracted tag lowercasing decision into a single helper function `should_lowercase_html_tag` to eliminate code duplication across opening_element, self_closing_element, and tag_name formatters. This makes the codebase more maintainable by centralizing the logic that determines when HTML tags should be lowercased based on both file source and tag canonicality.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me! Thank you. I left some suggestions, could you please apply them?
|  | ||
| /// Whether a tag should be lowercased in the current formatting context. | ||
| /// | ||
| /// Returns true only for canonical HTML tags in pure HTML files (.html). | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| /// Returns true only for canonical HTML tags in pure HTML files (.html). | |
| /// Returns `true` only for canonical HTML tags in pure HTML files (.html). | 
| #[derive(Debug, Clone, Default)] | ||
| #[derive(Debug, Clone)] | ||
| pub struct HtmlFormatOptions { | ||
| /// The file source (HTML, Svelte, Astro, or Vue). | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| /// The file source (HTML, Svelte, Astro, or Vue). | |
| /// The file source | 
The parentheses aren't needed because they can get outdated.
| /// Whether a tag should be lowercased in the current formatting context. | ||
| /// | ||
| /// Returns true only for canonical HTML tags in pure HTML files (.html). | ||
| /// Component frameworks (Svelte, Astro, Vue) preserve tag name casing. | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| /// Component frameworks (Svelte, Astro, Vue) preserve tag name casing. | |
| /// Component frameworks preserve tag name casing. | 
| /// | ||
| /// This combines file source checking with canonicality checking to provide | ||
| /// a single decision point for tag lowercasing behavior. | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| /// | |
| /// This combines file source checking with canonicality checking to provide | |
| /// a single decision point for tag lowercasing behavior. | 
This is an implementation detail; it isn't needed.
Simplify and improve documentation for tag lowercasing logic: - Remove redundant file source enumeration from HtmlFormatOptions - Streamline should_lowercase_html_tag documentation - Fix backtick formatting for consistency
| Awesome, thanks! Suggestions applied. Thanks for helping with the comments especially; good documentation is so important to maintaining the code. | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
crates/biome_html_formatter/src/utils/metadata.rs (1)
727-733: Consider linking toHTML_ALL_TAGSoris_canonical_html_tagin the doc comment.The doc comment is clear, but referencing [
HTML_ALL_TAGS] or [is_canonical_html_tag] would help developers understand what "canonical" means here and improve discoverability of related definitions./// Whether a tag should be lowercased in the current formatting context. /// /// Returns `true` only for canonical HTML tags in pure HTML files (.html). -/// Component frameworks preserve tag name casing. +/// Component frameworks preserve tag name casing. See [`is_canonical_html_tag`] +/// and [`HTML_ALL_TAGS`] for the definition of canonical tags. pub(crate) fn should_lowercase_html_tag(f: &HtmlFormatter, tag_name: &HtmlTagName) -> bool { f.options().file_source().is_html() && is_canonical_html_tag(tag_name) }
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
- crates/biome_html_formatter/src/context.rs(2 hunks)
- crates/biome_html_formatter/src/utils/metadata.rs(1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{rs,toml}
📄 CodeRabbit inference engine (CONTRIBUTING.md)
Format Rust and TOML files before committing (e.g., via
just f)
Files:
- crates/biome_html_formatter/src/context.rs
- crates/biome_html_formatter/src/utils/metadata.rs
**/*.rs
📄 CodeRabbit inference engine (CONTRIBUTING.md)
Document rules, assists, and their options with inline rustdoc in the Rust source
Files:
- crates/biome_html_formatter/src/context.rs
- crates/biome_html_formatter/src/utils/metadata.rs
🧠 Learnings (3)
📚 Learning: 2025-10-15T09:22:15.851Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-10-15T09:22:15.851Z
Learning: Applies to crates/biome_formatter/src/context.rs : Create HtmlFormatContext in context.rs with comments and source_map fields and implement FormatContext and CstFormatContext
Applied to files:
- crates/biome_html_formatter/src/context.rs
📚 Learning: 2025-10-15T09:22:15.851Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-10-15T09:22:15.851Z
Learning: Applies to crates/biome_formatter/src/comments.rs : Define HtmlCommentStyle implementing CommentStyle in comments.rs
Applied to files:
- crates/biome_html_formatter/src/context.rs
📚 Learning: 2025-10-15T09:22:15.851Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-10-15T09:22:15.851Z
Learning: Applies to crates/biome_formatter/src/comments.rs : Expose a public HtmlComments type alias: `pub type HtmlComments = Comments<HtmlLanguage>;`
Applied to files:
- crates/biome_html_formatter/src/context.rs
🧬 Code graph analysis (1)
crates/biome_html_formatter/src/context.rs (1)
packages/@biomejs/backend-jsonrpc/src/workspace.ts (10)
HtmlFileSource(9504-9506)
IndentStyle(454-454)
IndentWidth(455-455)
LineEnding(456-456)
LineWidth(462-462)
AttributePosition(447-447)
BracketSameLine(451-451)
WhitespaceSensitivity(914-914)
IndentScriptAndStyle(896-896)
SelfCloseVoidElements(900-900)
🔇 Additional comments (1)
crates/biome_html_formatter/src/context.rs (1)
12-79: LGTM! Clean implementation of file source tracking.The changes properly integrate
file_sourceintoHtmlFormatOptions:
- Explicit
Defaultimpl correctly initialises the field toHtmlFileSource::html()- Constructor and getter follow Rust conventions
- Doc comment is concise and clear
| 
 Seems good, do we want these extra links in the comment? | 
| Nah it's fine | 
Summary
Fixes #7864
The HTML formatter was incorrectly lowercasing component names like
<Button>to<button>in Svelte/Astro/Vue files when those names happened to match HTML element tag names. This broke imports from UI component libraries like Carbon Design, Material UI, and shadcn-svelte.Changes
file_sourcefield to track whether we're formatting HTML, Svelte, Astro, or Vue filesis_canonical_html_tag()to only return true for pure HTML filesspec_test.rsto use actual file source from extension instead of hardcodedHtmlFileSource::html()component-frameworks/directory to verify the fixBehavior
Before (buggy):
After (fixed):
Test Plan
<Button>,<TextInput>, etc. preserve casing.htmlfilesjust formatjust lintLLM use notice - This change was developed with Claude Code.