@@ -6,7 +6,7 @@ use std::{
6
6
use biome_formatter:: {
7
7
Buffer , Format , FormatElement , FormatResult , format_args, prelude:: * , write,
8
8
} ;
9
- use biome_html_syntax:: AnyHtmlElement ;
9
+ use biome_html_syntax:: { AnyHtmlElement , HtmlSyntaxNode } ;
10
10
use biome_rowan:: { SyntaxResult , TextLen , TextRange , TextSize , TokenText } ;
11
11
12
12
use crate :: { HtmlFormatter , comments:: HtmlComments , context:: HtmlFormatContext } ;
@@ -75,6 +75,11 @@ pub(crate) enum HtmlChild {
75
75
/// A Single word in a HTML text. For example, the words for `a b\nc` are `[a, b, c]`
76
76
Word ( HtmlWord ) ,
77
77
78
+ /// A comment in a HTML text.
79
+ ///
80
+ /// This is considered a seperate kind of "word" here because we must preserve whitespace between text and comments.
81
+ Comment ( HtmlWord ) ,
82
+
78
83
/// A ` ` whitespace
79
84
///
80
85
/// ```html
@@ -164,7 +169,7 @@ impl Format<HtmlFormatContext> for HtmlRawSpace {
164
169
165
170
pub ( crate ) fn html_split_children < I > (
166
171
children : I ,
167
- _comments : & HtmlComments ,
172
+ comments : & HtmlComments ,
168
173
) -> SyntaxResult < Vec < HtmlChild > >
169
174
where
170
175
I : IntoIterator < Item = AnyHtmlElement > ,
@@ -179,6 +184,11 @@ where
179
184
// Keep track if there's any leading/trailing empty line, new line or whitespace
180
185
181
186
let value_token = text. value_token ( ) ?;
187
+ let node = HtmlSyntaxNode :: from ( text) ;
188
+ for comment in comments. leading_dangling_trailing_comments ( & node) {
189
+ // Manually mark these comments as formatted because they are. Because we override the formatting of text content in here, the formatter does not seem to recognize them as formatted.
190
+ comment. mark_formatted ( ) ;
191
+ }
182
192
let mut chunks = HtmlSplitChunksIterator :: new ( value_token. text ( ) ) . peekable ( ) ;
183
193
184
194
// Text starting with a whitespace
@@ -216,15 +226,41 @@ where
216
226
}
217
227
}
218
228
229
+ let mut prev_was_comment = false ;
219
230
while let Some ( chunk) = chunks. next ( ) {
220
231
match chunk {
221
232
( _, HtmlTextChunk :: Whitespace ( whitespace) ) => {
222
233
// Only handle trailing whitespace. Words must always be joined by new lines
223
- if chunks. peek ( ) . is_none ( ) {
224
- if whitespace. contains ( '\n' ) {
225
- builder. entry ( HtmlChild :: Newline ) ;
226
- } else {
227
- builder. entry ( HtmlChild :: Whitespace )
234
+ let newlines = whitespace. chars ( ) . filter ( |b| * b == '\n' ) . count ( ) ;
235
+ match chunks. peek ( ) {
236
+ Some ( & ( _, HtmlTextChunk :: Comment ( _) ) ) => {
237
+ // if the next chunk is a comment, preserve the whitespace
238
+ if newlines >= 2 {
239
+ builder. entry ( HtmlChild :: EmptyLine )
240
+ } else if newlines == 1 {
241
+ builder. entry ( HtmlChild :: Newline )
242
+ } else {
243
+ builder. entry ( HtmlChild :: Whitespace )
244
+ }
245
+ }
246
+ None => {
247
+ if newlines >= 1 {
248
+ builder. entry ( HtmlChild :: Newline )
249
+ } else {
250
+ builder. entry ( HtmlChild :: Whitespace )
251
+ }
252
+ }
253
+ _ => {
254
+ // if the previous chunk was a comment, we need to preserve the whitespace before the next chunk (which will never be whitespace).
255
+ if prev_was_comment {
256
+ if newlines >= 2 {
257
+ builder. entry ( HtmlChild :: EmptyLine )
258
+ } else if newlines == 1 {
259
+ builder. entry ( HtmlChild :: Newline )
260
+ } else {
261
+ builder. entry ( HtmlChild :: Whitespace )
262
+ }
263
+ }
228
264
}
229
265
}
230
266
}
@@ -237,7 +273,16 @@ where
237
273
238
274
builder. entry ( HtmlChild :: Word ( HtmlWord :: new ( text, source_position) ) ) ;
239
275
}
276
+ ( relative_start, HtmlTextChunk :: Comment ( word) ) => {
277
+ let text = value_token
278
+ . token_text ( )
279
+ . slice ( TextRange :: at ( relative_start, word. text_len ( ) ) ) ;
280
+ let source_position = value_token. text_range ( ) . start ( ) + relative_start;
281
+
282
+ builder. entry ( HtmlChild :: Comment ( HtmlWord :: new ( text, source_position) ) ) ;
283
+ }
240
284
}
285
+ prev_was_comment = matches ! ( chunk, ( _, HtmlTextChunk :: Comment ( _) ) ) ;
241
286
}
242
287
prev_was_content = true ;
243
288
}
@@ -321,6 +366,7 @@ impl HtmlSplitChildrenBuilder {
321
366
enum HtmlTextChunk < ' a > {
322
367
Whitespace ( & ' a str ) ,
323
368
Word ( & ' a str ) ,
369
+ Comment ( & ' a str ) ,
324
370
}
325
371
326
372
/// Splits a text into whitespace only and non-whitespace chunks.
@@ -352,23 +398,54 @@ impl<'a> Iterator for HtmlSplitChunksIterator<'a> {
352
398
self . position += char. text_len ( ) ;
353
399
354
400
let is_whitespace = matches ! ( char , ' ' | '\n' | '\t' | '\r' ) ;
401
+ let mut maybe_comment = char == '<' ;
402
+ let mut definitely_comment = false ;
403
+ let mut seen_end_comment_chars = 0 ;
355
404
356
405
while let Some ( next) = self . chars . peek ( ) {
357
- let next_is_whitespace = matches ! ( next, ' ' | '\n' | '\t' | '\r' ) ;
406
+ if maybe_comment && !definitely_comment {
407
+ match ( self . position - start, next) {
408
+ ( idx, '!' ) if idx == 1 . into ( ) => { }
409
+ ( idx, '-' ) if idx == 2 . into ( ) || idx == 3 . into ( ) => { }
410
+ ( idx, _) if idx == 4 . into ( ) => {
411
+ definitely_comment = true ;
412
+ }
413
+ _ => {
414
+ maybe_comment = false ;
415
+ }
416
+ }
417
+ }
358
418
359
- if is_whitespace != next_is_whitespace {
360
- break ;
419
+ if definitely_comment {
420
+ match ( seen_end_comment_chars, next) {
421
+ ( 0 , '-' ) => seen_end_comment_chars += 1 ,
422
+ ( 1 , '-' ) => seen_end_comment_chars += 1 ,
423
+ ( 2 , '>' ) => seen_end_comment_chars += 1 ,
424
+ _ => seen_end_comment_chars = 0 ,
425
+ }
426
+ } else {
427
+ let next_is_whitespace = matches ! ( next, ' ' | '\n' | '\t' | '\r' ) ;
428
+
429
+ if is_whitespace != next_is_whitespace {
430
+ break ;
431
+ }
361
432
}
362
433
363
434
self . position += next. text_len ( ) ;
364
435
self . chars . next ( ) ;
436
+
437
+ if seen_end_comment_chars == 3 {
438
+ break ;
439
+ }
365
440
}
366
441
367
442
let range = TextRange :: new ( start, self . position ) ;
368
443
let slice = & self . text [ range] ;
369
444
370
445
let chunk = if is_whitespace {
371
446
HtmlTextChunk :: Whitespace ( slice)
447
+ } else if definitely_comment {
448
+ HtmlTextChunk :: Comment ( slice)
372
449
} else {
373
450
HtmlTextChunk :: Word ( slice)
374
451
} ;
0 commit comments