-
-
Notifications
You must be signed in to change notification settings - Fork 714
feat(core): embedded formatting in html files #7467
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
Conversation
|
WalkthroughAdds end-to-end embedded-content formatting across multiple crates. Introduces HtmlEmbeddedContent in the HTML grammar and parser, new Possibly related PRs
Suggested labels
Suggested reviewers
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests
Comment |
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: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
crates/biome_service/src/settings.rs (1)
1856-1865
: HTML override parser settings aren’t propagated (parser field ignored)to_html_language_settings doesn’t copy parser settings from overrides, so OverrideSettingPattern::apply_overrides_to_html_parser_options will always see defaults. This breaks per-pattern interpolation overrides.
Proposed fix (mirrors JS/CSS patterns):
-fn to_html_language_settings( - mut conf: HtmlConfiguration, - _parent_settings: &LanguageSettings<HtmlLanguage>, -) -> LanguageSettings<HtmlLanguage> { +fn to_html_language_settings( + mut conf: HtmlConfiguration, + parent_settings: &LanguageSettings<HtmlLanguage>, +) -> LanguageSettings<HtmlLanguage> { let mut language_setting: LanguageSettings<HtmlLanguage> = LanguageSettings::default(); let formatter = conf.formatter.take().unwrap_or_default(); language_setting.formatter = formatter.into(); + // Propagate parser overrides (e.g., interpolation) with parent fallback + let parser = conf.parser.take().unwrap_or_default(); + let parent_parser = &parent_settings.parser; + language_setting.parser.interpolation = + parser.interpolation.or(parent_parser.interpolation); + language_setting }crates/biome_formatter/src/lib.rs (1)
451-466
: Bug: wrong bounds inIndentWidthFromIntError
message.The error mentions
LineWidth::{MIN,MAX}
instead ofIndentWidth::{MIN,MAX}
and the docstring names the wrong type. This will confuse users (and tests).-/// Error type returned when converting a u16 to a [LineWidth] fails +/// Error type returned when converting a u8 to an [IndentWidth] fails #[derive(Clone, Copy, Debug)] pub struct IndentWidthFromIntError(pub u8); impl std::fmt::Display for IndentWidthFromIntError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { writeln!( f, "The indent width should be between {} and {}, got {}", - LineWidth::MIN, - LineWidth::MAX, + IndentWidth::MIN, + IndentWidth::MAX, self.0, ) } }crates/biome_service/src/file_handlers/html.rs (1)
311-377
: Script type handling: don’t treat non‑JS scripts as JSCurrently any <script> (except type="module" which is rightly JS) is parsed as JS. That wrongly catches e.g. application/ld+json, application/json, importmap, templates, etc.
Suggested tightening:
- if name_text.text_trimmed() == "script" { - let is_modules = opening_element.attributes().iter().any(|attr| { /* checks */ }); - let file_source = if is_modules { JsFileSource::js_module() } else { JsFileSource::js_script() }; + if name_text.text_trimmed() == "script" { + // Determine script type + let script_type = opening_element.attributes().iter().find_map(|attr| { + let attr = attr.as_html_attribute()?; + let name = attr.name().ok()?.value_token().ok()?; + if name.text_trimmed() != "type" { + return None; + } + attr.initializer()? + .value().ok()? + .as_html_string().cloned()? + .value_token().ok() + .map(|t| t.text_trimmed().to_owned()) + }); + + // Only parse as JS when type is absent or a known JS MIME, or explicitly "module" + let (is_module, is_js) = match script_type.as_deref() { + Some("module") => (true, true), + None => (false, true), // default JS + Some("text/javascript" | "application/javascript" | "text/ecmascript" | "application/ecmascript") => (false, true), + // importmap and json should NOT be parsed as JS + Some("importmap" | "application/json" | "application/ld+json") => (false, false), + _ => (false, false), + }; + if !is_js { + return Some(Vec::new()); + } + let file_source = if is_module { JsFileSource::js_module() } else { JsFileSource::js_script() };Add tests for script types you intend to skip.
🧹 Nitpick comments (29)
crates/biome_service/src/settings.rs (1)
1636-1643
: Typo in method name (“doublet”) — align with existing namingHtmlParseOptions uses with_double_text_expression elsewhere; the new setter is named set_doublet_text_expression. Let’s rename to set_double_text_expression for consistency and future discoverability, and update call sites.
Apply this change in this file after renaming the method in the parser crate:
- options.set_doublet_text_expression(interpolation.value()); + options.set_double_text_expression(interpolation.value());crates/biome_html_formatter/src/html/lists/element_list.rs (1)
63-65
: Tiny grammar nit“call it’s formatter” → “call its formatter”.
- // we know the parser will only emit one of these. We can simply call it's formatter and be done. + // we know the parser will only emit one of these. We can simply call its formatter and be done.crates/biome_html_parser/src/parser.rs (1)
121-126
: Rename set_doublet_text_expression → set_double_text_expressionTypo in public API; best to correct now for consistency with with_double_text_expression and HtmlTextExpressions::Double.
Minimal change:
- pub fn set_doublet_text_expression(&mut self, value: bool) { + pub fn set_double_text_expression(&mut self, value: bool) { match value { true => self.text_expression = Some(TextExpressionKind::Double), false => self.text_expression = None, } }Then update call sites (e.g., settings.rs).
crates/biome_html_formatter/src/html/auxiliary/element.rs (1)
17-19
: Update the comment to reflect embedded formatting now existsThe note about “script/style not yet implemented” is stale after this PR. Please align the docs with the new embedded pathway.
-/// `pre` tags are "preformatted", so we should not format the content inside them. <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/pre> -/// We ignore the `script` and `style` tags as well, since embedded language parsing/formatting is not yet implemented. +/// `pre` tags are "preformatted", so we do not format their content. <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/pre> +/// `script` and `style` are handled via embedded-language formatting, not as verbatim here. const HTML_VERBATIM_TAGS: &[&str] = &["pre"];crates/biome_cli/tests/cases/html.rs (1)
103-104
: Nit: prefer a valid CSS property in the sampleConsider using text-align over align to avoid future confusion if CSS validation ever tightens in tests. No behaviour change implied.
-<style>#id .class div > p { background-color: red; align: center; padding: 0; } </style> +<style>#id .class div > p { background-color: red; text-align: center; padding: 0; } </style>Also applies to: 146-148
crates/biome_html_syntax/src/element_ext.rs (1)
16-18
: Nit: binary_search requires sorted input; add a guard or simplifyVOID_ELEMENTS is sorted today. To avoid accidental reordering regressions, either add a debug_assert!(is_sorted) in tests/build.rs or use contains() for clarity (perf impact is negligible at this size).
- Ok(VOID_ELEMENTS - .binary_search(&name.value_token()?.text_trimmed()) - .is_ok()) + Ok(VOID_ELEMENTS.contains(&name.value_token()?.text_trimmed()))crates/biome_html_formatter/src/lib.rs (1)
209-221
: Tiny allocation nitIf Formatter exposes write_element, you can avoid allocating a Vec here. If not, feel free to ignore.
- f.write_elements(vec![ - FormatElement::Tag(StartEmbedded(range)), - FormatElement::Tag(EndEmbedded), - ])?; + // If available: + // f.write_element(FormatElement::Tag(StartEmbedded(range)))?; + // f.write_element(FormatElement::Tag(EndEmbedded))?;crates/biome_service/src/workspace/server.tests.rs (1)
110-172
: Solid E2E formatting test; add an idempotency check.Ensures the embedded second pass is stable. Also, consider using valid CSS (e.g.
.cls {}
) to avoid parser noise later.Apply this diff to assert idempotency:
@@ let result = workspace .format_file(FormatFileParams { path: Utf8PathBuf::from("/project/file.html").into(), project_key, }) .unwrap(); + // Formatting should be idempotent + let result2 = workspace + .format_file(FormatFileParams { + path: Utf8PathBuf::from("/project/file.html").into(), + project_key, + }) + .unwrap(); + assert_eq!(result2.as_code(), result.as_code(), "Formatting must be idempotent"); + insta::assert_snapshot!(result.as_code(), @r#"crates/biome_formatter/src/format_element/tag.rs (1)
72-76
: Polish the docs for the new tag.Tiny wording tweak for clarity.
Apply this diff:
- /// A content will be formatted later by another formatter. - /// These tags must be removed during the second pass. + /// Marks content to be formatted later by another formatter. + /// These tags are replaced during the second pass.crates/biome_service/src/file_handlers/mod.rs (2)
646-653
: Document the embedded-format callback (and consider&[T]
).Add brief docs; optionally accept a slice to avoid forcing allocations.
Apply this diff to document the callback:
-type FormatEmbedded = fn( +/// Callback used to format embedded segments collected during the first pass. +/// The implementor formats each segment and returns the final `Printed`. +type FormatEmbedded = fn( &BiomePath, &DocumentFileSource, AnyParse, &Settings, Vec<FormatEmbedNode>, ) -> Result<Printed, WorkspaceError>;If you want the zero-copy variant later:
- Change the fifth parameter to
&[FormatEmbedNode]
.- Update implementors accordingly.
654-659
: Add field-level docs toFormatEmbedNode
.Clarifies intent for future readers.
Apply this diff:
-pub(crate) struct FormatEmbedNode { - pub(crate) range: TextRange, - pub(crate) node: SendEmbeddedParse, - pub(crate) source: DocumentFileSource, -} +/// Describes one embedded region to be formatted on the second pass. +pub(crate) struct FormatEmbedNode { + /// The absolute range in the host document for this embedded content. + pub(crate) range: TextRange, + /// The parsed subtree (offset parse) of the embedded language. + pub(crate) node: SendEmbeddedParse, + /// The language/file-source of the embedded content (e.g. JS, CSS). + pub(crate) source: DocumentFileSource, +}crates/biome_formatter/src/lib.rs (3)
24-24
: Dropextern crate core
; it's redundant in 2021+.Rust injects
core
automatically; this line just adds noise.-extern crate core;
894-939
: Embedded formatting API: avoid cloning and the follow-up swap.The visitor approach is solid. Returning a cloned
Document
and also mutatingself.document
forces callsites into a needless copy +swap_document
. Make it mutate in place and return()
(or&mut Self
for chaining).- #[must_use] - pub fn format_embedded<F>(&mut self, fn_format_embedded: F) -> Document + pub fn format_embedded<F>(&mut self, fn_format_embedded: F) where F: Fn(TextRange) -> Option<Document>, { let document = &mut self.document; @@ - ElementTransformer::transform_document( + ElementTransformer::transform_document( document, &mut EmbeddedVisitor { fn_format_embedded }, ); - - document.clone() }Outside this file (example in
crates/biome_service/src/file_handlers/html.rs
):- let new_document = formatted.format_embedded(move |range| { /* ... */ }); - formatted.swap_document(new_document); + formatted.format_embedded(move |range| { /* ... */ });NB: unconditionally replacing
EndEmbedded
with a hard line may double-insert newlines in some layouts; consider deferring end-tag handling to the caller or making it configurable in a follow-up.
950-953
:swap_document
becomes unnecessary if the above refactor lands.Keep for now, but mark as deprecated later once callsites are updated.
crates/biome_service/src/file_handlers/css.rs (1)
150-169
: Parser option resolution LGTM.Overrides applied; future: consider whether
DocumentFileSource
should influence defaults.crates/biome_service/src/workspace/server.rs (5)
349-375
: Second pass for HTML: solid integration; consider DRYing it upThe script/style extraction here mirrors the logic in change_file(). A small helper (e.g. compute_embedded_nodes(project_key, &path, &file_source, &html_root)) would remove duplication and keep behaviour in lockstep.
Example sketch (outside this hunk):
fn compute_embedded_nodes<FsIdx>( &self, project_key: ProjectKey, biome_path: &BiomePath, file_source: &DocumentFileSource, html_root: &HtmlRoot, cache: &mut NodeCache, source_index_fn: FsIdx, ) -> Result<(Vec<EmbeddedJsContent>, Vec<EmbeddedCssContent>), WorkspaceError> where FsIdx: Fn(DocumentFileSource) -> usize + Copy { ... }
463-501
: Build once, use fast: minor tidyYou could reserve capacity to avoid a couple of reallocations.
- .map(|doc| { - doc.embedded_scripts + .map(|doc| { + let mut out = Vec::with_capacity(doc.embedded_scripts.len() + doc.embedded_styles.len()); + out.extend( + doc.embedded_scripts .clone() .iter() .map(|node| { // ... }) - .chain(doc.embedded_styles.clone().iter().map(|node| { + ); + out.extend(doc.embedded_styles.clone().iter().map(|node| { // ... - })) - .collect::<Vec<_>>() + })); + out })
510-543
: JS extraction path: looks rightDeriving JsParserOptions from project settings and walking descendants is fine. Consider iterating only HtmlElement nodes if performance ever bites, but this is OK for now.
545-579
: CSS options move semantics: avoid relying on Copyparse_embedded_style takes CssParserOptions by value and uses it multiple times internally. At the call site you pass options (by move) on each loop iteration; this compiles only if CssParserOptions is Copy. Be explicit to future‑proof.
- let Some(list) = crate::file_handlers::html::parse_embedded_style( + let Some(list) = crate::file_handlers::html::parse_embedded_style( element.clone(), cache, - options, + options.clone(), &source_index_fn, ) else {If CssParserOptions isn’t Clone, change parse_embedded_style to accept &CssParserOptions.
1281-1306
: Second pass on change_file(): keep in sync with open_file_internalSame note as above: a shared helper would prevent drift between these two nearly identical blocks.
crates/biome_service/src/file_handlers/html.rs (2)
379-424
: CSS extraction helper: looks fineReads cleanly and mirrors the JS path. One tiny nit: pass a cloned options (or take &CssParserOptions) to avoid relying on Copy in closures.
468-534
: format_embedded: avoid O(n) lookup per embedded regionEach callback invocation linearly searches embedded_nodes. Build a map keyed by TextRange once, then lookups are O(1). Also, double hard lines before content may introduce an extra blank line; confirm intended spacing.
- let new_document = formatted.format_embedded(move |range| { - let mut iter = embedded_nodes.iter(); - let node = iter.find(|node| node.range == range)?; + use rustc_hash::FxHashMap; + let by_range: FxHashMap<_, _> = embedded_nodes + .iter() + .map(|n| (n.range, n)) + .collect(); + let new_document = formatted.format_embedded(move |range| { + let node = by_range.get(&range)?;If spacing should be a single preceding hard line when indenting, consider:
- let elements = vec![ - FormatElement::Line(LineMode::Hard), - FormatElement::Tag(Tag::StartIndent), - FormatElement::Line(LineMode::Hard), + let elements = vec![ + FormatElement::Line(LineMode::Hard), + FormatElement::Tag(Tag::StartIndent),crates/biome_formatter/src/format_element/document.rs (4)
175-184
: Avoid cloning the whole document during transformWe can dodge an O(n) clone by moving the vec out and transforming it in one pass.
- pub fn transform_document<V: DocumentVisitor>(document: &mut Document, visitor: &mut V) { - document.elements = Self::transform_elements( - document.elements.clone(), - visitor, - &ElementPath::new(Vec::new()), - ); - } + pub fn transform_document<V: DocumentVisitor>(document: &mut Document, visitor: &mut V) { + let elements = std::mem::take(&mut document.elements); + document.elements = Self::transform_elements( + elements, + visitor, + &ElementPath::new(Vec::new()), + ); + }
200-206
: Preserve interning identity where possibleRebuilding
Interned
unconditionally drops sharing/identity, which can increase memory and alter IR refs (<ref interned *>
). Consider reusing the originalInterned
when children are unchanged.Would you like a follow-up patch that tracks a “changed” flag to conditionally rebuild the
Interned
?
194-197
: Trim path allocations
current_path.clone()
per node/variant is a lot of small Vec clones. A small stack (SmallVec<[usize; 8]>
) or push/pop on a shared Vec passed by &mut would reduce churn.Also applies to: 212-215
207-224
: Guard the unsafe best-fitting rebuildYou rely on
from_vec_unchecked
’s internal assert. Add a localdebug_assert!(variants.len() >= 2)
before the call to make the invariant obvious at the callsite.let variants: Vec<Box<[FormatElement]>> = best_fitting .variants() .iter() .enumerate() .map(|(variant_index, variant)| { let mut variant_path = current_path.clone(); variant_path.push(variant_index); let variant_path = ElementPath::new(variant_path); Self::transform_elements(variant.to_vec(), visitor, &variant_path) .into_boxed_slice() }) .collect(); + debug_assert!(variants.len() >= 2, "BestFitting requires at least two variants"); unsafe { FormatElement::BestFitting(BestFittingElement::from_vec_unchecked( variants, )) }
crates/biome_service/src/file_handlers/grit.rs (1)
93-104
: Explicitly return unit for clarity (optional)The empty body returns
()
, which is fine givenParserOptions = ()
. Returning it explicitly can make the intent clearer to readers.- ) -> Self::ParserOptions { - } + ) -> Self::ParserOptions { () }crates/biome_service/src/file_handlers/javascript.rs (1)
186-207
: Parse options: confirm hard-coded grit metavariables
grit_metavariables
is forced tofalse
. If theJsParserSettings.grit_metavariables
knob is meant to be honoured here (like other per-language parsers), thread it through; otherwise, a brief comment would help.crates/biome_service/src/file_handlers/graphql.rs (1)
112-119
: Be explicit about returning unit in resolve_parse_options
Add an explicit()
return to this function. ParserSettings and ParserOptions are both()
, and there are no GraphQL-specific parser options or overrides to propagate.fn resolve_parse_options( _overrides: &OverrideSettings, _language: &Self::ParserSettings, _path: &BiomePath, _file_source: &DocumentFileSource, ) -> Self::ParserOptions { -} + () +}
crates/biome_html_formatter/src/html/auxiliary/embedded_content.rs
Outdated
Show resolved
Hide resolved
if is_embedded_language_tag { | ||
// embedded language tags always have 1 element as content | ||
let list = p.start(); | ||
if p.at(HTML_LITERAL) { | ||
let m = p.start(); | ||
p.bump(HTML_LITERAL); | ||
m.complete(p, HTML_EMBEDDED_CONTENT); | ||
} | ||
list.complete(p, HTML_ELEMENT_LIST); | ||
|
||
parse_closing_tag(p).or_add_diagnostic(p, expected_closing_tag); | ||
} else { | ||
loop { | ||
ElementList.parse_list(p); | ||
if let Some(mut closing) = | ||
parse_closing_tag(p).or_add_diagnostic(p, expected_closing_tag) | ||
&& !closing.text(p).contains(opening_tag_name.as_str()) | ||
{ | ||
p.error(expected_matching_closing_tag(p, closing.range(p)).into_diagnostic(p)); | ||
closing.change_to_bogus(p); | ||
continue; | ||
} | ||
break; | ||
} |
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.
Missing matching-closing-tag check for embedded tags (regression risk)
For <script>/<style>/<pre>
, we no longer validate that the closing tag matches the opening. That can silently accept <script>...</style>
, producing a misleading CST and awkward formatter behaviour.
Proposed fix: reuse the same matching loop used for non-embedded elements.
let opening = m.complete(p, HTML_OPENING_ELEMENT);
- if is_embedded_language_tag {
+ if is_embedded_language_tag {
// embedded language tags always have 1 element as content
let list = p.start();
if p.at(HTML_LITERAL) {
let m = p.start();
p.bump(HTML_LITERAL);
m.complete(p, HTML_EMBEDDED_CONTENT);
}
list.complete(p, HTML_ELEMENT_LIST);
-
- parse_closing_tag(p).or_add_diagnostic(p, expected_closing_tag);
+ // Ensure the closing tag matches the opening tag, like the non-embedded path
+ loop {
+ if let Some(mut closing) =
+ parse_closing_tag(p).or_add_diagnostic(p, expected_closing_tag)
+ && !closing.text(p).contains(opening_tag_name.as_str())
+ {
+ p.error(expected_matching_closing_tag(p, closing.range(p)).into_diagnostic(p));
+ closing.change_to_bogus(p);
+ continue;
+ }
+ break;
+ }
} else {
loop {
ElementList.parse_list(p);
if let Some(mut closing) =
parse_closing_tag(p).or_add_diagnostic(p, expected_closing_tag)
&& !closing.text(p).contains(opening_tag_name.as_str())
{
p.error(expected_matching_closing_tag(p, closing.range(p)).into_diagnostic(p));
closing.change_to_bogus(p);
continue;
}
break;
}
}
📝 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.
if is_embedded_language_tag { | |
// embedded language tags always have 1 element as content | |
let list = p.start(); | |
if p.at(HTML_LITERAL) { | |
let m = p.start(); | |
p.bump(HTML_LITERAL); | |
m.complete(p, HTML_EMBEDDED_CONTENT); | |
} | |
list.complete(p, HTML_ELEMENT_LIST); | |
parse_closing_tag(p).or_add_diagnostic(p, expected_closing_tag); | |
} else { | |
loop { | |
ElementList.parse_list(p); | |
if let Some(mut closing) = | |
parse_closing_tag(p).or_add_diagnostic(p, expected_closing_tag) | |
&& !closing.text(p).contains(opening_tag_name.as_str()) | |
{ | |
p.error(expected_matching_closing_tag(p, closing.range(p)).into_diagnostic(p)); | |
closing.change_to_bogus(p); | |
continue; | |
} | |
break; | |
} | |
let opening = m.complete(p, HTML_OPENING_ELEMENT); | |
if is_embedded_language_tag { | |
// embedded language tags always have 1 element as content | |
let list = p.start(); | |
if p.at(HTML_LITERAL) { | |
let m = p.start(); | |
p.bump(HTML_LITERAL); | |
m.complete(p, HTML_EMBEDDED_CONTENT); | |
} | |
list.complete(p, HTML_ELEMENT_LIST); | |
- | |
// Ensure the closing tag matches the opening tag, like the non-embedded path | |
loop { | |
if let Some(mut closing) = | |
parse_closing_tag(p).or_add_diagnostic(p, expected_closing_tag) | |
&& !closing.text(p).contains(opening_tag_name.as_str()) | |
{ | |
p.error(expected_matching_closing_tag(p, closing.range(p)).into_diagnostic(p)); | |
closing.change_to_bogus(p); | |
continue; | |
} | |
break; | |
} | |
} else { | |
loop { | |
ElementList.parse_list(p); | |
if let Some(mut closing) = | |
parse_closing_tag(p).or_add_diagnostic(p, expected_closing_tag) | |
&& !closing.text(p).contains(opening_tag_name.as_str()) | |
{ | |
p.error(expected_matching_closing_tag(p, closing.range(p)).into_diagnostic(p)); | |
closing.change_to_bogus(p); | |
continue; | |
} | |
break; | |
} | |
} |
🤖 Prompt for AI Agents
In crates/biome_html_parser/src/syntax/mod.rs around lines 156 to 179,
embedded-language branch currently skips validating that the closing tag matches
the opening tag; update it to reuse the same matching-closing-tag loop used for
non-embedded elements: instead of unconditionally consuming a single
HTML_LITERAL element and calling parse_closing_tag once, run a loop that parses
the element list (or the single embedded content), calls
parse_closing_tag(...).or_add_diagnostic(...), checks that the closing tag text
contains the opening_tag_name, and on mismatch emits the
expected_matching_closing_tag diagnostic, changes the closing node to bogus, and
continues the loop until a matching closing tag is found.
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.
@dyc3 does this comment make sense to you?
/// `pre` tags are "preformatted", so we should not format the content inside them. <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/pre> | ||
/// We ignore the `script` and `style` tags as well, since embedded language parsing/formatting is not yet implemented. | ||
const HTML_VERBATIM_TAGS: &[&str] = &["script", "style", "pre"]; | ||
const HTML_VERBATIM_TAGS: &[&str] = &["pre"]; |
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 comment should be updated
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.
Nice work!
Just a little concerned about the visitor implementation, since it's generated, scarce on documentation, and the path logic looks suspicious. I'm a bit wary we might be adding code that nobody properly understands here.
); | ||
} | ||
|
||
fn transform_elements<V: DocumentVisitor>( |
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.
Can you add an explanation of how this algorithm works, including the role of the base_path
in it? As far as I can see the ElementPath
is not used for anything useful, but if it is, I'm not seeing it.
|
||
f.write_elements(vec![ | ||
FormatElement::Tag(StartEmbedded(range)), | ||
FormatElement::Tag(EndEmbedded), |
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.
Is there ever a situation where EndEmbedded
doesn't immediately follow StartEmbedded
? If not, doesn't it make more sense to just use a "self-closing" Embedded(range)
element, or is this just a convention we're following?
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.
We can potentially have nested embedded languages.
For example, html -> js -> graphql:
<script>
const query = gql`
query {
hello
}
`;
</script>
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.
Tag elements must have a closing tag. We have a check that makes sure that starting tags have a closing tag.
We could create a variant that isn't a tag
let mut formatted = format_node(options, &tree)?; | ||
let new_document = formatted.format_embedded(move |range| { | ||
let mut iter = embedded_nodes.iter(); | ||
let node = iter.find(|node| node.range == range)?; |
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.
Would it be possible to identify the node with an index instead of a range? It seems unfortunate we need to iterate all nodes again just to compare ranges.
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.
I couldn't find a way to do so. Embedded roots are saved in the workspace and the formatter doesn't know anything about them. Even if the formatter tracks and returns those indexes, in the workspace, embedded nodes are saved by language (a list for JS roots and list for CSS roots). This means that an index isn't reliable anymore.
Co-authored-by: Arend van Beelen jr. <arend@arendjr.nl>
Co-authored-by: Arend van Beelen jr. <arend@arendjr.nl>
Co-authored-by: Arend van Beelen jr. <arend@arendjr.nl>
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: 3
♻️ Duplicate comments (6)
crates/biome_html_formatter/src/html/auxiliary/embedded_content.rs (1)
1-3
: Correct import; previous mismatch resolvedUsing biome_rowan::TextRange is the right type here. Cheers for fixing the earlier slip.
crates/biome_formatter/src/format_element/document.rs (1)
607-610
: Start/EndEmbedded IR printing now correct – cheers
StartEmbedded(_)
prints an opening arm and isn’t grouped with end-tags;EndEmbedded
closes. Matches the earlier suggestion.Also applies to: 624-626
crates/biome_service/src/file_handlers/html.rs (3)
283-297
: Excellent consistency improvement!The parse function now correctly uses
settings.parse_options::<HtmlLanguage>()
instead of manually constructing options, ensuring proper override handling and consistency with other language handlers.
299-362
: Consider using node indices instead of range comparison for performance.The current approach requires iterating through all nodes to match ranges. Using indices would be more efficient.
Would you like me to generate a verification script to measure the performance impact of range-based lookups in embedded content extraction?
462-464
: Performance concern with linear search.Finding the matching embedded node via linear search through
embedded_nodes.iter().find()
could be inefficient for documents with many embedded elements. Consider using a HashMap for O(1) lookups.- let mut iter = embedded_nodes.iter(); - let node = iter.find(|node| node.range == range)?; + // Consider preprocessing embedded_nodes into a HashMap<TextRange, FormatEmbedNode> + // for O(1) lookups instead of O(n) linear searchcrates/biome_service/src/workspace/document.rs (1)
68-73
: Fixed: CSS node() now returns CssLanguage.This addresses the earlier bug report. Nice one.
🧹 Nitpick comments (9)
crates/biome_html_formatter/src/html/auxiliary/embedded_content.rs (2)
11-13
: Confirm range semantics for embedded contentReturning Some(node.range()) assumes the range is exactly the inner script/style text. If it includes the tag wrappers or surrounding indentation, the second pass may parse the wrong slice and misalign mappings. Please verify this returns the pure embedded source range; if not, use the inner content/token range instead and add a regression test.
I can draft a focused test case asserting the range covers only the inner text for <script> and <style>.
4-5
: Tiny win: derive Copy on the unit structUnit-like formatter types are trivially copyable; adding Copy can reduce noise in generic code.
-#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Copy, Default)] pub(crate) struct FormatHtmlEmbeddedContent;crates/biome_js_syntax/src/syntax_node.rs (1)
29-29
: Public alias looks good; add a doc comment for discoverabilityExpose a short rustdoc explaining when to use this vs
JsSyntaxNode
.crates/biome_css_syntax/src/syntax_node.rs (1)
25-25
: CSS offset alias approved; consider rustdocMirror the JS alias doc so users find this entrypoint easily.
crates/biome_formatter/src/lib.rs (3)
24-24
: Dropextern crate core;
Not needed in Rust 2018+; avoid churn and warnings.
894-935
: Avoid cloning the document and redundant swap
format_embedded
mutatesself.document
and then returns a clone, which the caller swaps back in. Return a reference instead and drop the swap call downstream.- #[must_use] - pub fn format_embedded<F>(&mut self, fn_format_embedded: F) -> Document + #[must_use] + pub fn format_embedded<F>(&mut self, fn_format_embedded: F) -> &Document where F: Fn(TextRange) -> Option<Document>, { let document = &mut self.document; … - document.clone() + document }Then remove the
swap_document
usage at the call site.Happy to push a follow-up diff across
biome_service
to remove the now-unnecessaryswap_document
.
946-949
:swap_document
likely unnecessary with the above changeIf
format_embedded
returns&Document
, this can be deleted.crates/biome_service/src/workspace/document.rs (2)
14-15
: Prefer allow over expect for unused diagnostics.
#[expect(unused)]
will fail the build once the field becomes used. If you just want to silence the lint, useallow
(or narrowerdead_code
) at field scope.- #[expect(unused)] + #[allow(dead_code)]
121-124
: Doc wording for Option/Result is ambiguous.Clarify when
None
,Ok
andErr
occur.- /// - `Option`: if the file can be read - /// - `Result`: if the file is read, but the file is too large + /// - `Option`: `None` if the file cannot be read; `Some(..)` otherwise. + /// - `Result`: `Ok(AnyParse)` when parsed; `Err(FileTooLarge)` when rejected due to size.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (11)
crates/biome_css_formatter/src/lib.rs
(2 hunks)crates/biome_css_syntax/src/syntax_node.rs
(1 hunks)crates/biome_formatter/src/format_element/document.rs
(5 hunks)crates/biome_formatter/src/lib.rs
(7 hunks)crates/biome_html_formatter/src/html/auxiliary/embedded_content.rs
(1 hunks)crates/biome_html_syntax/src/element_ext.rs
(2 hunks)crates/biome_js_formatter/src/lib.rs
(2 hunks)crates/biome_js_syntax/src/syntax_node.rs
(1 hunks)crates/biome_service/src/file_handlers/html.rs
(10 hunks)crates/biome_service/src/workspace/document.rs
(8 hunks)crates/biome_service/src/workspace/server.tests.rs
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- crates/biome_html_syntax/src/element_ext.rs
- crates/biome_service/src/workspace/server.tests.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_css_syntax/src/syntax_node.rs
crates/biome_js_syntax/src/syntax_node.rs
crates/biome_css_formatter/src/lib.rs
crates/biome_html_formatter/src/html/auxiliary/embedded_content.rs
crates/biome_js_formatter/src/lib.rs
crates/biome_*/**
📄 CodeRabbit inference engine (CLAUDE.md)
Place core crates under /crates/biome_*/
Files:
crates/biome_css_syntax/src/syntax_node.rs
crates/biome_js_syntax/src/syntax_node.rs
crates/biome_css_formatter/src/lib.rs
crates/biome_html_formatter/src/html/auxiliary/embedded_content.rs
crates/biome_js_formatter/src/lib.rs
crates/biome_service/src/workspace/document.rs
crates/biome_service/src/file_handlers/html.rs
crates/biome_formatter/src/lib.rs
crates/biome_formatter/src/format_element/document.rs
**/*.rs
📄 CodeRabbit inference engine (CONTRIBUTING.md)
Format all Rust source files before committing (just f)
Files:
crates/biome_css_syntax/src/syntax_node.rs
crates/biome_js_syntax/src/syntax_node.rs
crates/biome_css_formatter/src/lib.rs
crates/biome_html_formatter/src/html/auxiliary/embedded_content.rs
crates/biome_js_formatter/src/lib.rs
crates/biome_service/src/workspace/document.rs
crates/biome_service/src/file_handlers/html.rs
crates/biome_formatter/src/lib.rs
crates/biome_formatter/src/format_element/document.rs
crates/biome_js_formatter/**/*.rs
📄 CodeRabbit inference engine (crates/biome_js_formatter/CONTRIBUTING.md)
crates/biome_js_formatter/**/*.rs
: Use the generic Format trait and FormatNode for AST nodes when implementing the formatter
Build an intermediate IR via the provided helper APIs when formatting
Import the FormatNode trait and implement it for your Node
When a token is mandatory and present in the AST, use the AST-provided token (e.g., node.l_paren_token().format()) instead of hardcoded tokens
For non-mandatory tokens, use the formatter helper functions (e.g., token, space_token) instead of hardcoding
Do not attempt to fix code: if a mandatory token/node is missing, return None instead
Use dbg_write! to debug and inspect the written IR elements during formatter development
Files:
crates/biome_js_formatter/src/lib.rs
🧠 Learnings (22)
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/lib.rs : Expose a documented public function format_node(options: HtmlFormatOptions, root: &HtmlSyntaxNode) -> FormatResult<Formatted<HtmlFormatContext>> delegating to biome_formatter::format_node
Applied to files:
crates/biome_css_formatter/src/lib.rs
crates/biome_html_formatter/src/html/auxiliary/embedded_content.rs
crates/biome_js_formatter/src/lib.rs
crates/biome_service/src/file_handlers/html.rs
crates/biome_formatter/src/lib.rs
📚 Learning: 2025-08-11T11:48:52.001Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:52.001Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : Import the FormatNode trait and implement it for your Node
Applied to files:
crates/biome_css_formatter/src/lib.rs
crates/biome_html_formatter/src/html/auxiliary/embedded_content.rs
crates/biome_js_formatter/src/lib.rs
crates/biome_service/src/file_handlers/html.rs
crates/biome_formatter/src/lib.rs
📚 Learning: 2025-08-11T11:48:52.001Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:52.001Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : Use the generic Format trait and FormatNode for AST nodes when implementing the formatter
Applied to files:
crates/biome_css_formatter/src/lib.rs
crates/biome_html_formatter/src/html/auxiliary/embedded_content.rs
crates/biome_js_formatter/src/lib.rs
crates/biome_formatter/src/lib.rs
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/cst.rs : Define FormatHtmlSyntaxNode in cst.rs and implement FormatRule<HtmlSyntaxNode>, plus AsFormat and IntoFormat for HtmlSyntaxNode using the provided mapping code
Applied to files:
crates/biome_css_formatter/src/lib.rs
crates/biome_html_formatter/src/html/auxiliary/embedded_content.rs
crates/biome_js_formatter/src/lib.rs
crates/biome_service/src/file_handlers/html.rs
crates/biome_formatter/src/lib.rs
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/cst.rs : Provide the FormatNodeRule trait wiring (fmt, fmt_fields, is_suppressed, fmt_leading_comments, fmt_dangling_comments, fmt_trailing_comments) used to start node formatting
Applied to files:
crates/biome_css_formatter/src/lib.rs
crates/biome_html_formatter/src/html/auxiliary/embedded_content.rs
crates/biome_js_formatter/src/lib.rs
crates/biome_formatter/src/lib.rs
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/context.rs : Define HtmlFormatContext in context.rs with fields comments: Rc<HtmlComments> and source_map: Option<TransformSourceMap>, and implement FormatContext and CstFormatContext
Applied to files:
crates/biome_css_formatter/src/lib.rs
crates/biome_html_formatter/src/html/auxiliary/embedded_content.rs
crates/biome_js_formatter/src/lib.rs
crates/biome_service/src/file_handlers/html.rs
crates/biome_formatter/src/lib.rs
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/lib.rs : Place the plumbing traits and impls (AsFormat, IntoFormat, FormattedIterExt, and their Iterator adapters) in the biome_html_formatter crate’s lib.rs
Applied to files:
crates/biome_css_formatter/src/lib.rs
crates/biome_html_formatter/src/html/auxiliary/embedded_content.rs
crates/biome_service/src/file_handlers/html.rs
crates/biome_formatter/src/lib.rs
📚 Learning: 2025-08-17T08:55:30.118Z
Learnt from: CR
PR: biomejs/biome#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-17T08:55:30.118Z
Learning: Applies to crates/biome_html_formatter/benches/html_formatter.rs : Maintain the HTML formatter benchmark at benches/html_formatter.rs within the biome_html_formatter crate
Applied to files:
crates/biome_css_formatter/src/lib.rs
crates/biome_service/src/file_handlers/html.rs
📚 Learning: 2025-08-11T11:48:52.001Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:52.001Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : Build an intermediate IR via the provided helper APIs when formatting
Applied to files:
crates/biome_css_formatter/src/lib.rs
crates/biome_js_formatter/src/lib.rs
crates/biome_formatter/src/lib.rs
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/comments.rs : Define HtmlComments alias and HtmlCommentStyle in comments.rs, and implement CommentStyle for the language
Applied to files:
crates/biome_css_formatter/src/lib.rs
crates/biome_html_formatter/src/html/auxiliary/embedded_content.rs
crates/biome_service/src/file_handlers/html.rs
📚 Learning: 2025-08-11T11:48:52.001Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:52.001Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : Do not attempt to fix code: if a mandatory token/node is missing, return None instead
Applied to files:
crates/biome_css_formatter/src/lib.rs
crates/biome_html_formatter/src/html/auxiliary/embedded_content.rs
crates/biome_js_formatter/src/lib.rs
crates/biome_formatter/src/lib.rs
📚 Learning: 2025-08-11T11:48:52.001Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:52.001Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : For non-mandatory tokens, use the formatter helper functions (e.g., token, space_token) instead of hardcoding
Applied to files:
crates/biome_css_formatter/src/lib.rs
crates/biome_js_formatter/src/lib.rs
📚 Learning: 2025-08-11T11:48:52.001Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:52.001Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : When a token is mandatory and present in the AST, use the AST-provided token (e.g., node.l_paren_token().format()) instead of hardcoded tokens
Applied to files:
crates/biome_css_formatter/src/lib.rs
crates/biome_js_formatter/src/lib.rs
📚 Learning: 2025-09-10T08:05:22.867Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-09-10T08:05:22.867Z
Learning: Applies to crates/biome_analyze/**/src/{lint,assist}/**/*.rs : Code blocks in rule docs must specify language; invalid snippets require `expect_diagnostic`; use `options`/`full_options`/`use_options` markers as appropriate
Applied to files:
crates/biome_css_formatter/src/lib.rs
crates/biome_service/src/file_handlers/html.rs
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/tests/language.rs : Create tests/language.rs defining HtmlTestFormatLanguage and implement TestFormatLanguage for it
Applied to files:
crates/biome_html_formatter/src/html/auxiliary/embedded_content.rs
crates/biome_service/src/file_handlers/html.rs
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/lib.rs : Create the type alias: type HtmlFormatter<'buf> = Formatter<'buf, HtmlFormatContext>
Applied to files:
crates/biome_html_formatter/src/html/auxiliary/embedded_content.rs
📚 Learning: 2025-08-11T11:50:12.090Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:50:12.090Z
Learning: Applies to crates/biome_js_type_info/src/**/*.rs : Represent links between types using TypeReference (not Arc) to avoid cross-module retention and recursive structures; store type data in linear vectors
Applied to files:
crates/biome_html_formatter/src/html/auxiliary/embedded_content.rs
📚 Learning: 2025-08-11T11:46:05.836Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_diagnostics/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:46:05.836Z
Learning: Applies to crates/biome_diagnostics/**/*.rs : Use helper advice types from biome_diagnostics::v2 (CodeFrameAdvice, CommandAdvice, DiffAdvice, LogAdvice) when suitable
Applied to files:
crates/biome_html_formatter/src/html/auxiliary/embedded_content.rs
📚 Learning: 2025-08-11T11:50:12.090Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:50:12.090Z
Learning: Applies to crates/biome_js_type_info/{src,biome_module_graph/src}/**/*.rs : When pattern-matching on ResolvedTypeData via as_raw_data(), ensure any nested TypeReferences are subsequently resolved using the correct ResolverId; never use the raw data with a resolver without applying the right ResolverId to avoid panics
Applied to files:
crates/biome_html_formatter/src/html/auxiliary/embedded_content.rs
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Implement FormatLanguage for HtmlFormatLanguage binding SyntaxLanguage = HtmlLanguage, Context = HtmlFormatContext, and FormatRule = FormatHtmlSyntaxNode
Applied to files:
crates/biome_html_formatter/src/html/auxiliary/embedded_content.rs
📚 Learning: 2025-08-11T11:48:52.001Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:52.001Z
Learning: Applies to crates/biome_js_formatter/**/Cargo.toml : Add biome_js_formatter as a path dependency in Cargo.toml: biome_js_formatter = { version = "0.0.1", path = "../biome_js_formatter" }
Applied to files:
crates/biome_js_formatter/src/lib.rs
📚 Learning: 2025-08-11T11:48:52.001Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:52.001Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : Use dbg_write! to debug and inspect the written IR elements during formatter development
Applied to files:
crates/biome_formatter/src/lib.rs
🧬 Code graph analysis (7)
crates/biome_css_formatter/src/lib.rs (2)
crates/biome_formatter/src/lib.rs (11)
format_node_with_offset
(1547-1616)options
(756-756)options
(818-820)options
(1461-1461)new
(810-812)new
(885-887)new
(997-1009)new
(1234-1236)new
(1284-1286)new
(2047-2055)new
(2174-2176)crates/biome_js_formatter/src/lib.rs (3)
format_node_with_offset
(569-574)options
(523-525)new
(490-492)
crates/biome_html_formatter/src/html/auxiliary/embedded_content.rs (3)
crates/biome_html_formatter/src/html/auxiliary/content.rs (1)
fmt_fields
(9-11)crates/biome_html_formatter/src/lib.rs (2)
fmt_fields
(233-233)embedded_node_range
(228-230)crates/biome_html_formatter/src/verbatim.rs (1)
format_verbatim_skipped
(40-46)
crates/biome_js_formatter/src/lib.rs (2)
crates/biome_css_formatter/src/lib.rs (3)
format_node_with_offset
(386-391)options
(283-285)new
(255-257)crates/biome_formatter/src/lib.rs (11)
format_node_with_offset
(1547-1616)options
(756-756)options
(818-820)options
(1461-1461)new
(810-812)new
(885-887)new
(997-1009)new
(1234-1236)new
(1284-1286)new
(2047-2055)new
(2174-2176)
crates/biome_service/src/workspace/document.rs (1)
crates/biome_service/src/file_handlers/html.rs (2)
node
(469-469)node
(496-496)
crates/biome_service/src/file_handlers/html.rs (6)
crates/biome_service/src/workspace/server.rs (5)
settings
(527-527)settings
(563-563)content
(1965-1965)parse
(581-611)new
(120-142)crates/biome_service/src/settings.rs (1)
resolve_parse_options
(647-652)crates/biome_service/src/file_handlers/css.rs (2)
resolve_parse_options
(150-168)parse
(389-423)crates/biome_service/src/file_handlers/json.rs (2)
resolve_parse_options
(136-163)parse
(381-396)crates/biome_service/src/file_handlers/javascript.rs (3)
resolve_parse_options
(190-207)overrides
(328-331)parse
(520-545)crates/biome_service/src/workspace/document.rs (4)
parse
(64-64)parse
(71-71)node
(63-65)node
(70-72)
crates/biome_formatter/src/lib.rs (4)
crates/biome_css_formatter/src/lib.rs (3)
new
(255-257)format_node_with_offset
(386-391)format_node
(376-381)crates/biome_formatter/src/format_element/document.rs (3)
visit_element
(152-156)new
(23-25)transform_document
(164-166)crates/biome_js_formatter/src/lib.rs (3)
new
(490-492)format_node_with_offset
(569-574)format_node
(559-564)crates/biome_service/src/file_handlers/html.rs (3)
format_embedded
(450-531)node
(469-469)node
(496-496)
crates/biome_formatter/src/format_element/document.rs (3)
crates/biome_formatter/src/lib.rs (9)
new
(810-812)new
(885-887)new
(997-1009)new
(1234-1236)new
(1284-1286)new
(2047-2055)new
(2174-2176)visit_element
(911-925)document
(937-939)crates/biome_formatter/src/buffer.rs (6)
elements
(41-41)elements
(145-147)elements
(230-232)elements
(360-362)elements
(415-417)elements
(664-666)crates/biome_formatter/src/format_element.rs (2)
variants
(316-318)from_vec_unchecked
(298-307)
🔇 Additional comments (20)
crates/biome_html_formatter/src/html/auxiliary/embedded_content.rs (1)
7-9
: Embedded content replacement exists — no double-output expectedConfirmed: crates/biome_formatter/src/lib.rs (visit_element) handles FormatElement::Tag(Tag::StartEmbedded(range)) by calling the fn_format_embedded(range) callback and injecting the formatted document (EndEmbedded handled separately), so format_verbatim_skipped is being used to mark the span for replacement rather than causing the original content to be printed alongside the formatted output.
crates/biome_css_formatter/src/lib.rs (1)
383-392
: Offset formatting entrypoint looks correctDelegation to
biome_formatter::format_node_with_offset
mirrors JS; API surface is consistent.crates/biome_js_formatter/src/lib.rs (1)
566-574
: Offset formatting entrypoint LGTMMatches formatter-core API and existing
format_node
.crates/biome_formatter/src/lib.rs (3)
69-71
: ImportingSyntaxNodeWithOffset
is needed for the new APIAll good.
1021-1023
: Doc tweak reads better; accurate semanticsNice clarity on
Printed::range
.
1544-1616
: New offset-based formatter entrypoint LGTMLogic mirrors
format_node
, adjusted for offset roots. Good reuse and source-map handling.crates/biome_service/src/file_handlers/html.rs (4)
209-226
: LGTM on the resolve_parse_options implementation!The function properly handles HTML parse options with interpolation settings and applies overrides correctly.
364-406
: Clean refactor for CSS embedded content.The function signature improvements with generic type parameters and proper options handling look good. The file source index callback pattern is consistent with JS handling.
450-531
: Robust embedded formatting implementation.The format_embedded function properly handles both JS and CSS embedded content with correct indentation logic based on the indent_script_and_style option. The document swapping approach is clean.
308-330
: ```shell
#!/bin/bash
set -euo pipefailecho "1) Grep for relevant symbols (text_trimmed, is_script_tag, as_html_attribute, as_html_string, value_token)"
rg -n --hidden --no-ignore 'text_trimmed|is_script_tag|as_html_attribute|as_html_string|value_token' || trueecho
echo "2) Show html.rs around the snippet (lines ~280-340)"
sed -n '260,360p' crates/biome_service/src/file_handlers/html.rs || trueecho
echo "3) Search for implementations/definitions of text_trimmed and token helpers"
rg -n --hidden --no-ignore 'fn text_trimmed|impl .*text_trimmed|pub fn text_trimmed|\.text_trimmed\(' || true
rg -n --hidden --no-ignore 'fn as_html_attribute|fn as_html_string|fn value_token|trait AsHtmlAttribute' || true</blockquote></details> <details> <summary>crates/biome_service/src/workspace/document.rs (10)</summary><blockquote> `2-2`: **Import for CssLanguage is correct.** Needed for the corrected CSS node() return type. LGTM. --- `18-25`: **From<JsOffsetParse> → SendEmbeddedParse looks good.** Conversion captures root and diagnostics correctly. LGTM. --- `27-34`: **From<CssOffsetParse> → SendEmbeddedParse looks good.** Consistent with the JS conversion. LGTM. --- `43-44`: **Unified parse type for JS is a nice simplification.** Converging on `SendEmbeddedParse` reduces duplication. LGTM. --- `82-83`: **CSS also uses the unified parse type.** Consistent with JS. LGTM. --- `129-130`: **Confirm rename: `_embedded_scripts` → `embedded_scripts`** Field renamed — confirm there are no stale references to `_embedded_scripts`; rg -n '\b_embedded_scripts\b' returned no matches. Re-run the search including submodules/generated files if present. --- `57-59`: **Verify EmbeddedJsContent initialisers set file_source_index** Sandbox search failed (PCRE2 error / ripgrep skipped files); unable to confirm — run locally: rg -n -C3 --hidden --no-ignore -g '!**/target/**' 'EmbeddedJsContent\s*\{' and ensure every initialiser includes file_source_index. --- `134-135`: **Verify call sites after field rename `_embedded_styles` → `embedded_styles`.** File: crates/biome_service/src/workspace/document.rs (≈ lines 134–135) now declares `pub(crate) embedded_styles: Vec<EmbeddedCssContent>;`. Search for any remaining `_embedded_styles` or `.embedded_styles` references and update them (e.g. run: rg -nuu '\b_embedded_styles\b' && rg -nuu '\.embedded_styles\b'). --- `96-98`: **Ensure `file_source_index` is set for CSS content too.** ripgrep found no occurrences of EmbeddedCssContent in the repo — unable to verify. Manually confirm any CSS‑embedding structs/literals (EmbeddedCssContent or equivalents) initialise `file_source_index`. --- `115-118`: **Double-check all Document initialisers set file_source_index** Automated scan errored; manually verify every `Document { ... }` initialiser includes `file_source_index` — start with crates/biome_service/src/workspace/document.rs:115–118. </blockquote></details> </blockquote></details> </details> <!-- This is an auto-generated comment by CodeRabbit for review status -->
unsafe { | ||
FormatElement::BestFitting(BestFittingElement::from_vec_unchecked( | ||
variants, | ||
)) | ||
} |
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.
🛠️ Refactor suggestion
Add a safety comment for from_vec_unchecked
This unsafe
needs a brief invariant: e.g. variants length ≥ 2 and represent least/most expanded in order; elements remain valid post-visit.
🤖 Prompt for AI Agents
In crates/biome_formatter/src/format_element/document.rs around lines 194–198,
the unsafe call to BestFittingElement::from_vec_unchecked needs an explicit
safety comment: add a short doc-style comment immediately above the unsafe block
stating the invariants required by from_vec_unchecked (e.g. variants.len() >= 2;
the entries are ordered from least to most expanded; each FormatElement in
variants remains valid and unchanged for the lifetime expected by
BestFittingElement after visiting), and optionally add debug_asserts that check
variants.len() >= 2 and a comment that any further ordering/validity checks are
upheld by the caller/producer of variants.
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.
🚀
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
♻️ Duplicate comments (3)
crates/biome_html_parser/src/parser.rs (1)
121-126
: Good addition; this matches the earlier suggestion.Implements the exact setter signature previously requested and the logic is sound (true → Double, false → None).
crates/biome_formatter/src/format_element/document.rs (2)
155-162
: Avoid cloning the whole elements vec during transformUse mem::take to move elements out, transform, and put them back. Zero-copy win.
- pub fn transform_document<V: DocumentVisitor>(document: &mut Document, visitor: &mut V) { - document.elements = Self::transform_elements(document.elements.clone(), visitor); - } + pub fn transform_document<V: DocumentVisitor>(document: &mut Document, visitor: &mut V) { + let elements = std::mem::take(&mut document.elements); + document.elements = Self::transform_elements(elements, visitor); + }
187-193
: Strengthen unsafe invariants doc for from_vec_uncheckedThe current SAFETY note only mentions count preservation. Please spell out all invariants (len ≥ 2; ordering from least to most expanded preserved; variants content remains valid post-visit), and consider a debug_assert on len ≥ 2 here for belt-and-braces.
Example:
// SAFETY: `variants` preserves original variant ordering (least..most expanded), // contains at least two entries, and each transformed variant remains a valid // sequence of FormatElement. Length is unchanged from input. debug_assert!(variants.len() >= 2); unsafe { FormatElement::BestFitting(BestFittingElement::from_vec_unchecked(variants)) }
🧹 Nitpick comments (6)
crates/biome_html_parser/src/parser.rs (2)
121-126
: Document the “false resets to None” semantics.A brief rustdoc will avoid readers assuming “false” switches to Single rather than disabling expressions.
- pub fn set_double_text_expression(&mut self, value: bool) { + /// Toggle parsing of double-quoted text expressions. + /// + /// When `value` is `true`, enables [`TextExpressionKind::Double`]. + /// When `false`, disables text expressions entirely (`None`). + /// Use [`HtmlParseOptions::with_single_text_expression`] to enable single-quoted mode. + pub fn set_double_text_expression(&mut self, value: bool) {
121-126
: Consider a more general setter (optional).If more toggles are added, a single
set_text_expression(Option<TextExpressionKind>)
could reduce API surface while covering this case.+ /// Set the text expression mode explicitly. + pub fn set_text_expression(&mut self, kind: Option<TextExpressionKind>) { + self.text_expression = kind; + }crates/biome_formatter/src/lib.rs (4)
53-56
: Prefer consistent import path for DocumentVisitorMinor consistency nit: you import Document/ElementTransformer from format_element::document but DocumentVisitor from prelude::document. Import all three from the same module to keep things tidy.
Apply:
- use crate::format_element::document::{Document, ElementTransformer}; - use crate::prelude::document::DocumentVisitor; + use crate::format_element::document::{Document, ElementTransformer, DocumentVisitor};
893-934
: Avoid in-place mutate + clone; return a reference or build-and-swap (not both)format_embedded mutates self.document and then returns a clone, forcing callers to swap an already-updated document. Pick one model to avoid extra allocation.
Option A (mutate, return &Document; update callers to drop swap):
- #[must_use] - pub fn format_embedded<F>(&mut self, fn_format_embedded: F) -> Document + #[must_use] + pub fn format_embedded<F>(&mut self, fn_format_embedded: F) -> &Document where F: Fn(TextRange) -> Option<Document>, { let document = &mut self.document; ... - document.clone() + &self.document }And in biome_service/src/file_handlers/html.rs:
- let new_document = formatted.format_embedded(move |range| { /* ... */ }); - formatted.swap_document(new_document); + formatted.format_embedded(move |range| { /* ... */ });Option B (don’t mutate; return a new Document and let callers swap):
- Build a fresh Document and return it; remove the in-place transform.
917-922
: Unconditional hard line after EndEmbedded may over-breakReplacing EndEmbedded with Line(Hard) can double-insert blank lines depending on the embedded formatter’s trailing whitespace. Consider omitting the replacement (return None) and letting the inserted Document control spacing, or make it conditional.
Minimal change:
- FormatElement::Tag(Tag::EndEmbedded) => { - // FIXME: this might not play well for all cases, so we need to figure out - // a nicer way to replace the tag - Some(FormatElement::Line(LineMode::Hard)) - } + FormatElement::Tag(Tag::EndEmbedded) => NonePlease add/confirm tests for:
- Adjacent <script> and <style> blocks.
- Embedded block at EOF and at BOF.
- indent_script_and_style on/off.
1543-1615
: format_node_with_offset duplicates format_node; consider a shared helperThe body largely mirrors format_node. Extract a private helper to reduce drift and ease maintenance.
Sketch:
fn format_node_impl<L: FormatLanguage>( root: SyntaxNode<L::SyntaxLanguage>, language: L, ) -> FormatResult<Formatted<L::Context>> { /* current shared body */ } pub fn format_node<L: FormatLanguage>(root: &SyntaxNode<L::SyntaxLanguage>, language: L) -> ... { format_node_impl(root.clone(), language) } pub fn format_node_with_offset<L: FormatLanguage>( root: &SyntaxNodeWithOffset<L::SyntaxLanguage>, language: L, ) -> ... { // resolve transformed SyntaxNode, then: format_node_impl(resolved_root, language) }
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
crates/biome_css_formatter/src/lib.rs
(2 hunks)crates/biome_formatter/src/format_element/document.rs
(5 hunks)crates/biome_formatter/src/format_element/tag.rs
(5 hunks)crates/biome_formatter/src/lib.rs
(6 hunks)crates/biome_formatter/src/printer/mod.rs
(5 hunks)crates/biome_html_formatter/src/html/auxiliary/element.rs
(2 hunks)crates/biome_html_formatter/src/html/lists/element_list.rs
(1 hunks)crates/biome_html_formatter/src/lib.rs
(2 hunks)crates/biome_html_parser/src/parser.rs
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
- crates/biome_html_formatter/src/html/lists/element_list.rs
- crates/biome_css_formatter/src/lib.rs
- crates/biome_html_formatter/src/html/auxiliary/element.rs
- crates/biome_html_formatter/src/lib.rs
- crates/biome_formatter/src/printer/mod.rs
- crates/biome_formatter/src/format_element/tag.rs
🧰 Additional context used
📓 Path-based instructions (3)
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_parser/src/parser.rs
crates/biome_*/**
📄 CodeRabbit inference engine (CLAUDE.md)
Place core crates under /crates/biome_*/
Files:
crates/biome_html_parser/src/parser.rs
crates/biome_formatter/src/lib.rs
crates/biome_formatter/src/format_element/document.rs
**/*.rs
📄 CodeRabbit inference engine (CONTRIBUTING.md)
Format all Rust source files before committing (just f)
Files:
crates/biome_html_parser/src/parser.rs
crates/biome_formatter/src/lib.rs
crates/biome_formatter/src/format_element/document.rs
🧠 Learnings (10)
📚 Learning: 2025-08-11T11:48:52.001Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:52.001Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : Use the generic Format trait and FormatNode for AST nodes when implementing the formatter
Applied to files:
crates/biome_formatter/src/lib.rs
📚 Learning: 2025-08-11T11:48:52.001Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:52.001Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : Import the FormatNode trait and implement it for your Node
Applied to files:
crates/biome_formatter/src/lib.rs
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/lib.rs : Place the plumbing traits and impls (AsFormat, IntoFormat, FormattedIterExt, and their Iterator adapters) in the biome_html_formatter crate’s lib.rs
Applied to files:
crates/biome_formatter/src/lib.rs
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/lib.rs : Expose a documented public function format_node(options: HtmlFormatOptions, root: &HtmlSyntaxNode) -> FormatResult<Formatted<HtmlFormatContext>> delegating to biome_formatter::format_node
Applied to files:
crates/biome_formatter/src/lib.rs
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/cst.rs : Define FormatHtmlSyntaxNode in cst.rs and implement FormatRule<HtmlSyntaxNode>, plus AsFormat and IntoFormat for HtmlSyntaxNode using the provided mapping code
Applied to files:
crates/biome_formatter/src/lib.rs
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/context.rs : Define HtmlFormatContext in context.rs with fields comments: Rc<HtmlComments> and source_map: Option<TransformSourceMap>, and implement FormatContext and CstFormatContext
Applied to files:
crates/biome_formatter/src/lib.rs
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/cst.rs : Provide the FormatNodeRule trait wiring (fmt, fmt_fields, is_suppressed, fmt_leading_comments, fmt_dangling_comments, fmt_trailing_comments) used to start node formatting
Applied to files:
crates/biome_formatter/src/lib.rs
📚 Learning: 2025-08-11T11:48:52.001Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:52.001Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : Use dbg_write! to debug and inspect the written IR elements during formatter development
Applied to files:
crates/biome_formatter/src/lib.rs
📚 Learning: 2025-08-11T11:48:52.001Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:52.001Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : Build an intermediate IR via the provided helper APIs when formatting
Applied to files:
crates/biome_formatter/src/lib.rs
📚 Learning: 2025-08-11T11:48:52.001Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:52.001Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : Do not attempt to fix code: if a mandatory token/node is missing, return None instead
Applied to files:
crates/biome_formatter/src/lib.rs
🧬 Code graph analysis (2)
crates/biome_formatter/src/lib.rs (5)
crates/biome_formatter/src/format_element/document.rs (9)
fmt
(220-230)fmt
(282-647)fmt
(653-663)fmt
(669-678)visit_element
(152-152)new
(23-25)transform_document
(160-162)source_map
(246-248)from
(206-208)crates/biome_html_formatter/src/lib.rs (5)
fmt
(171-178)fmt
(196-205)fmt
(276-278)new
(139-141)format_node
(30-35)crates/biome_js_formatter/src/lib.rs (5)
fmt
(360-369)fmt
(437-439)new
(490-492)format_node_with_offset
(569-574)format_node
(559-564)crates/biome_service/src/file_handlers/html.rs (5)
format_embedded
(450-531)node
(469-469)node
(496-496)from
(52-56)from
(75-88)crates/biome_css_formatter/src/lib.rs (3)
new
(256-258)format_node_with_offset
(387-392)format_node
(377-382)
crates/biome_formatter/src/format_element/document.rs (2)
crates/biome_formatter/src/lib.rs (9)
new
(809-811)new
(884-886)new
(996-1008)new
(1233-1235)new
(1283-1285)new
(2046-2054)new
(2173-2175)visit_element
(910-924)document
(936-938)crates/biome_formatter/src/format_element.rs (2)
variants
(316-318)from_vec_unchecked
(298-307)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (16)
- GitHub Check: Check Dependencies
- GitHub Check: autofix
- GitHub Check: Bench (biome_html_parser)
- GitHub Check: Bench (biome_graphql_formatter)
- GitHub Check: Bench (biome_module_graph)
- GitHub Check: Bench (biome_js_analyze)
- GitHub Check: Bench (biome_graphql_parser)
- GitHub Check: Bench (biome_css_parser)
- GitHub Check: Bench (biome_json_parser)
- GitHub Check: Bench (biome_html_formatter)
- GitHub Check: Bench (biome_js_formatter)
- GitHub Check: Bench (biome_json_analyze)
- GitHub Check: Bench (biome_js_parser)
- GitHub Check: Bench (biome_css_analyze)
- GitHub Check: Bench (biome_json_formatter)
- GitHub Check: Bench (biome_css_formatter)
🔇 Additional comments (8)
crates/biome_formatter/src/lib.rs (4)
46-46
: LGTM: import surface updatedThe added Tag/TagKind import aligns with embedded tag usage. No concerns.
68-70
: LGTM: offset types importedSyntaxNodeWithOffset/TextRange plumbing looks correct for the new entry-point.
945-948
: swap_document API: fine, but may be redundant post-refactorswap_document is useful, but if you adopt Option A above it won’t be needed by current callers. Keeping it is harmless.
1020-1022
: Docs clarity: niceExplicitly mentioning TextRange and None is clearer. Cheers.
crates/biome_formatter/src/format_element/document.rs (4)
23-26
: LGTM: Document::newStraightforward constructor; matches usage across the crate.
141-148
: LGTM: element accessorsinto_elements/as_elements are handy for interop (e.g. Interned::new).
150-153
: LGTM: DocumentVisitor surfaceSignature matches current usage; simple and effective for element-level transforms.
601-604
: LGTM: StartEmbedded/EndEmbedded printing fixedStartEmbedded is now treated as a start tag and EndEmbedded as an end tag in the IR printer. This unblocks sane debugging output.
Also applies to: 618-621
CodSpeed Performance ReportMerging #7467 will not alter performanceComparing Summary
Footnotes |
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
♻️ Duplicate comments (2)
crates/biome_formatter/src/format_element/document.rs (2)
160-163
: Avoid cloning the entire element vector during transformUse std::mem::take to move elements out, transform, and put them back. Cuts a full clone.
- pub fn transform_document<V: DocumentVisitor>(document: &mut Document, visitor: &mut V) { - document.elements = Self::transform_elements(document.elements.clone(), visitor); - } + pub fn transform_document<V: DocumentVisitor>(document: &mut Document, visitor: &mut V) { + let elements = std::mem::take(&mut document.elements); + document.elements = Self::transform_elements(elements, visitor); + }
187-192
: Add explicit safety invariants for from_vec_uncheckedThe unsafe is fine, but please document the upheld invariants (and keep them true).
- // SAFETY: Safe because the number of variants is the same after the transformation + // SAFETY: + // - The number of variants is unchanged (>= 2). + // - The ordering (least→most expanded) is preserved. + // - Each element remains valid after visiting. unsafe {
🧹 Nitpick comments (2)
crates/biome_formatter/src/lib.rs (2)
53-56
: Unify import path for DocumentVisitorImport it from the same module as Document/ElementTransformer for consistency (and fewer surprises if prelude re-exports change).
-use crate::prelude::document::DocumentVisitor; +use crate::format_element::document::DocumentVisitor;
893-934
: Don’t clone the whole Document; make format_embedded mutate-and-return a reference (also allow FnMut)You already mutate self.document via ElementTransformer; returning a clone then swapping it back is redundant. Returning &Document avoids the extra allocation and lets call-sites drop swap_document. Allowing FnMut gives callers room to cache/look up state.
- #[must_use] - pub fn format_embedded<F>(&mut self, fn_format_embedded: F) -> Document + #[must_use] + pub fn format_embedded<F>(&mut self, fn_format_embedded: F) -> &Document where - F: Fn(TextRange) -> Option<Document>, + F: FnMut(TextRange) -> Option<Document>, { let document = &mut self.document; struct EmbeddedVisitor<F> { fn_format_embedded: F, } - impl<F> DocumentVisitor for EmbeddedVisitor<F> + impl<F> DocumentVisitor for EmbeddedVisitor<F> where - F: Fn(TextRange) -> Option<Document>, + F: FnMut(TextRange) -> Option<Document>, { fn visit_element(&mut self, element: &FormatElement) -> Option<FormatElement> { match element { FormatElement::Tag(Tag::StartEmbedded(range)) => { (self.fn_format_embedded)(*range).map(|document| { FormatElement::Interned(Interned::new(document.into_elements())) }) } FormatElement::Tag(Tag::EndEmbedded) => { // FIXME: this might not play well for all cases, so we need to figure out // a nicer way to replace the tag Some(FormatElement::Line(LineMode::Hard)) } _ => None, } } } ElementTransformer::transform_document( document, &mut EmbeddedVisitor { fn_format_embedded }, ); - document.clone() + &self.document }Follow-up: update call-sites (e.g. html handler) to drop the swap:
- let new_document = formatted.format_embedded(move |range| { /* ... */ }); - formatted.swap_document(new_document); + formatted.format_embedded(move |range| { /* ... */ });
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
crates/biome_css_formatter/src/lib.rs
(2 hunks)crates/biome_formatter/src/format_element/document.rs
(5 hunks)crates/biome_formatter/src/format_element/tag.rs
(5 hunks)crates/biome_formatter/src/lib.rs
(6 hunks)crates/biome_formatter/src/printer/mod.rs
(5 hunks)crates/biome_html_formatter/src/html/auxiliary/element.rs
(2 hunks)crates/biome_html_formatter/src/html/lists/element_list.rs
(1 hunks)crates/biome_html_formatter/src/lib.rs
(2 hunks)crates/biome_html_parser/src/parser.rs
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (7)
- crates/biome_html_formatter/src/lib.rs
- crates/biome_html_formatter/src/html/lists/element_list.rs
- crates/biome_html_parser/src/parser.rs
- crates/biome_css_formatter/src/lib.rs
- crates/biome_formatter/src/format_element/tag.rs
- crates/biome_html_formatter/src/html/auxiliary/element.rs
- crates/biome_formatter/src/printer/mod.rs
🧰 Additional context used
📓 Path-based instructions (2)
crates/biome_*/**
📄 CodeRabbit inference engine (CLAUDE.md)
Place core crates under /crates/biome_*/
Files:
crates/biome_formatter/src/format_element/document.rs
crates/biome_formatter/src/lib.rs
**/*.rs
📄 CodeRabbit inference engine (CONTRIBUTING.md)
Format all Rust source files before committing (just f)
Files:
crates/biome_formatter/src/format_element/document.rs
crates/biome_formatter/src/lib.rs
🧠 Learnings (10)
📚 Learning: 2025-08-11T11:48:52.001Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:52.001Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : Import the FormatNode trait and implement it for your Node
Applied to files:
crates/biome_formatter/src/lib.rs
📚 Learning: 2025-08-11T11:48:52.001Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:52.001Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : Use the generic Format trait and FormatNode for AST nodes when implementing the formatter
Applied to files:
crates/biome_formatter/src/lib.rs
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/lib.rs : Place the plumbing traits and impls (AsFormat, IntoFormat, FormattedIterExt, and their Iterator adapters) in the biome_html_formatter crate’s lib.rs
Applied to files:
crates/biome_formatter/src/lib.rs
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/lib.rs : Expose a documented public function format_node(options: HtmlFormatOptions, root: &HtmlSyntaxNode) -> FormatResult<Formatted<HtmlFormatContext>> delegating to biome_formatter::format_node
Applied to files:
crates/biome_formatter/src/lib.rs
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/cst.rs : Define FormatHtmlSyntaxNode in cst.rs and implement FormatRule<HtmlSyntaxNode>, plus AsFormat and IntoFormat for HtmlSyntaxNode using the provided mapping code
Applied to files:
crates/biome_formatter/src/lib.rs
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/context.rs : Define HtmlFormatContext in context.rs with fields comments: Rc<HtmlComments> and source_map: Option<TransformSourceMap>, and implement FormatContext and CstFormatContext
Applied to files:
crates/biome_formatter/src/lib.rs
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/cst.rs : Provide the FormatNodeRule trait wiring (fmt, fmt_fields, is_suppressed, fmt_leading_comments, fmt_dangling_comments, fmt_trailing_comments) used to start node formatting
Applied to files:
crates/biome_formatter/src/lib.rs
📚 Learning: 2025-08-11T11:48:52.001Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:52.001Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : Use dbg_write! to debug and inspect the written IR elements during formatter development
Applied to files:
crates/biome_formatter/src/lib.rs
📚 Learning: 2025-08-11T11:48:52.001Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:52.001Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : Build an intermediate IR via the provided helper APIs when formatting
Applied to files:
crates/biome_formatter/src/lib.rs
📚 Learning: 2025-08-11T11:48:52.001Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:52.001Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : Do not attempt to fix code: if a mandatory token/node is missing, return None instead
Applied to files:
crates/biome_formatter/src/lib.rs
🧬 Code graph analysis (2)
crates/biome_formatter/src/format_element/document.rs (2)
crates/biome_formatter/src/lib.rs (9)
new
(809-811)new
(884-886)new
(996-1008)new
(1233-1235)new
(1283-1285)new
(2046-2054)new
(2173-2175)visit_element
(910-924)document
(936-938)crates/biome_formatter/src/format_element.rs (2)
variants
(316-318)from_vec_unchecked
(298-307)
crates/biome_formatter/src/lib.rs (2)
crates/biome_formatter/src/format_element/document.rs (8)
fmt
(220-230)fmt
(282-647)fmt
(653-663)fmt
(669-678)visit_element
(152-152)new
(23-25)transform_document
(160-162)from
(206-208)crates/biome_service/src/file_handlers/html.rs (5)
format_embedded
(450-531)node
(469-469)node
(496-496)from
(52-56)from
(75-88)
🪛 GitHub Actions: Pull request
crates/biome_formatter/src/lib.rs
[error] 1553-1553: Cargo lint failed. Clippy error: needlessly taken reference of both operands in crates/biome_formatter/src/lib.rs:1553:16 ('&transformed == &root.node'). Replace with 'transformed == root.node'.
🔇 Additional comments (1)
crates/biome_formatter/src/format_element/document.rs (1)
601-604
: StartEmbedded now prints as a start tag — greatCorrectly treating StartEmbedded as a start and EndEmbedded as an end fixes IR mismatches and makes debugging sane.
Also applies to: 618-621
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
♻️ Duplicate comments (2)
crates/biome_service/src/file_handlers/html.rs (2)
283-290
: Good to see the parse function now usesresolve_parse_options
!The implementation properly delegates to
settings.parse_options::<HtmlLanguage>()
which respects overrides and language-specific settings.
464-464
: Performance concern: Linear search through all embedded nodesFinding nodes by comparing ranges requires iterating through potentially many embedded nodes. As mentioned in previous reviews, using an index would be more efficient.
🧹 Nitpick comments (1)
crates/biome_service/src/file_handlers/html.rs (1)
468-519
: Consider extracting common formatting logicBoth JS and CSS branches contain nearly identical logic for applying indentation. This duplication could be reduced.
Extract the common pattern into a helper function:
+fn format_embedded_with_indent( + formatted: Document, + indent: bool, +) -> Document { + let elements = if indent { + vec![ + FormatElement::Line(LineMode::Hard), + FormatElement::Tag(Tag::StartIndent), + FormatElement::Line(LineMode::Hard), + FormatElement::Interned(Interned::new(formatted.into_elements())), + FormatElement::Tag(Tag::EndIndent), + ] + } else { + vec![ + FormatElement::Line(LineMode::Hard), + FormatElement::Interned(Interned::new(formatted.into_elements())), + ] + }; + Document::new(elements) +} match node.source { DocumentFileSource::Js(_) => { let js_options = settings.format_options::<JsLanguage>(biome_path, &node.source); let node = node.node.clone().root.into_node::<JsLanguage>(); let formatted = biome_js_formatter::format_node_with_offset(js_options, &node).ok()?; - if indent_script_and_style { - let elements = vec![ - FormatElement::Line(LineMode::Hard), - FormatElement::Tag(Tag::StartIndent), - FormatElement::Line(LineMode::Hard), - FormatElement::Interned(Interned::new( - formatted.into_document().into_elements(), - )), - FormatElement::Tag(Tag::EndIndent), - ]; - - Some(Document::new(elements)) - } else { - let elements = vec![ - FormatElement::Line(LineMode::Hard), - FormatElement::Interned(Interned::new( - formatted.into_document().into_elements(), - )), - ]; - Some(Document::new(elements)) - } + Some(format_embedded_with_indent( + formatted.into_document(), + indent_script_and_style, + )) } DocumentFileSource::Css(_) => { let css_options = settings.format_options::<CssLanguage>(biome_path, &node.source); let node = node.node.clone().root.into_node::<CssLanguage>(); let formatted = biome_css_formatter::format_node_with_offset(css_options, &node).ok()?; - if indent_script_and_style { - let elements = vec![ - FormatElement::Line(LineMode::Hard), - FormatElement::Tag(Tag::StartIndent), - FormatElement::Line(LineMode::Hard), - FormatElement::Interned(Interned::new( - formatted.into_document().into_elements(), - )), - FormatElement::Tag(Tag::EndIndent), - ]; - let document = Document::new(elements); - Some(document) - } else { - let elements = vec![ - FormatElement::Line(LineMode::Hard), - FormatElement::Interned(Interned::new( - formatted.into_document().into_elements(), - )), - ]; - Some(Document::new(elements)) - } + Some(format_embedded_with_indent( + formatted.into_document(), + indent_script_and_style, + )) } _ => None, }
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
crates/biome_formatter/src/format_element/document.rs
(5 hunks)crates/biome_formatter/src/lib.rs
(6 hunks)crates/biome_html_parser/src/parser.rs
(1 hunks)crates/biome_service/src/file_handlers/html.rs
(10 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- crates/biome_html_parser/src/parser.rs
- crates/biome_formatter/src/lib.rs
- crates/biome_formatter/src/format_element/document.rs
🧰 Additional context used
📓 Path-based instructions (2)
crates/biome_*/**
📄 CodeRabbit inference engine (CLAUDE.md)
Place core crates under /crates/biome_*/
Files:
crates/biome_service/src/file_handlers/html.rs
**/*.rs
📄 CodeRabbit inference engine (CONTRIBUTING.md)
Format all Rust source files before committing (just f)
Files:
crates/biome_service/src/file_handlers/html.rs
🧠 Learnings (8)
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/cst.rs : Define FormatHtmlSyntaxNode in cst.rs and implement FormatRule<HtmlSyntaxNode>, plus AsFormat and IntoFormat for HtmlSyntaxNode using the provided mapping code
Applied to files:
crates/biome_service/src/file_handlers/html.rs
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/context.rs : Define HtmlFormatContext in context.rs with fields comments: Rc<HtmlComments> and source_map: Option<TransformSourceMap>, and implement FormatContext and CstFormatContext
Applied to files:
crates/biome_service/src/file_handlers/html.rs
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/lib.rs : Expose a documented public function format_node(options: HtmlFormatOptions, root: &HtmlSyntaxNode) -> FormatResult<Formatted<HtmlFormatContext>> delegating to biome_formatter::format_node
Applied to files:
crates/biome_service/src/file_handlers/html.rs
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/tests/language.rs : Create tests/language.rs defining HtmlTestFormatLanguage and implement TestFormatLanguage for it
Applied to files:
crates/biome_service/src/file_handlers/html.rs
📚 Learning: 2025-08-17T08:55:30.118Z
Learnt from: CR
PR: biomejs/biome#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-17T08:55:30.118Z
Learning: Applies to crates/biome_html_formatter/benches/html_formatter.rs : Maintain the HTML formatter benchmark at benches/html_formatter.rs within the biome_html_formatter crate
Applied to files:
crates/biome_service/src/file_handlers/html.rs
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/comments.rs : Define HtmlComments alias and HtmlCommentStyle in comments.rs, and implement CommentStyle for the language
Applied to files:
crates/biome_service/src/file_handlers/html.rs
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/lib.rs : Place the plumbing traits and impls (AsFormat, IntoFormat, FormattedIterExt, and their Iterator adapters) in the biome_html_formatter crate’s lib.rs
Applied to files:
crates/biome_service/src/file_handlers/html.rs
📚 Learning: 2025-08-11T11:48:52.001Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:52.001Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : Import the FormatNode trait and implement it for your Node
Applied to files:
crates/biome_service/src/file_handlers/html.rs
🧬 Code graph analysis (1)
crates/biome_service/src/file_handlers/html.rs (7)
crates/biome_service/src/workspace/server.rs (5)
settings
(547-547)settings
(583-583)content
(2019-2019)parse
(601-631)new
(120-142)crates/biome_service/src/settings.rs (1)
resolve_parse_options
(631-636)crates/biome_formatter/src/lib.rs (15)
options
(755-755)options
(817-819)options
(1457-1457)format_embedded
(895-930)format_node
(1470-1538)range
(1019-1021)format_node_with_offset
(1543-1612)new
(809-811)new
(884-886)new
(993-1005)new
(1230-1232)new
(1280-1282)new
(2043-2051)new
(2170-2172)document
(933-935)crates/biome_css_formatter/src/lib.rs (3)
options
(284-286)format_node
(377-382)format_node_with_offset
(387-392)crates/biome_js_formatter/src/lib.rs (3)
options
(523-525)format_node
(559-564)format_node_with_offset
(569-574)crates/biome_js_parser/src/parse.rs (6)
parse
(173-178)parse
(219-220)parse
(247-250)parse_js_with_offset_and_cache
(382-395)tree
(91-99)tree
(323-325)crates/biome_css_parser/src/lib.rs (3)
parse_css_with_offset_and_cache
(213-230)tree
(107-109)tree
(162-164)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (24)
- GitHub Check: Test (depot-ubuntu-24.04-arm-16)
- GitHub Check: Test (depot-windows-2022-16)
- GitHub Check: Lint project (depot-ubuntu-24.04-arm-16)
- GitHub Check: Check Dependencies
- GitHub Check: Lint project (depot-windows-2022)
- GitHub Check: Documentation
- GitHub Check: End-to-end tests
- GitHub Check: Bench (biome_graphql_parser)
- GitHub Check: Bench (biome_module_graph)
- GitHub Check: Bench (biome_graphql_formatter)
- GitHub Check: Bench (biome_package)
- GitHub Check: Bench (biome_configuration)
- GitHub Check: Bench (biome_json_analyze)
- GitHub Check: Bench (biome_js_analyze)
- GitHub Check: Bench (biome_json_formatter)
- GitHub Check: Bench (biome_html_formatter)
- GitHub Check: Bench (biome_css_formatter)
- GitHub Check: Bench (biome_html_parser)
- GitHub Check: Bench (biome_json_parser)
- GitHub Check: Bench (biome_js_formatter)
- GitHub Check: Bench (biome_css_parser)
- GitHub Check: Bench (biome_css_analyze)
- GitHub Check: autofix
- GitHub Check: Bench (biome_js_parser)
🔇 Additional comments (1)
crates/biome_service/src/file_handlers/html.rs (1)
309-324
: Script type detection looks solidThe logic correctly identifies module scripts by checking for
type="module"
attribute and defaults to script mode otherwise.
@dyc3 there are some regressions. I've seen some reformatting issues around the biome/crates/biome_html_formatter/src/html/lists/element_list.rs Lines 66 to 71 in d89a839
During tests, |
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: 5
🧹 Nitpick comments (14)
crates/biome_html_syntax/Cargo.toml (2)
16-16
: Avoid duplicating feature gating for biome_rowan/serde.You’re enabling
biome_rowan
’sserde
unconditionally and also listing it under theschema
feature. Pick one. Recommend gating viaschema
only.-biome_rowan = { workspace = true, features = ["serde"] } +biome_rowan = { workspace = true }
4-4
: Tweak description to avoid layering leak.Referencing the parser from the syntax crate blurs boundaries. Suggest a neutral phrasing.
-description = "SyntaxKind and common rowan definitions for biome_html_parser" +description = "Syntax kinds and common Rowan definitions for the HTML language"crates/biome_html_formatter/tests/quick_test.rs (1)
34-34
: Exercise both code paths for embedded formattingNice API update. Consider toggling the delegation via a local var so it’s trivial to run both paths during ad‑hoc checks.
- let doc = format_node(options.clone(), &tree.syntax(), false).unwrap(); + let delegate_fmt_embedded_nodes = false; // flip to true to try delegated embedded formatting + let doc = format_node(options.clone(), &tree.syntax(), delegate_fmt_embedded_nodes).unwrap();crates/biome_html_formatter/src/context.rs (1)
374-376
: Rename/docs nit: clarify what “delegate embedded” meansField and methods look good. A tiny doc pass would help future readers (“delegating embedded nodes” = allow script/style to be formatted by their own formatters). No behaviour change.
pub struct HtmlFormatContext { options: HtmlFormatOptions, /// The comments of the nodes and tokens in the document. comments: Rc<HtmlComments>, source_map: Option<TransformSourceMap>, - should_delegate_fmt_embedded_nodes: bool, + /// When true, HTML formatting may delegate embedded content (e.g. <script>/<style>) + /// to the respective language formatter. + should_delegate_fmt_embedded_nodes: bool, } impl HtmlFormatContext { pub fn new(options: HtmlFormatOptions, comments: HtmlComments) -> Self { Self { options, comments: Rc::new(comments), source_map: None, should_delegate_fmt_embedded_nodes: false, } } - pub fn with_fmt_embedded_nodes(mut self) -> Self { + /// Enable delegation of embedded content formatting. + pub fn with_fmt_embedded_nodes(mut self) -> Self { self.should_delegate_fmt_embedded_nodes = true; self } - pub fn should_delegate_fmt_embedded_nodes(&self) -> bool { + /// Returns whether embedded content formatting is delegated. + pub fn should_delegate_fmt_embedded_nodes(&self) -> bool { self.should_delegate_fmt_embedded_nodes }Also applies to: 384-385, 393-396, 398-400
crates/biome_html_syntax/src/element_ext.rs (1)
86-88
: Fill the test stub with real cases.Add assertions for: no type,
type="text/javascript"
,type="module"
, and a non‑JS MIME type. Happy to draft if you like.crates/biome_service/src/file_handlers/html.rs (3)
462-465
: Drop the temp iterator.Slight tidy to avoid an unused mutable:
- let mut iter = embedded_nodes.iter(); - let node = iter.find(|node| node.range == range)?; + let node = embedded_nodes.iter().find(|n| n.range == range)?;
472-493
: Avoid extra blank lines around embedded content.You add a hard line before StartIndent and another hard line immediately after, plus an extra hard line from EndEmbedded replacement. This can yield double spacing. Suggest keeping a single leading hard line and relying on EndEmbedded’s hard line for the trailing break:
- let elements = vec![ - FormatElement::Line(LineMode::Hard), - FormatElement::Tag(Tag::StartIndent), - FormatElement::Line(LineMode::Hard), - FormatElement::Interned(Interned::new( - formatted.into_document().into_elements(), - )), - FormatElement::Tag(Tag::EndIndent), - ]; + let elements = vec![ + FormatElement::Tag(Tag::StartIndent), + FormatElement::Line(LineMode::Hard), + FormatElement::Interned(Interned::new( + formatted.into_document().into_elements(), + )), + FormatElement::Tag(Tag::EndIndent), + ];Apply the same to the CSS branch.
Also applies to: 500-519
471-474
: Don’t silently swallow formatter errors for embedded nodes.
.ok()?
will drop errors and leaveStartEmbedded
unreplaced, risking a broken print. Consider logging and substituting the original text, or accumulating an error and returningWorkspaceError
fromformat_embedded
. I can sketch a small error collector if helpful.Also applies to: 497-499
crates/biome_formatter/src/lib.rs (6)
46-46
: Remove unused importTagKind
.It’s not referenced; drop it to avoid warnings.
- use crate::prelude::{Tag, TagKind}; + use crate::prelude::Tag;
893-931
: Avoid unconditional EndEmbedded hard line; track only when a StartEmbedded was replaced.Today, if the closure returns
None
,EndEmbedded
still becomes a hard line, producing stray newlines. Track whether a start tag was transformed before emitting the trailing line. Also consider re-propagating expand after transformation.- struct EmbeddedVisitor<F> { - fn_format_embedded: F, - } + struct EmbeddedVisitor<F> { + fn_format_embedded: F, + should_replace_end: bool, + } impl<F> DocumentVisitor for EmbeddedVisitor<F> where F: FnMut(TextRange) -> Option<Document>, { fn visit_element(&mut self, element: &FormatElement) -> Option<FormatElement> { match element { FormatElement::Tag(Tag::StartEmbedded(range)) => { - (self.fn_format_embedded)(*range).map(|document| { - FormatElement::Interned(Interned::new(document.into_elements())) - }) + if let Some(document) = (self.fn_format_embedded)(*range) { + self.should_replace_end = true; + Some(FormatElement::Interned(Interned::new( + document.into_elements(), + ))) + } else { + None + } } FormatElement::Tag(Tag::EndEmbedded) => { - // FIXME: this might not play well for all cases, so we need to figure out - // a nicer way to replace the tag - Some(FormatElement::Line(LineMode::Hard)) + if self.should_replace_end { + self.should_replace_end = false; + Some(FormatElement::Line(LineMode::Hard)) + } else { + None + } } _ => None, } } } ElementTransformer::transform_document( document, - &mut EmbeddedVisitor { fn_format_embedded }, + &mut EmbeddedVisitor { fn_format_embedded, should_replace_end: false }, ); + // Re-run expansion propagation to account for injected elements. + document.propagate_expand();Verification: please add a test where the closure returns
None
for an embedded range to ensure no extra blank line is introduced.
1472-1474
: Fix parameter name and wording in docs.The comment mentions
skip_embedded_nodes
and has a stray backtick; the parameter isdelegate_fmt_embedded_nodes
.-/// When `skip_embedded_nodes` is `true`, the content of `<script>` and `<style` is formatted -/// verbatim. +/// When `delegate_fmt_embedded_nodes` is true, the language emits embedded markers for content +/// such as `<script>` and `<style>`; callers should then run a second pass via +/// `Formatted::format_embedded` to format that content. Otherwise, embedded content is treated verbatim.
1479-1481
: Remove needless clone when callingtransform
.Borrow the parameter directly; the clone is redundant.
- let (root, source_map) = match language.transform(&root.clone()) { + let (root, source_map) = match language.transform(root) {
1482-1482
: Drop needless reference in equality.One more ampersand bites the dust; keeps Clippy happy.
- if &transformed == root { + if transformed == root {
1549-1618
: Nit: avoid clone in offset variant too.Mirror the non-offset fix.
- let (root, source_map) = match language.transform(&root.node.clone()) { + let (root, source_map) = match language.transform(&root.node) {
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
Cargo.lock
is excluded by!**/*.lock
and included by**
📒 Files selected for processing (15)
crates/biome_css_formatter/src/lib.rs
(3 hunks)crates/biome_formatter/src/lib.rs
(8 hunks)crates/biome_formatter_test/src/lib.rs
(1 hunks)crates/biome_graphql_formatter/src/lib.rs
(2 hunks)crates/biome_grit_formatter/src/lib.rs
(2 hunks)crates/biome_html_formatter/src/context.rs
(2 hunks)crates/biome_html_formatter/src/html/auxiliary/embedded_content.rs
(1 hunks)crates/biome_html_formatter/src/html/lists/element_list.rs
(1 hunks)crates/biome_html_formatter/src/lib.rs
(4 hunks)crates/biome_html_formatter/tests/quick_test.rs
(1 hunks)crates/biome_html_syntax/Cargo.toml
(1 hunks)crates/biome_html_syntax/src/element_ext.rs
(2 hunks)crates/biome_js_formatter/src/lib.rs
(3 hunks)crates/biome_json_formatter/src/lib.rs
(2 hunks)crates/biome_service/src/file_handlers/html.rs
(12 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- crates/biome_html_formatter/src/html/auxiliary/embedded_content.rs
- crates/biome_html_formatter/src/lib.rs
🧰 Additional context used
📓 Path-based instructions (8)
crates/biome_*/**
📄 CodeRabbit inference engine (CLAUDE.md)
Place core crates under /crates/biome_*/
Files:
crates/biome_formatter_test/src/lib.rs
crates/biome_html_formatter/src/html/lists/element_list.rs
crates/biome_html_formatter/src/context.rs
crates/biome_html_formatter/tests/quick_test.rs
crates/biome_js_formatter/src/lib.rs
crates/biome_graphql_formatter/src/lib.rs
crates/biome_html_syntax/Cargo.toml
crates/biome_css_formatter/src/lib.rs
crates/biome_json_formatter/src/lib.rs
crates/biome_grit_formatter/src/lib.rs
crates/biome_html_syntax/src/element_ext.rs
crates/biome_service/src/file_handlers/html.rs
crates/biome_formatter/src/lib.rs
**/*.rs
📄 CodeRabbit inference engine (CONTRIBUTING.md)
Format all Rust source files before committing (just f)
Files:
crates/biome_formatter_test/src/lib.rs
crates/biome_html_formatter/src/html/lists/element_list.rs
crates/biome_html_formatter/src/context.rs
crates/biome_html_formatter/tests/quick_test.rs
crates/biome_js_formatter/src/lib.rs
crates/biome_graphql_formatter/src/lib.rs
crates/biome_css_formatter/src/lib.rs
crates/biome_json_formatter/src/lib.rs
crates/biome_grit_formatter/src/lib.rs
crates/biome_html_syntax/src/element_ext.rs
crates/biome_service/src/file_handlers/html.rs
crates/biome_formatter/src/lib.rs
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/lists/element_list.rs
crates/biome_html_formatter/src/context.rs
crates/biome_html_formatter/tests/quick_test.rs
crates/biome_js_formatter/src/lib.rs
crates/biome_graphql_formatter/src/lib.rs
crates/biome_html_syntax/Cargo.toml
crates/biome_css_formatter/src/lib.rs
crates/biome_json_formatter/src/lib.rs
crates/biome_grit_formatter/src/lib.rs
crates/biome_html_syntax/src/element_ext.rs
**/tests/**
📄 CodeRabbit inference engine (CLAUDE.md)
Place test files under a tests/ directory in each crate
Files:
crates/biome_html_formatter/tests/quick_test.rs
crates/biome_js_formatter/**/*.rs
📄 CodeRabbit inference engine (crates/biome_js_formatter/CONTRIBUTING.md)
crates/biome_js_formatter/**/*.rs
: Use the generic Format trait and FormatNode for AST nodes when implementing the formatter
Build an intermediate IR via the provided helper APIs when formatting
Import the FormatNode trait and implement it for your Node
When a token is mandatory and present in the AST, use the AST-provided token (e.g., node.l_paren_token().format()) instead of hardcoded tokens
For non-mandatory tokens, use the formatter helper functions (e.g., token, space_token) instead of hardcoding
Do not attempt to fix code: if a mandatory token/node is missing, return None instead
Use dbg_write! to debug and inspect the written IR elements during formatter development
Files:
crates/biome_js_formatter/src/lib.rs
**/*.toml
📄 CodeRabbit inference engine (CONTRIBUTING.md)
Format all TOML files before committing (just f, via taplo-cli)
Files:
crates/biome_html_syntax/Cargo.toml
crates/**/Cargo.toml
📄 CodeRabbit inference engine (CONTRIBUTING.md)
crates/**/Cargo.toml
: In internal crates, reference internal dependencies with workspace = true
Use path dependencies for dev-dependencies in internal crates to avoid requiring published versions
Files:
crates/biome_html_syntax/Cargo.toml
crates/*/Cargo.toml
📄 CodeRabbit inference engine (CONTRIBUTING.md)
Place new crates under the crates/ directory
Files:
crates/biome_html_syntax/Cargo.toml
🧠 Learnings (30)
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/tests/language.rs : Create tests/language.rs defining HtmlTestFormatLanguage and implement TestFormatLanguage for it
Applied to files:
crates/biome_formatter_test/src/lib.rs
crates/biome_html_formatter/tests/quick_test.rs
crates/biome_service/src/file_handlers/html.rs
📚 Learning: 2025-08-11T11:48:52.001Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:52.001Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : Import the FormatNode trait and implement it for your Node
Applied to files:
crates/biome_formatter_test/src/lib.rs
crates/biome_html_formatter/src/context.rs
crates/biome_html_formatter/tests/quick_test.rs
crates/biome_js_formatter/src/lib.rs
crates/biome_graphql_formatter/src/lib.rs
crates/biome_css_formatter/src/lib.rs
crates/biome_json_formatter/src/lib.rs
crates/biome_grit_formatter/src/lib.rs
crates/biome_service/src/file_handlers/html.rs
crates/biome_formatter/src/lib.rs
📚 Learning: 2025-08-11T11:48:52.001Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:52.001Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : Use the generic Format trait and FormatNode for AST nodes when implementing the formatter
Applied to files:
crates/biome_formatter_test/src/lib.rs
crates/biome_html_formatter/src/context.rs
crates/biome_html_formatter/tests/quick_test.rs
crates/biome_js_formatter/src/lib.rs
crates/biome_graphql_formatter/src/lib.rs
crates/biome_css_formatter/src/lib.rs
crates/biome_json_formatter/src/lib.rs
crates/biome_grit_formatter/src/lib.rs
crates/biome_service/src/file_handlers/html.rs
crates/biome_formatter/src/lib.rs
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/lib.rs : Expose a documented public function format_node(options: HtmlFormatOptions, root: &HtmlSyntaxNode) -> FormatResult<Formatted<HtmlFormatContext>> delegating to biome_formatter::format_node
Applied to files:
crates/biome_formatter_test/src/lib.rs
crates/biome_html_formatter/src/html/lists/element_list.rs
crates/biome_html_formatter/src/context.rs
crates/biome_html_formatter/tests/quick_test.rs
crates/biome_js_formatter/src/lib.rs
crates/biome_graphql_formatter/src/lib.rs
crates/biome_css_formatter/src/lib.rs
crates/biome_json_formatter/src/lib.rs
crates/biome_grit_formatter/src/lib.rs
crates/biome_service/src/file_handlers/html.rs
crates/biome_formatter/src/lib.rs
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/cst.rs : Provide the FormatNodeRule trait wiring (fmt, fmt_fields, is_suppressed, fmt_leading_comments, fmt_dangling_comments, fmt_trailing_comments) used to start node formatting
Applied to files:
crates/biome_formatter_test/src/lib.rs
crates/biome_html_formatter/src/html/lists/element_list.rs
crates/biome_html_formatter/src/context.rs
crates/biome_html_formatter/tests/quick_test.rs
crates/biome_js_formatter/src/lib.rs
crates/biome_graphql_formatter/src/lib.rs
crates/biome_css_formatter/src/lib.rs
crates/biome_json_formatter/src/lib.rs
crates/biome_grit_formatter/src/lib.rs
crates/biome_formatter/src/lib.rs
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/tests/spec_test.rs : Create tests/spec_test.rs implementing the run(spec_input_file, _expected_file, test_directory, _file_type) function as shown and include!("language.rs")
Applied to files:
crates/biome_formatter_test/src/lib.rs
crates/biome_html_formatter/tests/quick_test.rs
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/lib.rs : Place the plumbing traits and impls (AsFormat, IntoFormat, FormattedIterExt, and their Iterator adapters) in the biome_html_formatter crate’s lib.rs
Applied to files:
crates/biome_formatter_test/src/lib.rs
crates/biome_css_formatter/src/lib.rs
crates/biome_grit_formatter/src/lib.rs
crates/biome_service/src/file_handlers/html.rs
crates/biome_formatter/src/lib.rs
📚 Learning: 2025-08-17T08:55:30.118Z
Learnt from: CR
PR: biomejs/biome#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-17T08:55:30.118Z
Learning: Applies to crates/biome_html_formatter/benches/html_formatter.rs : Maintain the HTML formatter benchmark at benches/html_formatter.rs within the biome_html_formatter crate
Applied to files:
crates/biome_formatter_test/src/lib.rs
crates/biome_html_formatter/tests/quick_test.rs
crates/biome_css_formatter/src/lib.rs
crates/biome_service/src/file_handlers/html.rs
📚 Learning: 2025-08-17T08:55:30.118Z
Learnt from: CR
PR: biomejs/biome#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-17T08:55:30.118Z
Learning: Applies to crates/biome_*_{syntax,parser,formatter,analyze,factory,semantic}/** : Maintain the per-language crate structure: biome_{lang}_{syntax,parser,formatter,analyze,factory,semantic}
Applied to files:
crates/biome_formatter_test/src/lib.rs
crates/biome_html_syntax/Cargo.toml
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: After scaffolding, replace uses of format_verbatim_node with proper biome_formatter IR to implement real formatting
Applied to files:
crates/biome_formatter_test/src/lib.rs
crates/biome_grit_formatter/src/lib.rs
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/cst.rs : Define FormatHtmlSyntaxNode in cst.rs and implement FormatRule<HtmlSyntaxNode>, plus AsFormat and IntoFormat for HtmlSyntaxNode using the provided mapping code
Applied to files:
crates/biome_formatter_test/src/lib.rs
crates/biome_html_formatter/src/html/lists/element_list.rs
crates/biome_html_formatter/src/context.rs
crates/biome_html_formatter/tests/quick_test.rs
crates/biome_js_formatter/src/lib.rs
crates/biome_graphql_formatter/src/lib.rs
crates/biome_css_formatter/src/lib.rs
crates/biome_json_formatter/src/lib.rs
crates/biome_grit_formatter/src/lib.rs
crates/biome_html_syntax/src/element_ext.rs
crates/biome_service/src/file_handlers/html.rs
crates/biome_formatter/src/lib.rs
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/context.rs : Define HtmlFormatContext in context.rs with fields comments: Rc<HtmlComments> and source_map: Option<TransformSourceMap>, and implement FormatContext and CstFormatContext
Applied to files:
crates/biome_html_formatter/src/context.rs
crates/biome_js_formatter/src/lib.rs
crates/biome_graphql_formatter/src/lib.rs
crates/biome_css_formatter/src/lib.rs
crates/biome_json_formatter/src/lib.rs
crates/biome_grit_formatter/src/lib.rs
crates/biome_service/src/file_handlers/html.rs
crates/biome_formatter/src/lib.rs
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Implement FormatLanguage for HtmlFormatLanguage binding SyntaxLanguage = HtmlLanguage, Context = HtmlFormatContext, and FormatRule = FormatHtmlSyntaxNode
Applied to files:
crates/biome_html_formatter/src/context.rs
📚 Learning: 2025-09-10T08:05:22.867Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-09-10T08:05:22.867Z
Learning: Applies to crates/biome_analyze/crates/biome_js_analyze/tests/quick_test.rs : Quick test: edit `tests/quick_test.rs`, remove or comment `#[ignore]`, set `SOURCE`, and adjust `RuleFilter`
Applied to files:
crates/biome_html_formatter/tests/quick_test.rs
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/tests/spec_tests.rs : Create tests/spec_tests.rs in the biome_html_formatter crate that generates tests via tests_macros::gen_tests! for all HTML files at tests/specs/html/**/*.html
Applied to files:
crates/biome_html_formatter/tests/quick_test.rs
📚 Learning: 2025-08-11T11:48:52.001Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:52.001Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : Build an intermediate IR via the provided helper APIs when formatting
Applied to files:
crates/biome_js_formatter/src/lib.rs
crates/biome_formatter/src/lib.rs
📚 Learning: 2025-08-11T11:48:52.001Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:52.001Z
Learning: Applies to crates/biome_js_formatter/**/Cargo.toml : Add biome_js_formatter as a path dependency in Cargo.toml: biome_js_formatter = { version = "0.0.1", path = "../biome_js_formatter" }
Applied to files:
crates/biome_js_formatter/src/lib.rs
crates/biome_html_syntax/Cargo.toml
📚 Learning: 2025-08-11T11:48:52.001Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:52.001Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : When a token is mandatory and present in the AST, use the AST-provided token (e.g., node.l_paren_token().format()) instead of hardcoded tokens
Applied to files:
crates/biome_js_formatter/src/lib.rs
crates/biome_css_formatter/src/lib.rs
crates/biome_json_formatter/src/lib.rs
📚 Learning: 2025-08-11T11:48:52.001Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:52.001Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : Do not attempt to fix code: if a mandatory token/node is missing, return None instead
Applied to files:
crates/biome_js_formatter/src/lib.rs
crates/biome_css_formatter/src/lib.rs
crates/biome_formatter/src/lib.rs
📚 Learning: 2025-08-11T11:48:52.001Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:52.001Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : For non-mandatory tokens, use the formatter helper functions (e.g., token, space_token) instead of hardcoding
Applied to files:
crates/biome_js_formatter/src/lib.rs
crates/biome_css_formatter/src/lib.rs
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/Cargo.toml : Add the specified dev-dependencies (biome_formatter_test, biome_html_factory, biome_html_parser, biome_parser, biome_service, countme with feature enable, iai 0.1.1, quickcheck, quickcheck_macros, tests_macros) to Cargo.toml under [dev-dependencies]
Applied to files:
crates/biome_html_syntax/Cargo.toml
📚 Learning: 2025-09-07T17:35:00.517Z
Learnt from: CR
PR: biomejs/biome#0
File: CONTRIBUTING.md:0-0
Timestamp: 2025-09-07T17:35:00.517Z
Learning: Applies to crates/**/Cargo.toml : Use path dependencies for dev-dependencies in internal crates to avoid requiring published versions
Applied to files:
crates/biome_html_syntax/Cargo.toml
📚 Learning: 2025-09-07T17:35:00.517Z
Learnt from: CR
PR: biomejs/biome#0
File: CONTRIBUTING.md:0-0
Timestamp: 2025-09-07T17:35:00.517Z
Learning: Applies to Cargo.toml : Define shared workspace dependencies in the root Cargo.toml using the [workspace.dependencies] table
Applied to files:
crates/biome_html_syntax/Cargo.toml
📚 Learning: 2025-09-10T08:05:22.867Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-09-10T08:05:22.867Z
Learning: Applies to crates/biome_analyze/**/src/{lint,assist}/**/*.rs : In declare_lint_rule! macros, set `version: "next"` for new or updated rules
Applied to files:
crates/biome_html_syntax/Cargo.toml
📚 Learning: 2025-09-07T17:35:00.517Z
Learnt from: CR
PR: biomejs/biome#0
File: CONTRIBUTING.md:0-0
Timestamp: 2025-09-07T17:35:00.517Z
Learning: Applies to crates/**/Cargo.toml : In internal crates, reference internal dependencies with workspace = true
Applied to files:
crates/biome_html_syntax/Cargo.toml
📚 Learning: 2025-08-11T11:53:15.299Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_service/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:53:15.299Z
Learning: Applies to crates/biome_service/src/workspace.rs : Implement the Workspace trait in src/workspace.rs
Applied to files:
crates/biome_html_syntax/Cargo.toml
📚 Learning: 2025-08-17T08:57:34.751Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_parser/CONTRIBUTING.md:0-0
Timestamp: 2025-08-17T08:57:34.751Z
Learning: Applies to crates/biome_parser/crates/biome_*_syntax/** : Create a new crate named biome_<language>_syntax under crates/
Applied to files:
crates/biome_html_syntax/Cargo.toml
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/comments.rs : Define HtmlComments alias and HtmlCommentStyle in comments.rs, and implement CommentStyle for the language
Applied to files:
crates/biome_css_formatter/src/lib.rs
crates/biome_html_syntax/src/element_ext.rs
crates/biome_service/src/file_handlers/html.rs
📚 Learning: 2025-09-10T08:05:22.867Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-09-10T08:05:22.867Z
Learning: Applies to crates/biome_analyze/**/src/{lint,assist}/**/*.rs : Code blocks in rule docs must specify language; invalid snippets require `expect_diagnostic`; use `options`/`full_options`/`use_options` markers as appropriate
Applied to files:
crates/biome_css_formatter/src/lib.rs
📚 Learning: 2025-08-11T11:48:52.001Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:52.001Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : Use dbg_write! to debug and inspect the written IR elements during formatter development
Applied to files:
crates/biome_formatter/src/lib.rs
🧬 Code graph analysis (10)
crates/biome_formatter_test/src/lib.rs (3)
crates/biome_css_formatter/src/lib.rs (1)
format_node
(378-383)crates/biome_formatter/src/lib.rs (1)
format_node
(1474-1543)crates/biome_html_formatter/src/lib.rs (1)
format_node
(30-40)
crates/biome_html_formatter/src/html/lists/element_list.rs (1)
crates/biome_html_formatter/src/context.rs (1)
should_delegate_fmt_embedded_nodes
(398-400)
crates/biome_html_formatter/tests/quick_test.rs (2)
crates/biome_formatter/src/lib.rs (4)
format_node
(1474-1543)options
(755-755)options
(817-819)options
(1457-1457)crates/biome_html_formatter/src/lib.rs (2)
format_node
(30-40)options
(154-156)
crates/biome_js_formatter/src/lib.rs (3)
crates/biome_css_formatter/src/lib.rs (4)
format_node
(378-383)new
(256-258)options
(284-286)format_node_with_offset
(388-393)crates/biome_formatter/src/lib.rs (12)
format_node
(1474-1543)new
(809-811)new
(884-886)new
(993-1005)new
(1230-1232)new
(1280-1282)new
(2049-2057)new
(2176-2178)options
(755-755)options
(817-819)options
(1457-1457)format_node_with_offset
(1548-1618)crates/biome_html_formatter/src/lib.rs (3)
format_node
(30-40)new
(144-146)options
(154-156)
crates/biome_graphql_formatter/src/lib.rs (3)
crates/biome_css_formatter/src/lib.rs (3)
format_node
(378-383)new
(256-258)options
(284-286)crates/biome_formatter/src/lib.rs (11)
format_node
(1474-1543)new
(809-811)new
(884-886)new
(993-1005)new
(1230-1232)new
(1280-1282)new
(2049-2057)new
(2176-2178)options
(755-755)options
(817-819)options
(1457-1457)crates/biome_html_formatter/src/lib.rs (3)
format_node
(30-40)new
(144-146)options
(154-156)
crates/biome_css_formatter/src/lib.rs (1)
crates/biome_formatter/src/lib.rs (9)
format_node
(1474-1543)new
(809-811)new
(884-886)new
(993-1005)new
(1230-1232)new
(1280-1282)new
(2049-2057)new
(2176-2178)format_node_with_offset
(1548-1618)
crates/biome_json_formatter/src/lib.rs (8)
crates/biome_css_formatter/src/lib.rs (3)
format_node
(378-383)new
(256-258)options
(284-286)crates/biome_formatter/src/lib.rs (11)
format_node
(1474-1543)new
(809-811)new
(884-886)new
(993-1005)new
(1230-1232)new
(1280-1282)new
(2049-2057)new
(2176-2178)options
(755-755)options
(817-819)options
(1457-1457)crates/biome_formatter_test/src/lib.rs (1)
format_node
(27-33)crates/biome_graphql_formatter/src/lib.rs (3)
format_node
(347-352)new
(295-297)options
(310-312)crates/biome_grit_formatter/src/lib.rs (3)
format_node
(36-41)new
(135-137)options
(164-166)crates/biome_html_formatter/src/lib.rs (3)
format_node
(30-40)new
(144-146)options
(154-156)crates/biome_js_formatter/src/lib.rs (3)
format_node
(560-565)new
(490-492)options
(523-525)crates/biome_html_formatter/src/context.rs (4)
new
(50-54)new
(330-332)new
(379-386)options
(406-408)
crates/biome_grit_formatter/src/lib.rs (2)
crates/biome_formatter/src/lib.rs (11)
format_node
(1474-1543)new
(809-811)new
(884-886)new
(993-1005)new
(1230-1232)new
(1280-1282)new
(2049-2057)new
(2176-2178)options
(755-755)options
(817-819)options
(1457-1457)crates/biome_html_formatter/src/lib.rs (3)
format_node
(30-40)new
(144-146)options
(154-156)
crates/biome_service/src/file_handlers/html.rs (10)
crates/biome_service/src/file_handlers/css.rs (11)
settings
(381-381)settings
(385-385)settings
(389-389)settings
(454-454)settings
(470-470)settings
(488-488)settings
(502-502)settings
(536-536)settings
(600-600)resolve_parse_options
(156-175)parse
(396-437)crates/biome_service/src/settings.rs (3)
check_feature_activity
(1974-1988)check_override_feature_activity
(1998-2006)resolve_parse_options
(631-636)crates/biome_formatter/src/lib.rs (34)
fmt
(126-131)fmt
(195-201)fmt
(284-286)fmt
(290-293)fmt
(297-299)fmt
(364-366)fmt
(370-373)fmt
(377-379)fmt
(411-413)fmt
(419-425)fmt
(455-463)fmt
(473-481)fmt
(560-565)fmt
(606-608)fmt
(612-614)fmt
(646-651)options
(755-755)options
(817-819)options
(1457-1457)format_embedded
(895-930)value
(219-221)value
(321-323)value
(588-590)value
(680-682)format_node
(1474-1543)range
(1019-1021)new
(809-811)new
(884-886)new
(993-1005)new
(1230-1232)new
(1280-1282)new
(2049-2057)new
(2176-2178)document
(933-935)crates/biome_service/src/file_handlers/javascript.rs (3)
resolve_parse_options
(189-206)overrides
(327-330)parse
(523-538)crates/biome_css_formatter/src/lib.rs (2)
options
(284-286)format_node
(378-383)crates/biome_html_formatter/src/lib.rs (2)
options
(154-156)format_node
(30-40)crates/biome_js_formatter/src/lib.rs (2)
options
(523-525)format_node
(560-565)crates/biome_formatter/src/format_element/document.rs (3)
options
(248-250)new
(23-25)into_elements
(141-143)crates/biome_js_parser/src/parse.rs (4)
parse
(173-178)parse
(219-220)parse
(247-250)parse_js_with_offset_and_cache
(382-395)crates/biome_css_parser/src/lib.rs (1)
parse_css_with_offset_and_cache
(213-230)
crates/biome_formatter/src/lib.rs (3)
crates/biome_html_formatter/src/lib.rs (6)
fmt
(182-189)fmt
(207-216)fmt
(287-289)new
(144-146)format_node
(30-40)formatted
(312-321)crates/biome_formatter/src/format_element/document.rs (8)
fmt
(226-236)fmt
(288-653)fmt
(659-669)fmt
(675-684)visit_element
(152-152)new
(23-25)transform_document
(160-163)from
(212-214)crates/biome_service/src/file_handlers/html.rs (5)
format_embedded
(450-529)from
(52-56)from
(75-88)node
(469-469)node
(496-496)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (21)
- GitHub Check: Bench (biome_js_parser)
- GitHub Check: Bench (biome_configuration)
- GitHub Check: Bench (biome_html_formatter)
- GitHub Check: Bench (biome_json_formatter)
- GitHub Check: Bench (biome_package)
- GitHub Check: Bench (biome_json_parser)
- GitHub Check: Bench (biome_css_analyze)
- GitHub Check: Bench (biome_module_graph)
- GitHub Check: Bench (biome_js_analyze)
- GitHub Check: Bench (biome_json_analyze)
- GitHub Check: Bench (biome_graphql_formatter)
- GitHub Check: Bench (biome_html_parser)
- GitHub Check: Bench (biome_graphql_parser)
- GitHub Check: Bench (biome_js_formatter)
- GitHub Check: Check Dependencies
- GitHub Check: Test (depot-ubuntu-24.04-arm-16)
- GitHub Check: Bench (biome_css_parser)
- GitHub Check: Test (depot-windows-2022-16)
- GitHub Check: Documentation
- GitHub Check: Bench (biome_css_formatter)
- GitHub Check: autofix
🔇 Additional comments (21)
crates/biome_html_syntax/Cargo.toml (3)
18-21
: Deps look consistent with workspace setup.
camino
,schemars
(optional), andserde
are wired correctly. Nothing to change.
2-2
: LGTM on workspace metadata.
authors.workspace = true
is aligned with the repo’s Cargo layout.
11-11
: Version bump OK — workspace manifest updated. Top-level Cargo.toml contains biome_html_syntax = { version = "0.5.7", path = "./crates/biome_html_syntax" } (Cargo.toml:134); no external exact-version pins found.crates/biome_service/src/file_handlers/html.rs (1)
283-297
: Nice switch to Settings::parse_options.Good consistency with other handlers and respects overrides.
crates/biome_formatter_test/src/lib.rs (1)
32-33
: API alignment looks good.Updating test harness to pass the delegate flag keeps it in lockstep with the core API.
crates/biome_graphql_formatter/src/lib.rs (2)
318-323
: Create-context signature updated correctly.Accepts the delegate flag and keeps behaviour unchanged. All good.
348-352
: Wrapper forwards the delegate flag.Passing
false
matches existing behaviour for GraphQL.crates/biome_css_formatter/src/lib.rs (3)
292-296
: Create-context parity with core API.Signature updated; behaviour unchanged.
382-383
: CSS format_node wrapper aligned.Three‑arg call looks right.
385-393
: New offset entrypoint: 👍Public
format_node_with_offset
mirrors JS and core; good for embedded use.crates/biome_grit_formatter/src/lib.rs (2)
40-41
: Wrapper updated to 3‑arg API.No behaviour change; good.
168-178
: Create-context accepts delegate flag.Underscored param is fine until you need it.
crates/biome_json_formatter/src/lib.rs (2)
272-276
: Create-context signature match.Correct plumbing; no behaviour change.
349-350
: JSON wrapper aligned with formatter API.Looks good.
crates/biome_js_formatter/src/lib.rs (2)
531-535
: Create-context signature aligned.Delegate flag accepted; behaviour preserved.
564-575
: New JS offset entrypoint is correct.Matches core
format_node_with_offset
usage; good for HTML embedding.crates/biome_formatter/src/lib.rs (5)
53-56
: Visitor/offset plumbing imports look good.Public
DocumentVisitor
,ElementTransformer
,Interned
,LineMode
, andSyntaxNodeWithOffset
wiring is appropriate here.Also applies to: 68-70
942-944
:swap_document
addition is fine.Simple and useful mutator; no concerns.
1017-1019
: Good clarification onPrinted::range
docs.Accurately explains semantics.
1523-1543
: New formatting pipeline informat_node
looks solid.State/buffer flow, propagation, and comment assertions are wired correctly.
1987-1988
: Disabling embedded delegation for range formatting is sensible.Prevents nested second-pass formatting during selections.
delegate_fmt_embedded_nodes: bool, | ||
) -> Self::Context; |
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.
💡 Verification agent
🧩 Analysis chain
Trait signature change: ensure all implementors were updated.
FormatLanguage::create_context
now takes delegate_fmt_embedded_nodes: bool
. Please verify every impl is updated accordingly (build breaks can hide in non-workspace crates).
🏁 Script executed:
#!/bin/bash
# List all FormatLanguage impls and nearby create_context signatures
rg -nP 'impl\s+FormatLanguage\s+for\s+[A-Za-z0-9_:]+' -g 'crates/**/src/**/*.rs' -C3
echo
rg -nP 'fn\s+create_context\s*\(' -g 'crates/**/src/**/*.rs' -C3
Length of output: 6180
Trait signature changed — update all create_context impls
FormatLanguage::create_context now takes delegate_fmt_embedded_nodes: bool; the following impls still use the old signature — update their fn create_context(...) signatures to add the new bool parameter:
- crates/biome_json_formatter/src/lib.rs:268
- crates/biome_js_formatter/src/lib.rs:527
- crates/biome_graphql_formatter/src/lib.rs:314
- crates/biome_html_formatter/src/lib.rs:158
- crates/biome_grit_formatter/src/lib.rs:168
- crates/biome_css_formatter/src/lib.rs:288
Build will fail until these are updated (check non-workspace crates as well).
🤖 Prompt for AI Agents
crates/biome_formatter/src/lib.rs around lines 1464-1465: the
FormatLanguage::create_context trait signature now includes a new bool parameter
delegate_fmt_embedded_nodes; update all impls to match. For each listed file
(crates/biome_json_formatter/src/lib.rs:268,
crates/biome_js_formatter/src/lib.rs:527,
crates/biome_graphql_formatter/src/lib.rs:314,
crates/biome_html_formatter/src/lib.rs:158,
crates/biome_grit_formatter/src/lib.rs:168,
crates/biome_css_formatter/src/lib.rs:288) change the fn create_context
signatures to add the final parameter delegate_fmt_embedded_nodes: bool, update
any use of that parameter inside the impl, and propagate the new argument from
call sites so callers pass the bool through; rebuild to find and fix any
remaining call-site mismatches in non-workspace crates as well.
let should_delegate_fmt_embedded_nodes = f.context().should_delegate_fmt_embedded_nodes(); | ||
// early exit - If it's just a single HtmlEmbeddedContent node as the only child, | ||
// we know the parser will only emit one of these. We can simply call its formatter and be done. | ||
// This is also necessary for how we implement embedded language formatting. | ||
if node.len() == 1 | ||
&& let Some(AnyHtmlElement::AnyHtmlContent(AnyHtmlContent::HtmlEmbeddedContent( | ||
embedded_content, | ||
))) = node.first() | ||
&& should_delegate_fmt_embedded_nodes | ||
{ | ||
return embedded_content.format().fmt(f); | ||
} | ||
|
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.
🛠️ Refactor suggestion
Missing borrowed tag emission around early‑exit path (outputs malformed HTML)
The early return skips printing borrowed_opening_r_angle
and borrowed_closing_tag
, which are normally emitted in fmt_children
. For <script>/<style>…</script>
, this can drop the >
and/or the closing tag prefix, yielding incorrect output.
Apply this to preserve the borrowed tokens when delegating:
if node.is_empty() {
return Ok(());
}
- let should_delegate_fmt_embedded_nodes = f.context().should_delegate_fmt_embedded_nodes();
- // early exit - If it's just a single HtmlEmbeddedContent node as the only child,
- // we know the parser will only emit one of these. We can simply call its formatter and be done.
- // This is also necessary for how we implement embedded language formatting.
- if node.len() == 1
- && let Some(AnyHtmlElement::AnyHtmlContent(AnyHtmlContent::HtmlEmbeddedContent(
- embedded_content,
- ))) = node.first()
- && should_delegate_fmt_embedded_nodes
- {
- return embedded_content.format().fmt(f);
- }
+ let should_delegate_fmt_embedded_nodes = f.context().should_delegate_fmt_embedded_nodes();
+ // early exit - If it's just a single HtmlEmbeddedContent node as the only child,
+ // we know the parser will only emit one of these. We can simply call its formatter and be done.
+ // This is also necessary for how we implement embedded language formatting.
+ if node.len() == 1
+ && let Some(AnyHtmlElement::AnyHtmlContent(AnyHtmlContent::HtmlEmbeddedContent(
+ embedded_content,
+ ))) = node.first()
+ && should_delegate_fmt_embedded_nodes
+ {
+ // Preserve the borrowed '>' and closing tag around the embedded content.
+ let borrowed_opening_r_angle = self
+ .borrowed_tokens
+ .borrowed_opening_r_angle
+ .as_ref()
+ .map(|token| format_with(|f| token.format().fmt(f)))
+ .memoized();
+ let borrowed_closing_tag = self
+ .borrowed_tokens
+ .borrowed_closing_tag
+ .as_ref()
+ .map(|tag| format_with(|f| format_partial_closing_tag(f, tag)))
+ .memoized();
+
+ write!(f, [borrowed_opening_r_angle])?;
+ embedded_content.format().fmt(f)?;
+ write!(f, [borrowed_closing_tag])?;
+ return Ok(());
+ }
As a follow‑up, consider adding a spec test with <script src="" defer>/* js */</script>
to catch regressions here. I can draft one if helpful.
📝 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.
let should_delegate_fmt_embedded_nodes = f.context().should_delegate_fmt_embedded_nodes(); | |
// early exit - If it's just a single HtmlEmbeddedContent node as the only child, | |
// we know the parser will only emit one of these. We can simply call its formatter and be done. | |
// This is also necessary for how we implement embedded language formatting. | |
if node.len() == 1 | |
&& let Some(AnyHtmlElement::AnyHtmlContent(AnyHtmlContent::HtmlEmbeddedContent( | |
embedded_content, | |
))) = node.first() | |
&& should_delegate_fmt_embedded_nodes | |
{ | |
return embedded_content.format().fmt(f); | |
} | |
if node.is_empty() { | |
return Ok(()); | |
} | |
let should_delegate_fmt_embedded_nodes = f.context().should_delegate_fmt_embedded_nodes(); | |
// early exit - If it's just a single HtmlEmbeddedContent node as the only child, | |
// we know the parser will only emit one of these. We can simply call its formatter and be done. | |
// This is also necessary for how we implement embedded language formatting. | |
if node.len() == 1 | |
&& let Some(AnyHtmlElement::AnyHtmlContent(AnyHtmlContent::HtmlEmbeddedContent( | |
embedded_content, | |
))) = node.first() | |
&& should_delegate_fmt_embedded_nodes | |
{ | |
// Preserve the borrowed '>' and closing tag around the embedded content. | |
let borrowed_opening_r_angle = self | |
.borrowed_tokens | |
.borrowed_opening_r_angle | |
.as_ref() | |
.map(|token| format_with(|f| token.format().fmt(f))) | |
.memoized(); | |
let borrowed_closing_tag = self | |
.borrowed_tokens | |
.borrowed_closing_tag | |
.as_ref() | |
.map(|tag| format_with(|f| format_partial_closing_tag(f, tag))) | |
.memoized(); | |
write!(f, [borrowed_opening_r_angle])?; | |
embedded_content.format().fmt(f)?; | |
write!(f, [borrowed_closing_tag])?; | |
return Ok(()); | |
} |
🤖 Prompt for AI Agents
In crates/biome_html_formatter/src/html/lists/element_list.rs around lines 62 to
74, the early-return path that delegates formatting to HtmlEmbeddedContent skips
emission of the borrowed_opening_r_angle and borrowed_closing_tag tokens and
therefore can produce malformed HTML; fix by emitting the borrowed opening
r-angle token before delegating to embedded_content.format() and emitting the
borrowed closing tag token (or the closing tag prefix) after the delegated
formatter completes so the surrounding tag punctuation is preserved; ensure
these borrowed tokens are emitted using the same methods/borrowed token
variables used in fmt_children and keep the early-exit behavior otherwise
unchanged.
[dev-dependencies] | ||
biome_html_parser = { path = "../biome_html_parser" } | ||
|
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.
💡 Verification agent
🧩 Analysis chain
Dev-dep introduces a dependency cycle with biome_html_parser.
biome_html_parser
depends on this crate; adding it here (even as a dev-dep) creates a Cargo cycle and will fail builds/tests. Keep biome_html_syntax
at the bottom of the stack.
- Drop the dev-dep and move any tests that need the parser into
biome_html_parser
(or a separate integration-test crate that depends on both).
Apply:
-[dev-dependencies]
-biome_html_parser = { path = "../biome_html_parser" }
Run to confirm the cycle:
🏁 Script executed:
#!/bin/bash
# Show that parser depends on syntax (cycle if syntax dev-depends on parser)
fd -a Cargo.toml crates/biome_html_parser | xargs -I{} rg -n -C2 $'name\\s*=\\s*"biome_html_parser"|^\\s*biome_html_syntax\\s*=\\s*\\{' {}
Length of output: 402
Remove dev-dependency — cycle confirmed
crates/biome_html_parser/Cargo.toml declares biome_html_syntax = { workspace = true }, so adding biome_html_parser as a dev-dependency in crates/biome_html_syntax creates a Cargo dependency cycle and will break builds/tests. Keep biome_html_syntax at the bottom of the stack.
Action: remove the dev-dep and move tests that need the parser into biome_html_parser or into a separate integration-test crate that depends on both.
Apply:
-[dev-dependencies]
-biome_html_parser = { path = "../biome_html_parser" }
📝 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.
[dev-dependencies] | |
biome_html_parser = { path = "../biome_html_parser" } |
🤖 Prompt for AI Agents
In crates/biome_html_syntax/Cargo.toml around lines 22 to 24, the dev-dependency
entry adding biome_html_parser creates a Cargo dependency cycle because
crates/biome_html_parser already depends on workspace member biome_html_syntax;
remove the biome_html_parser dev-dependency from this file. Move any tests that
require the parser into the biome_html_parser crate (or into a separate
integration-test crate that depends on both) so that biome_html_syntax remains a
leaf in the dependency graph and builds/tests no longer form a cycle.
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/benches/html_formatter.rs (1)
48-49
: Bench opts out of embedded delegation — add a clarifying comment, and benchmark the delegated path elsewherePassing
false
here is sensible for a pure HTML first-pass benchmark; it avoids generating embedded tags that require the service-level second pass. Please add a short inline comment to make this intent obvious. For performance tracking of the full two-pass flow, consider a sibling benchmark inbiome_service
where delegation is actually exercised.- format_node(HtmlFormatOptions::default(), root.syntax(), false) + // Intentionally keep `delegate_fmt_embedded_nodes = false`: + // this bench measures the HTML first pass only (no embedded delegation). + format_node(HtmlFormatOptions::default(), root.syntax(), false)
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
crates/biome_html_formatter/benches/html_formatter.rs
(1 hunks)crates/biome_html_formatter/src/html/auxiliary/embedded_content.rs
(1 hunks)crates/biome_html_syntax/src/element_ext.rs
(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- crates/biome_html_syntax/src/element_ext.rs
- crates/biome_html_formatter/src/html/auxiliary/embedded_content.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/benches/html_formatter.rs
crates/biome_*/**
📄 CodeRabbit inference engine (CLAUDE.md)
Place core crates under /crates/biome_*/
Files:
crates/biome_html_formatter/benches/html_formatter.rs
crates/biome_html_formatter/benches/html_formatter.rs
📄 CodeRabbit inference engine (CLAUDE.md)
Maintain the HTML formatter benchmark at benches/html_formatter.rs within the biome_html_formatter crate
Files:
crates/biome_html_formatter/benches/html_formatter.rs
**/*.rs
📄 CodeRabbit inference engine (CONTRIBUTING.md)
Format all Rust source files before committing (just f)
Files:
crates/biome_html_formatter/benches/html_formatter.rs
🧠 Learnings (9)
📓 Common learnings
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: After scaffolding, replace uses of format_verbatim_node with proper biome_formatter IR to implement real formatting
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/cst.rs : Define FormatHtmlSyntaxNode in cst.rs and implement FormatRule<HtmlSyntaxNode>, plus AsFormat and IntoFormat for HtmlSyntaxNode using the provided mapping code
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:52.001Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : Use the generic Format trait and FormatNode for AST nodes when implementing the formatter
📚 Learning: 2025-08-17T08:55:30.118Z
Learnt from: CR
PR: biomejs/biome#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-17T08:55:30.118Z
Learning: Applies to crates/biome_html_formatter/benches/html_formatter.rs : Maintain the HTML formatter benchmark at benches/html_formatter.rs within the biome_html_formatter crate
Applied to files:
crates/biome_html_formatter/benches/html_formatter.rs
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/lib.rs : Expose a documented public function format_node(options: HtmlFormatOptions, root: &HtmlSyntaxNode) -> FormatResult<Formatted<HtmlFormatContext>> delegating to biome_formatter::format_node
Applied to files:
crates/biome_html_formatter/benches/html_formatter.rs
📚 Learning: 2025-08-11T11:48:52.001Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:52.001Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : Use the generic Format trait and FormatNode for AST nodes when implementing the formatter
Applied to files:
crates/biome_html_formatter/benches/html_formatter.rs
📚 Learning: 2025-08-11T11:48:52.001Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:52.001Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : Import the FormatNode trait and implement it for your Node
Applied to files:
crates/biome_html_formatter/benches/html_formatter.rs
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/cst.rs : Define FormatHtmlSyntaxNode in cst.rs and implement FormatRule<HtmlSyntaxNode>, plus AsFormat and IntoFormat for HtmlSyntaxNode using the provided mapping code
Applied to files:
crates/biome_html_formatter/benches/html_formatter.rs
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/cst.rs : Provide the FormatNodeRule trait wiring (fmt, fmt_fields, is_suppressed, fmt_leading_comments, fmt_dangling_comments, fmt_trailing_comments) used to start node formatting
Applied to files:
crates/biome_html_formatter/benches/html_formatter.rs
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/tests/language.rs : Create tests/language.rs defining HtmlTestFormatLanguage and implement TestFormatLanguage for it
Applied to files:
crates/biome_html_formatter/benches/html_formatter.rs
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/context.rs : Define HtmlFormatContext in context.rs with fields comments: Rc<HtmlComments> and source_map: Option<TransformSourceMap>, and implement FormatContext and CstFormatContext
Applied to files:
crates/biome_html_formatter/benches/html_formatter.rs
🧬 Code graph analysis (1)
crates/biome_html_formatter/benches/html_formatter.rs (1)
crates/biome_html_formatter/src/lib.rs (1)
format_node
(30-40)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (23)
- GitHub Check: Test (depot-ubuntu-24.04-arm-16)
- GitHub Check: Documentation
- GitHub Check: Test (depot-windows-2022-16)
- GitHub Check: Lint project (depot-windows-2022)
- GitHub Check: Lint project (depot-ubuntu-24.04-arm-16)
- GitHub Check: Check Dependencies
- GitHub Check: autofix
- GitHub Check: Bench (biome_json_formatter)
- GitHub Check: Bench (biome_js_formatter)
- GitHub Check: Bench (biome_js_analyze)
- GitHub Check: Bench (biome_html_formatter)
- GitHub Check: Bench (biome_configuration)
- GitHub Check: Bench (biome_module_graph)
- GitHub Check: Bench (biome_graphql_formatter)
- GitHub Check: Bench (biome_package)
- GitHub Check: Bench (biome_html_parser)
- GitHub Check: Bench (biome_graphql_parser)
- GitHub Check: Bench (biome_css_parser)
- GitHub Check: Bench (biome_json_analyze)
- GitHub Check: Bench (biome_css_formatter)
- GitHub Check: Bench (biome_js_parser)
- GitHub Check: Bench (biome_css_analyze)
- GitHub Check: Bench (biome_json_parser)
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: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
crates/biome_html_formatter/src/html/auxiliary/element.rs (1)
126-165
: Unify child formatting through HtmlElementList (keep whitespace/borrowing semantics consistent)Special-casing
children.format()
when delegating bypassesHtmlElementList
options (layout, borrowing, entry tags), which is likely the source of the regressions around<script>/<style>
.Remove the delegation branch and always go through
HtmlElementList
; the embedded child’s own rule will render embedded content when the context flag is on.- // The order here is important. First, we must check if we can delegate the formatting - // of embedded nodes, then we check if we should format them verbatim. - if should_format_embedded_nodes { - write!(f, [children.format()])?; - } else if should_be_verbatim { + // Verbatim wins; otherwise always go through HtmlElementList (it cooperates with delegation). + if should_be_verbatim { write!(f, [&format_html_verbatim_node(children.syntax())])?; } else { let format_children = FormatHtmlElementList::default() .with_options(FormatHtmlElementListOptions { layout: HtmlChildListLayout::BestFitting, is_element_whitespace_sensitive: is_whitespace_sensitive, borrowed_r_angle, borrowed_closing_tag, }) .fmt_children(&children, f)?; match format_children { FormatChildrenResult::ForceMultiline(multiline) => { write!(f, [multiline])?; } FormatChildrenResult::BestFitting { flat_children, expanded_children, } => { let expanded_children = expanded_children.memoized(); write!( f, [ &if_group_breaks(&expanded_children).with_group_id(Some(attr_group_id)), &if_group_fits_on_line(&best_fitting![ format_args![flat_children], format_args![expanded_children], ]) .with_group_id(Some(attr_group_id)), ] )?; } } }
♻️ Duplicate comments (2)
crates/biome_service/src/file_handlers/html.rs (1)
283-291
: Nice: parse() now uses resolve_parse_options pathSwitching to settings.parse_options honours overrides and keeps it consistent with other languages. Good catch.
crates/biome_html_syntax/src/element_ext.rs (1)
88-107
: Handle MIME parameters and add missing JS MIME type
is_javascript_tag
doesn’t strip parameters (e.g.text/javascript; charset=UTF-8
) and missestext/ecmascript
.- let is_type_javascript = type_attribute.is_none_or(|attribute| { + let is_type_javascript = type_attribute.is_none_or(|attribute| { attribute .initializer() .and_then(|initializer| initializer.value().ok()) .and_then(|value| value.as_html_string().cloned()) .and_then(|value| value.value_token().ok()) .is_some_and(|token| { - let text = inner_string_text(&token); - text.eq_ignore_ascii_case("text/javascript") - || text.eq_ignore_ascii_case("application/javascript") - || text.eq_ignore_ascii_case("application/ecmascript") - || text.eq_ignore_ascii_case("module") + let text = inner_string_text(&token); + // normalise: lower-case and strip parameters + let mut v = text.to_ascii_lowercase(); + if let Some((mime, _)) = v.split_once(';') { v = mime.trim().to_string(); } + v == "text/javascript" + || v == "application/javascript" + || v == "text/ecmascript" + || v == "application/ecmascript" + || v == "module" }) });
🧹 Nitpick comments (6)
crates/biome_service/src/file_handlers/html.rs (3)
348-390
: Avoid recomputing file_source_index and clone CSS options per parseMinor efficiency + consistency with JS: compute file_source_index once per <style>, and pass cloned options to avoid accidental moves.
fn parse_embedded_style<F>( @@ - if html_element.is_style_tag().unwrap_or_default() { - Some( + if html_element.is_style_tag().unwrap_or_default() { + let file_source_index = source_index_fn(CssFileSource::css()); + Some( html_element .children() .iter() .filter_map(|child| child.as_any_html_content().cloned()) .filter_map(|child| child.as_html_embedded_content().cloned()) .filter_map(|child| { let content = child.value_token().ok()?; let parse = parse_css_with_offset_and_cache( content.text(), content.text_range().start(), cache, - options, + options.clone(), ); - let file_source_index = source_index_fn(CssFileSource::css()); - Some(EmbeddedCssContent { parse: parse.into(), element_range: child.range(), content_range: content.text_range(), content_offset: content.text_range().start(), file_source_index, }) }) .collect::<Vec<_>>(), )
447-449
: Tidy: drop unnecessary iter bindingInline the find; no need for a mutable iterator binding here.
- let mut iter = embedded_nodes.iter(); - let node = iter.find(|node| node.range == range)?; + let node = embedded_nodes.iter().find(|node| node.range == range)?;
42-42
: Unused importstd::fmt::Debug isn’t used; safe to drop.
-use std::fmt::Debug;
crates/biome_html_formatter/src/html/auxiliary/element.rs (1)
17-21
: Update the doc comment to reflect embedded formatting supportThe comment still claims script/style aren’t supported; that’s outdated with the new delegation path.
Apply:
-/// `pre` tags are "preformatted", so we should not format the content inside them. <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/pre> -/// We ignore the `script` and `style` tags as well, since embedded language parsing/formatting is not yet implemented. +/// `pre` is preformatted (don’t touch its content). <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/pre> +/// `script`/`style` are formatted via embedded-language delegation when enabled; otherwise they’re treated verbatim.crates/biome_html_syntax/src/element_ext.rs (2)
24-53
: Deduplicate attribute lookup across element typesThe two
find_attribute_by_name
impls are almost identical. A small helper that scans an attribute list would reduce duplication and drift.Happy to draft a helper that takes an iterator over attributes and performs a case-insensitive match.
Also applies to: 55-68, 70-86
143-207
: Broaden tests: parameters, case-insensitivity, and module detectionAdd coverage for
text/ecmascript
, parameterised MIME, andis_javascript_module
behaviour.@@ fn test_is_javascript_tag() { @@ assert!(element.is_javascript_tag().unwrap()); @@ assert!(element.is_javascript_tag().unwrap()); @@ assert!(element.is_javascript_tag().unwrap()); @@ assert!(element.is_javascript_tag().unwrap()); + + // text/ecmascript + let html = r#"<script type="text/ecmascript"></script>"#; + let syntax = parse_html(html, HtmlParseOptions::default()); + let element = syntax.tree().syntax().descendants().find_map(HtmlElement::cast).unwrap(); + assert!(element.is_javascript_tag().unwrap()); + + // parameters and case-insensitivity + let html = r#"<script TYPE="text/javascript; charset=UTF-8"></script>"#; + let syntax = parse_html(html, HtmlParseOptions::default()); + let element = syntax.tree().syntax().descendants().find_map(HtmlElement::cast).unwrap(); + assert!(element.is_javascript_tag().unwrap()); } + + #[test] + fn test_is_javascript_module() { + // module + let html = r#"<script type="module"></script>"#; + let syntax = parse_html(html, HtmlParseOptions::default()); + let element = syntax.tree().syntax().descendants().find_map(HtmlElement::cast).unwrap(); + assert!(element.is_javascript_module().unwrap()); + + // no type => NOT module + let html = r#"<script></script>"#; + let syntax = parse_html(html, HtmlParseOptions::default()); + let element = syntax.tree().syntax().descendants().find_map(HtmlElement::cast).unwrap(); + assert!(!element.is_javascript_module().unwrap()); + }
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (5)
Cargo.lock
is excluded by!**/*.lock
and included by**
crates/biome_html_formatter/tests/specs/html/elements/pre-with-brackets.html.snap
is excluded by!**/*.snap
and included by**
crates/biome_html_formatter/tests/specs/html/elements/pre-with-braille.html.snap
is excluded by!**/*.snap
and included by**
crates/biome_html_formatter/tests/specs/html/elements/pre.html.snap
is excluded by!**/*.snap
and included by**
crates/biome_html_formatter/tests/specs/html/embedded/strict-whitespace-sensitivity/sensitive-style.html.snap
is excluded by!**/*.snap
and included by**
📒 Files selected for processing (7)
crates/biome_html_formatter/src/html/auxiliary/element.rs
(3 hunks)crates/biome_html_formatter/src/html/auxiliary/embedded_content.rs
(1 hunks)crates/biome_html_formatter/src/html/lists/element_list.rs
(2 hunks)crates/biome_html_syntax/Cargo.toml
(1 hunks)crates/biome_html_syntax/src/element_ext.rs
(2 hunks)crates/biome_service/src/file_handlers/html.rs
(12 hunks)crates/biome_service/src/workspace/server.tests.rs
(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
- crates/biome_html_formatter/src/html/lists/element_list.rs
- crates/biome_html_formatter/src/html/auxiliary/embedded_content.rs
- crates/biome_html_syntax/Cargo.toml
- crates/biome_service/src/workspace/server.tests.rs
🧰 Additional context used
📓 Path-based instructions (3)
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_syntax/src/element_ext.rs
crates/biome_html_formatter/src/html/auxiliary/element.rs
crates/biome_*/**
📄 CodeRabbit inference engine (CLAUDE.md)
Place core crates under /crates/biome_*/
Files:
crates/biome_html_syntax/src/element_ext.rs
crates/biome_html_formatter/src/html/auxiliary/element.rs
crates/biome_service/src/file_handlers/html.rs
**/*.rs
📄 CodeRabbit inference engine (CONTRIBUTING.md)
Format all Rust source files before committing (just f)
Files:
crates/biome_html_syntax/src/element_ext.rs
crates/biome_html_formatter/src/html/auxiliary/element.rs
crates/biome_service/src/file_handlers/html.rs
🧠 Learnings (10)
📓 Common learnings
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/cst.rs : Define FormatHtmlSyntaxNode in cst.rs and implement FormatRule<HtmlSyntaxNode>, plus AsFormat and IntoFormat for HtmlSyntaxNode using the provided mapping code
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/context.rs : Define HtmlFormatContext in context.rs with fields comments: Rc<HtmlComments> and source_map: Option<TransformSourceMap>, and implement FormatContext and CstFormatContext
Learnt from: CR
PR: biomejs/biome#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-17T08:55:30.118Z
Learning: Applies to crates/biome_html_formatter/benches/html_formatter.rs : Maintain the HTML formatter benchmark at benches/html_formatter.rs within the biome_html_formatter crate
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/tests/language.rs : Create tests/language.rs defining HtmlTestFormatLanguage and implement TestFormatLanguage for it
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/lib.rs : Place the plumbing traits and impls (AsFormat, IntoFormat, FormattedIterExt, and their Iterator adapters) in the biome_html_formatter crate’s lib.rs
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:52.001Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : Use the generic Format trait and FormatNode for AST nodes when implementing the formatter
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:52.001Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : Import the FormatNode trait and implement it for your Node
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/comments.rs : Define HtmlComments alias and HtmlCommentStyle in comments.rs, and implement CommentStyle for the language
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:52.001Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : Build an intermediate IR via the provided helper APIs when formatting
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/lib.rs : Expose a documented public function format_node(options: HtmlFormatOptions, root: &HtmlSyntaxNode) -> FormatResult<Formatted<HtmlFormatContext>> delegating to biome_formatter::format_node
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/cst.rs : Define FormatHtmlSyntaxNode in cst.rs and implement FormatRule<HtmlSyntaxNode>, plus AsFormat and IntoFormat for HtmlSyntaxNode using the provided mapping code
Applied to files:
crates/biome_html_syntax/src/element_ext.rs
crates/biome_html_formatter/src/html/auxiliary/element.rs
crates/biome_service/src/file_handlers/html.rs
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/comments.rs : Define HtmlComments alias and HtmlCommentStyle in comments.rs, and implement CommentStyle for the language
Applied to files:
crates/biome_html_syntax/src/element_ext.rs
crates/biome_service/src/file_handlers/html.rs
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/lib.rs : Expose a documented public function format_node(options: HtmlFormatOptions, root: &HtmlSyntaxNode) -> FormatResult<Formatted<HtmlFormatContext>> delegating to biome_formatter::format_node
Applied to files:
crates/biome_html_syntax/src/element_ext.rs
crates/biome_html_formatter/src/html/auxiliary/element.rs
crates/biome_service/src/file_handlers/html.rs
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/context.rs : Define HtmlFormatContext in context.rs with fields comments: Rc<HtmlComments> and source_map: Option<TransformSourceMap>, and implement FormatContext and CstFormatContext
Applied to files:
crates/biome_service/src/file_handlers/html.rs
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/tests/language.rs : Create tests/language.rs defining HtmlTestFormatLanguage and implement TestFormatLanguage for it
Applied to files:
crates/biome_service/src/file_handlers/html.rs
📚 Learning: 2025-08-17T08:55:30.118Z
Learnt from: CR
PR: biomejs/biome#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-17T08:55:30.118Z
Learning: Applies to crates/biome_html_formatter/benches/html_formatter.rs : Maintain the HTML formatter benchmark at benches/html_formatter.rs within the biome_html_formatter crate
Applied to files:
crates/biome_service/src/file_handlers/html.rs
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/lib.rs : Place the plumbing traits and impls (AsFormat, IntoFormat, FormattedIterExt, and their Iterator adapters) in the biome_html_formatter crate’s lib.rs
Applied to files:
crates/biome_service/src/file_handlers/html.rs
📚 Learning: 2025-08-11T11:48:52.001Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:52.001Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : Import the FormatNode trait and implement it for your Node
Applied to files:
crates/biome_service/src/file_handlers/html.rs
📚 Learning: 2025-08-11T11:48:52.001Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:52.001Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : Use the generic Format trait and FormatNode for AST nodes when implementing the formatter
Applied to files:
crates/biome_service/src/file_handlers/html.rs
🧬 Code graph analysis (3)
crates/biome_html_syntax/src/element_ext.rs (2)
crates/biome_js_syntax/src/expr_ext.rs (4)
inner_string_text
(597-599)inner_string_text
(2180-2182)value
(2605-2620)text
(1849-1854)crates/biome_html_parser/src/lib.rs (1)
parse_html
(37-40)
crates/biome_html_formatter/src/html/auxiliary/element.rs (2)
crates/biome_html_formatter/src/html/lists/element_list.rs (2)
write
(717-753)write
(863-877)crates/biome_html_formatter/src/verbatim.rs (1)
format_html_verbatim_node
(28-36)
crates/biome_service/src/file_handlers/html.rs (7)
crates/biome_service/src/file_handlers/css.rs (11)
settings
(381-381)settings
(385-385)settings
(389-389)settings
(454-454)settings
(470-470)settings
(488-488)settings
(502-502)settings
(536-536)settings
(600-600)resolve_parse_options
(156-175)parse
(396-437)crates/biome_service/src/settings.rs (1)
resolve_parse_options
(631-636)crates/biome_formatter/src/lib.rs (31)
fmt
(126-131)fmt
(195-201)fmt
(284-286)fmt
(290-293)fmt
(297-299)fmt
(364-366)fmt
(370-373)fmt
(377-379)fmt
(411-413)fmt
(419-425)fmt
(455-463)fmt
(473-481)fmt
(560-565)fmt
(606-608)fmt
(612-614)fmt
(646-651)options
(755-755)options
(817-819)options
(1457-1457)format_embedded
(895-930)format_node
(1474-1543)range
(1019-1021)format_node_with_offset
(1548-1618)new
(809-811)new
(884-886)new
(993-1005)new
(1230-1232)new
(1280-1282)new
(2049-2057)new
(2176-2178)document
(933-935)crates/biome_service/src/file_handlers/javascript.rs (3)
resolve_parse_options
(189-206)overrides
(327-330)parse
(523-538)crates/biome_html_formatter/src/lib.rs (2)
options
(154-156)format_node
(30-40)crates/biome_js_parser/src/parse.rs (4)
parse
(173-178)parse
(219-220)parse
(247-250)parse_js_with_offset_and_cache
(382-395)crates/biome_css_parser/src/lib.rs (1)
parse_css_with_offset_and_cache
(213-230)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (21)
- GitHub Check: Test (depot-windows-2022-16)
- GitHub Check: Documentation
- GitHub Check: Test (depot-ubuntu-24.04-arm-16)
- GitHub Check: Bench (biome_html_parser)
- GitHub Check: Check Dependencies
- GitHub Check: Bench (biome_module_graph)
- GitHub Check: Bench (biome_configuration)
- GitHub Check: Bench (biome_package)
- GitHub Check: Bench (biome_graphql_formatter)
- GitHub Check: Bench (biome_html_formatter)
- GitHub Check: Bench (biome_json_formatter)
- GitHub Check: Bench (biome_json_analyze)
- GitHub Check: Bench (biome_js_analyze)
- GitHub Check: Bench (biome_css_formatter)
- GitHub Check: Bench (biome_css_parser)
- GitHub Check: Bench (biome_css_analyze)
- GitHub Check: Bench (biome_js_formatter)
- GitHub Check: Bench (biome_graphql_parser)
- GitHub Check: Bench (biome_json_parser)
- GitHub Check: Bench (biome_js_parser)
- GitHub Check: autofix
🔇 Additional comments (3)
crates/biome_service/src/file_handlers/html.rs (2)
541-546
: Should fix_all also delegate embedded formatting?Currently fix_all formats with delegate_fmt_embedded_nodes = false. If users expect the same output as “format”, consider enabling delegation here too.
- false, + true,
307-316
: Module detection correctnessAssuming HtmlElement::is_javascript_module handles ASCII case-insensitive attribute/value matching for type="module". Please confirm to avoid regressions with e.g. TYPE="MODULE".
crates/biome_html_syntax/src/element_ext.rs (1)
1-4
: No action required —inner_string_text
is present in this crate.
Foundpub fn inner_string_text
in crates/biome_html_syntax/src/lib.rs (around line 116). All good — import is valid.
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
♻️ Duplicate comments (3)
crates/biome_html_formatter/src/html/auxiliary/element.rs (3)
51-57
: Delegate via node semantics, not a string listHard-coding
["script","style"]
risks delegating the wrong things (e.g. non‑JS<script type="application/ld+json">
) and drifting from parser semantics. Use the element helpers instead.- let should_format_embedded_nodes = f.context().should_delegate_fmt_embedded_nodes() - && EMBEDDED_NODES.iter().any(|tag| { - tag_name - .as_ref() - .is_some_and(|tag_name| tag_name.text().eq_ignore_ascii_case(tag)) - }); + let should_format_embedded_nodes = if f.context().should_delegate_fmt_embedded_nodes() { + // Only delegate for recognised embedded content + node.is_javascript_tag()? || node.is_style_tag()? + } else { + false + };
96-104
: Don’t borrow ‘>’/closing tag when delegatingIf we borrow while taking the delegation path, nothing prints the borrowed tokens (we bypass
HtmlElementList
), so>
/closing tags can disappear. Guard the borrow flags.- let should_borrow_opening_r_angle = is_whitespace_sensitive + let should_borrow_opening_r_angle = is_whitespace_sensitive && !children.is_empty() && !content_has_leading_whitespace - && !should_be_verbatim; + && !should_be_verbatim + && !should_format_embedded_nodes; - let should_borrow_closing_tag = is_whitespace_sensitive + let should_borrow_closing_tag = is_whitespace_sensitive && !children.is_empty() && !content_has_trailing_whitespace - && !should_be_verbatim; + && !should_be_verbatim + && !should_format_embedded_nodes;
22-24
: Remove the EMBEDDED_NODES constant to avoid driftOnce using semantic helpers, this constant is redundant and can get out of sync.
-/// script and style should be ignored when there's no delegation of formatting of embedded nodes. -const EMBEDDED_NODES: &[&str] = &["script", "style"]; +
🧹 Nitpick comments (1)
crates/biome_html_formatter/src/html/auxiliary/element.rs (1)
17-21
: Doc comment is outdatedWe now support embedded formatting; adjust wording so it doesn’t claim script/style aren’t implemented.
-/// `pre` tags are "preformatted", so we should not format the content inside them. <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/pre> -/// We ignore the `script` and `style` tags as well, since embedded language parsing/formatting is not yet implemented. -/// +/// `pre` tags are "preformatted", so we should not format the content inside them. <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/pre> +/// `script` and `style` are treated as embedded languages when delegation is enabled; otherwise their content is kept verbatim.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (3)
Cargo.lock
is excluded by!**/*.lock
and included by**
crates/biome_cli/tests/snapshots/main_cases_html/should_format_indent_embedded_languages.snap
is excluded by!**/*.snap
and included by**
crates/biome_cli/tests/snapshots/main_cases_html/should_format_indent_embedded_languages_with_language_options.snap
is excluded by!**/*.snap
and included by**
📒 Files selected for processing (3)
crates/biome_formatter/Cargo.toml
(0 hunks)crates/biome_html_formatter/src/html/auxiliary/element.rs
(3 hunks)crates/biome_html_formatter/src/html/auxiliary/embedded_content.rs
(1 hunks)
💤 Files with no reviewable changes (1)
- crates/biome_formatter/Cargo.toml
🚧 Files skipped from review as they are similar to previous changes (1)
- crates/biome_html_formatter/src/html/auxiliary/embedded_content.rs
🧰 Additional context used
📓 Path-based instructions (3)
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/element.rs
crates/biome_*/**
📄 CodeRabbit inference engine (CLAUDE.md)
Place core crates under /crates/biome_*/
Files:
crates/biome_html_formatter/src/html/auxiliary/element.rs
**/*.rs
📄 CodeRabbit inference engine (CONTRIBUTING.md)
Format all Rust source files before committing (just f)
Files:
crates/biome_html_formatter/src/html/auxiliary/element.rs
🧠 Learnings (9)
📓 Common learnings
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/cst.rs : Define FormatHtmlSyntaxNode in cst.rs and implement FormatRule<HtmlSyntaxNode>, plus AsFormat and IntoFormat for HtmlSyntaxNode using the provided mapping code
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/context.rs : Define HtmlFormatContext in context.rs with fields comments: Rc<HtmlComments> and source_map: Option<TransformSourceMap>, and implement FormatContext and CstFormatContext
Learnt from: CR
PR: biomejs/biome#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-17T08:55:30.118Z
Learning: Applies to crates/biome_html_formatter/benches/html_formatter.rs : Maintain the HTML formatter benchmark at benches/html_formatter.rs within the biome_html_formatter crate
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:52.001Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : Use the generic Format trait and FormatNode for AST nodes when implementing the formatter
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/tests/language.rs : Create tests/language.rs defining HtmlTestFormatLanguage and implement TestFormatLanguage for it
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/cst.rs : Define FormatHtmlSyntaxNode in cst.rs and implement FormatRule<HtmlSyntaxNode>, plus AsFormat and IntoFormat for HtmlSyntaxNode using the provided mapping code
Applied to files:
crates/biome_html_formatter/src/html/auxiliary/element.rs
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/comments.rs : Define HtmlComments alias and HtmlCommentStyle in comments.rs, and implement CommentStyle for the language
Applied to files:
crates/biome_html_formatter/src/html/auxiliary/element.rs
📚 Learning: 2025-08-17T08:55:30.118Z
Learnt from: CR
PR: biomejs/biome#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-17T08:55:30.118Z
Learning: Applies to crates/biome_html_formatter/benches/html_formatter.rs : Maintain the HTML formatter benchmark at benches/html_formatter.rs within the biome_html_formatter crate
Applied to files:
crates/biome_html_formatter/src/html/auxiliary/element.rs
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/context.rs : Define HtmlFormatContext in context.rs with fields comments: Rc<HtmlComments> and source_map: Option<TransformSourceMap>, and implement FormatContext and CstFormatContext
Applied to files:
crates/biome_html_formatter/src/html/auxiliary/element.rs
📚 Learning: 2025-08-11T11:48:52.001Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:52.001Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : When a token is mandatory and present in the AST, use the AST-provided token (e.g., node.l_paren_token().format()) instead of hardcoded tokens
Applied to files:
crates/biome_html_formatter/src/html/auxiliary/element.rs
📚 Learning: 2025-08-11T11:48:52.001Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:52.001Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : Use dbg_write! to debug and inspect the written IR elements during formatter development
Applied to files:
crates/biome_html_formatter/src/html/auxiliary/element.rs
📚 Learning: 2025-08-11T11:48:52.001Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:52.001Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : Do not attempt to fix code: if a mandatory token/node is missing, return None instead
Applied to files:
crates/biome_html_formatter/src/html/auxiliary/element.rs
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/lib.rs : Expose a documented public function format_node(options: HtmlFormatOptions, root: &HtmlSyntaxNode) -> FormatResult<Formatted<HtmlFormatContext>> delegating to biome_formatter::format_node
Applied to files:
crates/biome_html_formatter/src/html/auxiliary/element.rs
🧬 Code graph analysis (1)
crates/biome_html_formatter/src/html/auxiliary/element.rs (2)
crates/biome_html_formatter/src/html/lists/element_list.rs (2)
write
(717-753)write
(863-877)crates/biome_html_formatter/src/verbatim.rs (1)
format_html_verbatim_node
(28-36)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (23)
- GitHub Check: Bench (biome_json_parser)
- GitHub Check: Test (depot-windows-2022-16)
- GitHub Check: Test (depot-ubuntu-24.04-arm-16)
- GitHub Check: Documentation
- GitHub Check: autofix
- GitHub Check: Bench (biome_json_formatter)
- GitHub Check: Bench (biome_css_parser)
- GitHub Check: Bench (biome_configuration)
- GitHub Check: Bench (biome_package)
- GitHub Check: Lint project (depot-windows-2022)
- GitHub Check: Lint project (depot-ubuntu-24.04-arm-16)
- GitHub Check: Bench (biome_css_analyze)
- GitHub Check: Bench (biome_js_formatter)
- GitHub Check: Bench (biome_module_graph)
- GitHub Check: Bench (biome_js_parser)
- GitHub Check: Check Dependencies
- GitHub Check: Bench (biome_graphql_formatter)
- GitHub Check: Bench (biome_html_parser)
- GitHub Check: Bench (biome_html_formatter)
- GitHub Check: Bench (biome_js_analyze)
- GitHub Check: Bench (biome_css_formatter)
- GitHub Check: Bench (biome_json_analyze)
- GitHub Check: Bench (biome_graphql_parser)
🔇 Additional comments (1)
crates/biome_html_formatter/src/html/auxiliary/element.rs (1)
125-131
: Add spec for delegated <script>/<style> & verify service-level flag
- Add a spec that ensures delegated <script>/<style> prints the opening '>' and the closing tag.
- Confirm the context flag is enabled along the service path (service uses formatted.format_embedded). Quick pointers: crates/biome_html_formatter/src/html/lists/element_list.rs:63; crates/biome_html_formatter/src/html/auxiliary/embedded_content.rs:16; crates/biome_html_formatter/src/html/auxiliary/element.rs:51; crates/biome_service/src/file_handlers/html.rs:444–448; crates/biome_service/src/workspace/server.rs:1586–1590.
- Optional: funnel both paths through HtmlElementList (borrowed_* = None) to keep break/indent coupling consistent.
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
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
crates/biome_html_syntax/src/element_ext.rs (1)
17-21
: Void-element check is case-sensitive (incorrect for<BR>
etc.)Tag names are ASCII case-insensitive;
<BR>
would return false. Lowercase before searching.- let name = self.name()?; - Ok(VOID_ELEMENTS - .binary_search(&name.value_token()?.text_trimmed()) - .is_ok()) + let name = self.name()?; + let lower = name.value_token()?.text_trimmed().to_ascii_lowercase(); + Ok(VOID_ELEMENTS.binary_search(&lower.as_str()).is_ok())
♻️ Duplicate comments (2)
crates/biome_html_formatter/src/html/auxiliary/element.rs (1)
93-103
: Fixed: no borrowing when delegating (prevents token loss)Gating the borrow flags with
!should_format_embedded_nodes
resolves the missing>
/closing tag issue during delegation.crates/biome_html_syntax/src/element_ext.rs (1)
94-114
: Script type parsing ignores parameters and missestext/ecmascript
type="text/javascript; charset=utf-8"
won’t match;text/ecmascript
also isn’t accepted. Split off parameters and include the missing MIME.- let is_type_javascript = type_attribute.is_none_or(|attribute| { + let is_type_javascript = type_attribute.is_none_or(|attribute| { attribute .initializer() .and_then(|initializer| initializer.value().ok()) .and_then(|value| value.as_html_string().cloned()) .and_then(|value| value.value_token().ok()) .is_some_and(|token| { - let text = inner_string_text(&token); - text.eq_ignore_ascii_case("text/javascript") - || text.eq_ignore_ascii_case("application/javascript") - || text.eq_ignore_ascii_case("application/ecmascript") - || text.eq_ignore_ascii_case("module") + let raw = inner_string_text(&token); + let mime = raw.split(';').next().unwrap_or(""); + mime.eq_ignore_ascii_case("text/javascript") + || mime.eq_ignore_ascii_case("application/javascript") + || mime.eq_ignore_ascii_case("text/ecmascript") + || mime.eq_ignore_ascii_case("application/ecmascript") + || mime.eq_ignore_ascii_case("module") }) });
🧹 Nitpick comments (5)
crates/biome_html_formatter/src/html/auxiliary/element.rs (3)
17-21
: Outdated doc: embedded formatting now existsThe comment still says script/style aren’t implemented. Please update to reflect that they’re verbatim only when delegation is disabled.
-/// We ignore the `script` and `style` tags as well, since embedded language parsing/formatting is not yet implemented. +/// `script` and `style` are formatted verbatim only when embedded formatting is disabled; otherwise we delegate to the embedded formatters.
124-131
: Ensure clean newline and attribute-break coupling for embedded contentWhen delegating, the children aren’t tied to the opening tag’s group. If attributes wrap, embed content can still start on the same line. Consider adding a conditional hard break linked to the attribute group.
- if should_format_embedded_nodes { - write!(f, [children.format()])?; + if should_format_embedded_nodes { + // Keep embedded content on a new line when attributes break, + // and avoid double newlines if the content already starts with whitespace. + if !content_has_leading_whitespace { + write!(f, [if_group_breaks(&hard_line_break()).with_group_id(Some(attr_group_id))])?; + } + write!(f, [children.format()])?;
48-53
: Style type handling: consider guarding non‑CSS<style>
We currently delegate for any
<style>
regardless oftype
. Iftype
is non‑CSS (e.g.text/less
), the CSS parser will choke. Optional: gate delegation to absenttype
ortext/css
, fall back to verbatim otherwise.Would you like a patch adding
is_css_style_tag()
mirroring the script logic?crates/biome_html_syntax/src/element_ext.rs (2)
73-92
: De-duplicate attribute lookup with a small helper/traitBoth element types implement identical
find_attribute_by_name
. Consider a shared helper or a small trait to DRY it up.
149-213
: Add tests: default type, params, and case-insensitivityCover no-
type
, param case, and upper-case tags to lock in the fixes.@@ mod tests { @@ fn test_is_javascript_tag() { @@ } + + #[test] + fn test_is_javascript_tag_defaults_and_params() { + // No type => classic JS + let html = r#"<script>let a=1;</script>"#; + let syntax = parse_html(html, HtmlParseOptions::default()); + let element = syntax.tree().syntax().descendants().find_map(HtmlElement::cast).unwrap(); + assert!(element.is_javascript_tag().unwrap()); + + // Params on MIME type + let html = r#"<script type="text/javascript; charset=utf-8">let a=1;</script>"#; + let syntax = parse_html(html, HtmlParseOptions::default()); + let element = syntax.tree().syntax().descendants().find_map(HtmlElement::cast).unwrap(); + assert!(element.is_javascript_tag().unwrap()); + + // Upper-case tag + module + let html = r#"<SCRIPT TYPE="module">export {};</SCRIPT>"#; + let syntax = parse_html(html, HtmlParseOptions::default()); + let element = syntax.tree().syntax().descendants().find_map(HtmlElement::cast).unwrap(); + assert!(element.is_javascript_tag().unwrap()); + }
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
crates/biome_html_formatter/src/html/auxiliary/element.rs
(4 hunks)crates/biome_html_syntax/src/element_ext.rs
(2 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
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_syntax/src/element_ext.rs
crates/biome_html_formatter/src/html/auxiliary/element.rs
crates/biome_*/**
📄 CodeRabbit inference engine (CLAUDE.md)
Place core crates under /crates/biome_*/
Files:
crates/biome_html_syntax/src/element_ext.rs
crates/biome_html_formatter/src/html/auxiliary/element.rs
**/*.rs
📄 CodeRabbit inference engine (CONTRIBUTING.md)
Format all Rust source files before committing (just f)
Files:
crates/biome_html_syntax/src/element_ext.rs
crates/biome_html_formatter/src/html/auxiliary/element.rs
🧠 Learnings (9)
📓 Common learnings
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/context.rs : Define HtmlFormatContext in context.rs with fields comments: Rc<HtmlComments> and source_map: Option<TransformSourceMap>, and implement FormatContext and CstFormatContext
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/cst.rs : Define FormatHtmlSyntaxNode in cst.rs and implement FormatRule<HtmlSyntaxNode>, plus AsFormat and IntoFormat for HtmlSyntaxNode using the provided mapping code
Learnt from: CR
PR: biomejs/biome#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-17T08:55:30.118Z
Learning: Applies to crates/biome_html_formatter/benches/html_formatter.rs : Maintain the HTML formatter benchmark at benches/html_formatter.rs within the biome_html_formatter crate
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:52.001Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : Use the generic Format trait and FormatNode for AST nodes when implementing the formatter
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/tests/language.rs : Create tests/language.rs defining HtmlTestFormatLanguage and implement TestFormatLanguage for it
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/cst.rs : Define FormatHtmlSyntaxNode in cst.rs and implement FormatRule<HtmlSyntaxNode>, plus AsFormat and IntoFormat for HtmlSyntaxNode using the provided mapping code
Applied to files:
crates/biome_html_syntax/src/element_ext.rs
crates/biome_html_formatter/src/html/auxiliary/element.rs
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/comments.rs : Define HtmlComments alias and HtmlCommentStyle in comments.rs, and implement CommentStyle for the language
Applied to files:
crates/biome_html_syntax/src/element_ext.rs
crates/biome_html_formatter/src/html/auxiliary/element.rs
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/lib.rs : Expose a documented public function format_node(options: HtmlFormatOptions, root: &HtmlSyntaxNode) -> FormatResult<Formatted<HtmlFormatContext>> delegating to biome_formatter::format_node
Applied to files:
crates/biome_html_syntax/src/element_ext.rs
crates/biome_html_formatter/src/html/auxiliary/element.rs
📚 Learning: 2025-08-17T08:55:30.118Z
Learnt from: CR
PR: biomejs/biome#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-17T08:55:30.118Z
Learning: Applies to crates/biome_html_formatter/benches/html_formatter.rs : Maintain the HTML formatter benchmark at benches/html_formatter.rs within the biome_html_formatter crate
Applied to files:
crates/biome_html_formatter/src/html/auxiliary/element.rs
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/context.rs : Define HtmlFormatContext in context.rs with fields comments: Rc<HtmlComments> and source_map: Option<TransformSourceMap>, and implement FormatContext and CstFormatContext
Applied to files:
crates/biome_html_formatter/src/html/auxiliary/element.rs
📚 Learning: 2025-08-11T11:48:52.001Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:52.001Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : When a token is mandatory and present in the AST, use the AST-provided token (e.g., node.l_paren_token().format()) instead of hardcoded tokens
Applied to files:
crates/biome_html_formatter/src/html/auxiliary/element.rs
📚 Learning: 2025-08-11T11:48:52.001Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:52.001Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : Use dbg_write! to debug and inspect the written IR elements during formatter development
Applied to files:
crates/biome_html_formatter/src/html/auxiliary/element.rs
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/cst.rs : Provide the FormatNodeRule trait wiring (fmt, fmt_fields, is_suppressed, fmt_leading_comments, fmt_dangling_comments, fmt_trailing_comments) used to start node formatting
Applied to files:
crates/biome_html_formatter/src/html/auxiliary/element.rs
🧬 Code graph analysis (1)
crates/biome_html_formatter/src/html/auxiliary/element.rs (5)
crates/biome_service/src/file_handlers/html.rs (3)
node
(453-453)node
(480-480)format
(417-432)crates/biome_service/src/file_handlers/javascript.rs (1)
format
(983-1000)crates/biome_service/src/file_handlers/svelte.rs (1)
format
(140-163)crates/biome_service/src/file_handlers/vue.rs (1)
format
(140-163)crates/biome_html_formatter/src/verbatim.rs (1)
format_html_verbatim_node
(28-36)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (23)
- GitHub Check: Documentation
- GitHub Check: Test (depot-ubuntu-24.04-arm-16)
- GitHub Check: Test (depot-windows-2022-16)
- GitHub Check: Check Dependencies
- GitHub Check: Lint project (depot-ubuntu-24.04-arm-16)
- GitHub Check: Lint project (depot-windows-2022)
- GitHub Check: autofix
- GitHub Check: Bench (biome_package)
- GitHub Check: Bench (biome_html_parser)
- GitHub Check: Bench (biome_graphql_formatter)
- GitHub Check: Bench (biome_configuration)
- GitHub Check: Bench (biome_module_graph)
- GitHub Check: Bench (biome_html_formatter)
- GitHub Check: Bench (biome_json_analyze)
- GitHub Check: Bench (biome_js_parser)
- GitHub Check: Bench (biome_graphql_parser)
- GitHub Check: Bench (biome_json_parser)
- GitHub Check: Bench (biome_css_parser)
- GitHub Check: Bench (biome_json_formatter)
- GitHub Check: Bench (biome_css_formatter)
- GitHub Check: Bench (biome_css_analyze)
- GitHub Check: Bench (biome_js_formatter)
- GitHub Check: Bench (biome_js_analyze)
🔇 Additional comments (3)
crates/biome_html_formatter/src/html/auxiliary/element.rs (1)
48-53
: Good: semantic detection for delegationUsing
is_javascript_tag()
/is_style_tag()
under the delegation flag is the right call. Keeps policy in one place.crates/biome_html_syntax/src/element_ext.rs (2)
116-132
: LGTM: module detection is now explicitUsing
is_some_and("module")
avoids the previous “missing type ⇒ module” bug.
55-71
: Attribute lookup is sensibly case-insensitiveNice touch on ASCII case-insensitive attribute names.
Summary
Part of #6657
This PR establishes the infrastructure for embedded formatting and implements it for
.html
files.The infrastructure brings my ideas of how we should do it, so feel free to discuss it.
Formatter infrastructure
The infrastructure was designed to avoid any sort of cyclic dependencies. This is an important part of how I designed it. Because of this, the formatting is done inside
biome_service
, which is the crate that imports allbiome_*_formatter
crates.The formatting of embedded code is in two passes. The first pass is the usual formatting that we always implement in all languages; the second pass is done via a new function called
format_embedded
. This function accepts a closure that provides theTextRange
of where the embedded node is found (we will get to thatTextRange
in a bit). The caller will use thatTextRange
to retrieve the CST root, and then use its own formatter (JS and CSS in case of the implementation). Eventually, the closure must return a formatterDocument
.The function
format_embedded
is called insidebiome_formatter
, which now tracks the embedded nodes during the first formatting pass. The node is tracked using the newTag:StartEmbedded(TextRange)
. TheTextRange
is precisely the range that I mentioned in the previous paragraph. This range is collected using a new function calledembedded_node_range
which is implemented in eachFormatNode
of the language. In this PR, we implement the function inside the elementHtmlEmbeddedContent
. The range we save must match the range of CST we save in theWorkspace
. That's how we track the embedded nodes.During the second pass, we replace
Tag:StartEmbedded(TextRange)
with the format elements returned by the closure mentioned in the first paragraph. The elementTag::EndEmbedded
is currently replaced with a hard line. I left a note there, so in the future, we can use a better heuristic and choose a node that fits the situation.The replacement of the
FormatElement
s is done using a new visitor infrastructure. The code of the visitor was suggested using an AI agent. I suggested a solution that allows replacing a vector element with nested infrastructure.Parsing of embedded nodes
The parsing of embedded nodes is now done inside the
Workspace
because we must retrieve the parsing options of the current project.ServiceLanguage
andWorkspace
The
ServiceLanguage
has been updated with a new capability calledformat_embedded
. I chose to add a new capability because it needs to accept a list of embedded nodes saved inside the Workspace. This allows us to switch from one formatting to another depending on the presence of some embedded nodes.The
ServiceLanguage
now expose a new function calledparse_options
. Up until now the resolution of these options was done only inside theparse
function. Now the scope become larger, so I needed a common infrastructure to retrieve those options. When we parse embedded nodes, we use the parsing options defined in the configuration.Test Plan
I added some tests. For some reason, the CSS formatting is a bit funky. I will tackle that in the next PR.
Docs
I believe it doesn't require docs for now