Skip to content

Commit 5c441aa

Browse files
committed
feat(format/html): implement suppression comments
1 parent 8227773 commit 5c441aa

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+1104
-1105
lines changed

crates/biome_formatter/src/trivia.rs

+2
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,8 @@ impl<L: Language> FormatSkippedTokenTrivia<'_, L> {
530530
where
531531
Context: CstFormatContext<Language = L>,
532532
{
533+
dbg!("FormatSkippedTokenTrivia");
534+
533535
// Lines/spaces before the next token/comment
534536
let (mut lines, mut spaces) = match self.token.prev_token() {
535537
Some(token) => {

crates/biome_html_factory/src/generated/node_factory.rs

-14
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/biome_html_factory/src/generated/syntax_factory.rs

-33
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/biome_html_formatter/src/comments.rs

+20-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use biome_formatter::{
88
prelude::*,
99
write,
1010
};
11-
use biome_html_syntax::HtmlLanguage;
11+
use biome_html_syntax::{HtmlLanguage, HtmlSyntaxKind};
1212
use biome_rowan::{SyntaxTriviaPieceComments, TextLen};
1313
use biome_suppression::parse_suppression_comment;
1414

@@ -96,10 +96,29 @@ impl CommentStyle for HtmlCommentStyle {
9696
CommentKind::Block
9797
}
9898

99+
/// This allows us to override which comments are associated with which nodes.
100+
///
101+
/// While every comment is directly attached to a **syntax token**, Biome actually builds a map of comments to **syntax nodes** separately. This map lives in [`HtmlComments`]. This is so that we can easily look up comments that are associated with a specific node. It's part of how suppression comments are handled.
102+
///
103+
/// This method specifically, however, lets us fine tune which comments are associated with which nodes. This is useful when the default heuristic fails.
99104
fn place_comment(
100105
&self,
101106
comment: DecoratedComment<Self::Language>,
102107
) -> CommentPlacement<Self::Language> {
108+
// Fix trailing comments that are right before EOF being assigned to the wrong node.
109+
//
110+
// The issue is demonstrated in the example below.
111+
// ```html
112+
// Foo
113+
//
114+
// <!-- This comment gets assigned to the text node, despite it being actually attached to the EOF token. -->
115+
// ```
116+
if let Some(token) = comment.following_token() {
117+
if token.kind() == HtmlSyntaxKind::EOF {
118+
return CommentPlacement::trailing(comment.enclosing_node().clone(), comment);
119+
}
120+
}
121+
103122
CommentPlacement::Default(comment)
104123
}
105124
}

crates/biome_html_formatter/src/generated.rs

-38
Original file line numberDiff line numberDiff line change
@@ -189,44 +189,6 @@ impl IntoFormat<HtmlFormatContext> for biome_html_syntax::HtmlClosingElement {
189189
)
190190
}
191191
}
192-
impl FormatRule<biome_html_syntax::HtmlComment>
193-
for crate::html::auxiliary::comment::FormatHtmlComment
194-
{
195-
type Context = HtmlFormatContext;
196-
#[inline(always)]
197-
fn fmt(
198-
&self,
199-
node: &biome_html_syntax::HtmlComment,
200-
f: &mut HtmlFormatter,
201-
) -> FormatResult<()> {
202-
FormatNodeRule::<biome_html_syntax::HtmlComment>::fmt(self, node, f)
203-
}
204-
}
205-
impl AsFormat<HtmlFormatContext> for biome_html_syntax::HtmlComment {
206-
type Format<'a> = FormatRefWithRule<
207-
'a,
208-
biome_html_syntax::HtmlComment,
209-
crate::html::auxiliary::comment::FormatHtmlComment,
210-
>;
211-
fn format(&self) -> Self::Format<'_> {
212-
FormatRefWithRule::new(
213-
self,
214-
crate::html::auxiliary::comment::FormatHtmlComment::default(),
215-
)
216-
}
217-
}
218-
impl IntoFormat<HtmlFormatContext> for biome_html_syntax::HtmlComment {
219-
type Format = FormatOwnedWithRule<
220-
biome_html_syntax::HtmlComment,
221-
crate::html::auxiliary::comment::FormatHtmlComment,
222-
>;
223-
fn into_format(self) -> Self::Format {
224-
FormatOwnedWithRule::new(
225-
self,
226-
crate::html::auxiliary::comment::FormatHtmlComment::default(),
227-
)
228-
}
229-
}
230192
impl FormatRule<biome_html_syntax::HtmlContent>
231193
for crate::html::auxiliary::content::FormatHtmlContent
232194
{

crates/biome_html_formatter/src/html/any/element.rs

-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ impl FormatRule<AnyHtmlElement> for FormatAnyHtmlElement {
1010
match node {
1111
AnyHtmlElement::HtmlBogusElement(node) => node.format().fmt(f),
1212
AnyHtmlElement::HtmlCdataSection(node) => node.format().fmt(f),
13-
AnyHtmlElement::HtmlComment(node) => node.format().fmt(f),
1413
AnyHtmlElement::HtmlContent(node) => node.format().fmt(f),
1514
AnyHtmlElement::HtmlElement(node) => node.format().fmt(f),
1615
AnyHtmlElement::HtmlSelfClosingElement(node) => node.format().fmt(f),

crates/biome_html_formatter/src/html/auxiliary/comment.rs

-10
This file was deleted.

crates/biome_html_formatter/src/html/auxiliary/mod.rs

-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ pub(crate) mod attribute_initializer_clause;
55
pub(crate) mod attribute_name;
66
pub(crate) mod cdata_section;
77
pub(crate) mod closing_element;
8-
pub(crate) mod comment;
98
pub(crate) mod content;
109
pub(crate) mod directive;
1110
pub(crate) mod element;
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,26 @@
11
use crate::prelude::*;
22
use biome_formatter::write;
3-
use biome_html_syntax::HtmlRoot;
3+
use biome_html_syntax::{HtmlRoot, HtmlRootFields};
44
#[derive(Debug, Clone, Default)]
55
pub(crate) struct FormatHtmlRoot;
66
impl FormatNodeRule<HtmlRoot> for FormatHtmlRoot {
77
fn fmt_fields(&self, node: &HtmlRoot, f: &mut HtmlFormatter) -> FormatResult<()> {
8-
if let Some(bom) = node.bom_token() {
9-
bom.format().fmt(f)?;
10-
}
11-
if let Some(directive) = node.directive() {
12-
directive.format().fmt(f)?;
13-
}
8+
let HtmlRootFields {
9+
bom_token,
10+
directive,
11+
html,
12+
eof_token,
13+
} = node.as_fields();
1414

15-
node.html().format().fmt(f)?;
16-
17-
if let Ok(eof) = node.eof_token() {
18-
eof.format().fmt(f)?;
19-
}
20-
write!(f, [hard_line_break()])?;
21-
22-
Ok(())
15+
write!(
16+
f,
17+
[
18+
bom_token.format(),
19+
directive.format(),
20+
html.format(),
21+
hard_line_break(),
22+
format_removed(&eof_token?),
23+
]
24+
)
2325
}
2426
}

crates/biome_html_formatter/src/html/lists/element_list.rs

+38-35
Original file line numberDiff line numberDiff line change
@@ -62,15 +62,17 @@ impl FormatRule<HtmlElementList> for FormatHtmlElementList {
6262
let result = self.fmt_children(node, f)?;
6363
match result {
6464
FormatChildrenResult::ForceMultiline(format_multiline) => {
65-
write!(f, [format_multiline])
65+
write!(f, [format_multiline])?;
6666
}
6767
FormatChildrenResult::BestFitting {
6868
flat_children,
6969
expanded_children,
7070
} => {
71-
write!(f, [best_fitting![flat_children, expanded_children]])
71+
write!(f, [best_fitting![flat_children, expanded_children]])?;
7272
}
7373
}
74+
75+
Ok(())
7476
}
7577
}
7678

@@ -113,8 +115,6 @@ impl FormatHtmlElementList {
113115
list: &HtmlElementList,
114116
f: &mut HtmlFormatter,
115117
) -> FormatResult<FormatChildrenResult> {
116-
self.disarm_debug_assertions(list, f);
117-
118118
let borrowed_opening_r_angle = self
119119
.borrowed_tokens
120120
.borrowed_opening_r_angle
@@ -147,7 +147,7 @@ impl FormatHtmlElementList {
147147

148148
let mut force_multiline = layout.is_multiline();
149149

150-
let mut children = html_split_children(list.iter(), f.context().comments())?;
150+
let mut children = html_split_children(list.iter(), f)?;
151151

152152
// Trim trailing new lines
153153
if let Some(HtmlChild::EmptyLine | HtmlChild::Newline) = children.last() {
@@ -181,6 +181,11 @@ impl FormatHtmlElementList {
181181
Some(WordSeparator::BetweenWords)
182182
}
183183

184+
Some(HtmlChild::Comment(_)) => {
185+
// FIXME: probably not correct behavior here
186+
Some(WordSeparator::Lines(0))
187+
}
188+
184189
// Last word or last word before an element without any whitespace in between
185190
Some(HtmlChild::NonText(next_child)) => Some(WordSeparator::EndOfText {
186191
is_soft_line_break: !matches!(
@@ -191,6 +196,8 @@ impl FormatHtmlElementList {
191196
is_element_whitespace_sensitive_from_element(f, next_child),
192197
}),
193198

199+
Some(HtmlChild::Verbatim(_)) => None,
200+
194201
Some(HtmlChild::Newline | HtmlChild::Whitespace | HtmlChild::EmptyLine) => {
195202
None
196203
}
@@ -210,6 +217,20 @@ impl FormatHtmlElementList {
210217
}
211218
}
212219

220+
HtmlChild::Comment(comment) => {
221+
// FIXME: definitely wrong behavior
222+
let memoized = comment.memoized();
223+
flat.write(&format_args![memoized], f);
224+
multiline.write_content(&format_args![memoized], f);
225+
}
226+
227+
HtmlChild::Verbatim(element) => {
228+
multiline.write_content(&format_verbatim_skipped(element.syntax()), f);
229+
// we have to force multiline mode because the verbatim element can contain newlines.
230+
// since we are always going to end up in multiline mode, we can also skip writing this element to the `flat` builder.
231+
force_multiline = true;
232+
}
233+
213234
// * Whitespace after the opening tag and before a meaningful text: `<div> a`
214235
// * Whitespace before the closing tag: `a </div>`
215236
// * Whitespace before an opening tag: `a <div>`
@@ -315,6 +336,10 @@ impl FormatHtmlElementList {
315336
}
316337
}
317338

339+
Some(HtmlChild::Comment(_)) | Some(HtmlChild::Verbatim(_)) => {
340+
Some(LineMode::Hard)
341+
}
342+
318343
// Add a hard line break if what comes after the element is not a text or is all whitespace
319344
Some(HtmlChild::NonText(next_non_text)) => {
320345
// In the case of the formatter using the multiline layout, we want to treat inline elements like we do words.
@@ -423,36 +448,6 @@ impl FormatHtmlElementList {
423448
}
424449
}
425450

426-
/// Tracks the tokens of [HtmlContent] nodes to be formatted and
427-
/// asserts that the suppression comments are checked (they get ignored).
428-
///
429-
/// This is necessary because the formatting of [HtmlContentList] bypasses the node formatting for
430-
/// [HtmlContent] and instead, formats the nodes itself.
431-
#[cfg(debug_assertions)]
432-
fn disarm_debug_assertions(&self, node: &HtmlElementList, f: &mut HtmlFormatter) {
433-
use AnyHtmlElement::*;
434-
use biome_formatter::CstFormatContext;
435-
436-
for child in node {
437-
match child {
438-
HtmlContent(text) => {
439-
f.state_mut().track_token(&text.value_token().unwrap());
440-
441-
// You can't suppress a text node
442-
f.context()
443-
.comments()
444-
.mark_suppression_checked(text.syntax());
445-
}
446-
_ => {
447-
continue;
448-
}
449-
}
450-
}
451-
}
452-
453-
#[cfg(not(debug_assertions))]
454-
fn disarm_debug_assertions(&self, _: &HtmlElementList, _: &mut HtmlFormatter) {}
455-
456451
fn layout(&self, meta: ChildrenMeta) -> HtmlChildListLayout {
457452
match self.layout {
458453
HtmlChildListLayout::BestFitting => {
@@ -524,6 +519,9 @@ enum WordSeparator {
524519
/// `a b`
525520
BetweenWords,
526521

522+
/// Seperator between 2 lines. Creates hard line breaks.
523+
Lines(usize),
524+
527525
/// A separator of a word at the end of a [HtmlText] element. Either because it is the last
528526
/// child in its parent OR it is right before the start of another child (element, expression, ...).
529527
///
@@ -571,6 +569,11 @@ impl Format<HtmlFormatContext> for WordSeparator {
571569
fn fmt(&self, f: &mut Formatter<HtmlFormatContext>) -> FormatResult<()> {
572570
match self {
573571
WordSeparator::BetweenWords => soft_line_break_or_space().fmt(f),
572+
WordSeparator::Lines(count) => match count {
573+
0 => Ok(()),
574+
1 => hard_line_break().fmt(f),
575+
_ => empty_line().fmt(f),
576+
},
574577
WordSeparator::EndOfText {
575578
is_soft_line_break,
576579
is_next_element_whitespace_sensitive,

0 commit comments

Comments
 (0)