Skip to content

Conversation

@matanshavit
Copy link
Contributor

@matanshavit matanshavit commented Oct 25, 2025

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

  • Store file source in HtmlFormatOptions: Added file_source field to track whether we're formatting HTML, Svelte, Astro, or Vue files
  • Make tag canonicality check file-type-aware: Updated is_canonical_html_tag() to only return true for pure HTML files
  • Fix test infrastructure: Changed spec_test.rs to use actual file source from extension instead of hardcoded HtmlFileSource::html()
  • Add comprehensive tests: Created test suite in component-frameworks/ directory to verify the fix

Behavior

Before (buggy):

<!-- Input -->
<Button label="test" />

<!-- Output -->
<button label="test"/> <!-- ❌ Incorrectly lowercased -->

After (fixed):

<!-- Input -->
<Button label="test" />

<!-- Output -->
<Button label="test"/> <!-- ✅ Correctly preserved -->

Test Plan

  • ✅ All 54 HTML formatter tests pass
  • ✅ Component framework tests verify <Button>, <TextInput>, etc. preserve casing
  • ✅ HTML regression test confirms canonical tags still lowercase in .html files
  • ✅ Code formatted with just format
  • ✅ Linting passes with just lint
  • ✅ Changeset created

LLM use notice - This change was developed with Claude Code.

@changeset-bot
Copy link

changeset-bot bot commented Oct 25, 2025

🦋 Changeset detected

Latest commit: cfd4cb1

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

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

Not sure what this means? Click here to learn what changesets are.

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

@github-actions github-actions bot added A-Formatter Area: formatter L-HTML Language: HTML labels Oct 25, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 25, 2025

Walkthrough

Adds file-source awareness to the HTML formatter and gates HTML-tag lowercasing on that context. HtmlFormatOptions gained a private file_source: HtmlFileSource, a public new(file_source) constructor, a file_source(&self) getter and an explicit Default impl. Formatting now calls should_lowercase_html_tag(f, tag_name) (which checks formatter file source and canonical status) instead of the prior is_canonical_html_tag. Tests and spec fixtures for HTML, Svelte, Astro and Vue component-casing were added and test setup now passes the actual file source into HtmlFormatOptions. A changeset describing the behaviour was also added.

Possibly related PRs

Suggested reviewers

  • dyc3
  • ematipico

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Title Check ✅ Passed The title "fix(formatter): preserve component tag casing in Svelte/Astro/Vue files" is specific, concise, and clearly summarizes the primary change in the pull request. It uses conventional commit format, avoids vague terminology, and accurately reflects the main objective of fixing the incorrect lowercasing of component names across multiple framework file types.
Linked Issues Check ✅ Passed The pull request successfully addresses all coding-related objectives from issue #7864. The implementation stores file source in HtmlFormatOptions, makes the tag canonicality check file-type-aware via the new should_lowercase_html_tag function, updates test infrastructure to derive file source from extensions rather than hardcoding HTML, and adds comprehensive test coverage across Svelte, Astro, Vue, and HTML files. The changes ensure components preserve casing in framework files whilst canonical HTML tags still lowercase in pure HTML files.
Out of Scope Changes Check ✅ Passed All changes in the pull request are directly scoped to fixing component tag casing. The modifications focus exclusively on: tracking file source in formatting options, updating canonicality checks to be file-type-aware, correcting test infrastructure to use actual file sources, and adding test cases for Svelte, Astro, and Vue frameworks. No unrelated refactoring, cleanup, or tangential changes are present.
Description Check ✅ Passed The description is comprehensive and directly related to the changeset. It clearly explains the issue being fixed (issue #7864), the root cause, the implementation approach with specific details about changes made, before/after behaviour with examples, a test plan showing verification across multiple frameworks, and includes an LLM use notice as requested by maintainers.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

…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
@matanshavit matanshavit force-pushed the fix/7864-preserve-component-tag-casing branch from e3f8fa6 to 80ba973 Compare October 25, 2025 18:11
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: 1

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e3f8fa6 and 80ba973.

⛔ Files ignored due to path filters (2)
  • crates/biome_html_formatter/tests/specs/html/component-frameworks/html-canonical-lowercasing.html.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_formatter/tests/specs/html/component-frameworks/vue-component-casing.vue.snap is excluded by !**/*.snap and 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., via just f which 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.

@ematipico
Copy link
Member

@matanshavit did you use some AI tool? If so, please disclose it

@matanshavit
Copy link
Contributor Author

matanshavit commented Oct 25, 2025

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 -

LLM use notice - This change was developed with Claude Code.

Copy link
Contributor

@dyc3 dyc3 left a 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

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.
@matanshavit matanshavit requested a review from dyc3 October 28, 2025 21:59
Copy link
Member

@ematipico ematipico left a comment

Choose a reason for hiding this comment

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

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).
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
/// 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).
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
/// 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.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
/// Component frameworks (Svelte, Astro, Vue) preserve tag name casing.
/// Component frameworks preserve tag name casing.

Comment on lines 731 to 733
///
/// This combines file source checking with canonicality checking to provide
/// a single decision point for tag lowercasing behavior.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
///
/// 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
@matanshavit
Copy link
Contributor Author

Awesome, thanks! Suggestions applied. Thanks for helping with the comments especially; good documentation is so important to maintaining the code.

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

🧹 Nitpick comments (1)
crates/biome_html_formatter/src/utils/metadata.rs (1)

727-733: Consider linking to HTML_ALL_TAGS or is_canonical_html_tag in 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

📥 Commits

Reviewing files that changed from the base of the PR and between 9f3d926 and 1a1279f.

📒 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_source into HtmlFormatOptions:

  • Explicit Default impl correctly initialises the field to HtmlFileSource::html()
  • Constructor and getter follow Rust conventions
  • Doc comment is concise and clear

@matanshavit
Copy link
Contributor Author

🧹 Nitpick comments (1)

crates/biome_html_formatter/src/utils/metadata.rs (1)> 727-733: Consider linking to HTML_ALL_TAGS or is_canonical_html_tag in 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)
 }

Seems good, do we want these extra links in the comment?

@ematipico ematipico merged commit c80361d into biomejs:main Oct 30, 2025
11 of 13 checks passed
@ematipico
Copy link
Member

Nah it's fine

@github-actions github-actions bot mentioned this pull request Oct 29, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Formatter Area: formatter L-HTML Language: HTML

Projects

None yet

Development

Successfully merging this pull request may close these issues.

HTML/Svelte/Astro/Vue formatting incorrectly lowercases component names that are variations of HTML element tag names

3 participants