@@ -400,19 +400,24 @@ impl<'s> DocGen<'s> for Element<'s> {
400
400
. split_once ( ':' )
401
401
. and_then ( |( namespace, name) | namespace. eq_ignore_ascii_case ( "html" ) . then_some ( name) )
402
402
. unwrap_or ( self . tag_name ) ;
403
+ let formatted_tag_name = if matches ! (
404
+ ctx. language,
405
+ Language :: Html | Language :: Jinja | Language :: Vento
406
+ ) && css_dataset:: tags:: STANDARD_HTML_TAGS
407
+ . iter ( )
408
+ . any ( |tag| tag. eq_ignore_ascii_case ( self . tag_name ) )
409
+ {
410
+ Cow :: from ( self . tag_name . to_ascii_lowercase ( ) )
411
+ } else {
412
+ Cow :: from ( self . tag_name )
413
+ } ;
403
414
let is_root = state. is_root ;
404
415
let mut state = State {
405
416
current_tag_name : Some ( tag_name) ,
406
417
is_root : false ,
407
418
in_svg : tag_name. eq_ignore_ascii_case ( "svg" ) ,
408
419
indent_level : state. indent_level ,
409
420
} ;
410
- let should_lower_cased = matches ! (
411
- ctx. language,
412
- Language :: Html | Language :: Jinja | Language :: Vento
413
- ) && css_dataset:: tags:: STANDARD_HTML_TAGS
414
- . iter ( )
415
- . any ( |tag| tag. eq_ignore_ascii_case ( self . tag_name ) ) ;
416
421
417
422
let self_closing = if helpers:: is_void_element ( tag_name, ctx. language ) {
418
423
ctx. options
@@ -447,115 +452,128 @@ impl<'s> DocGen<'s> for Element<'s> {
447
452
let mut docs = Vec :: with_capacity ( 5 ) ;
448
453
449
454
docs. push ( Doc :: text ( "<" ) ) ;
450
- docs. push ( Doc :: text ( if should_lower_cased {
451
- Cow :: from ( self . tag_name . to_ascii_lowercase ( ) )
452
- } else {
453
- Cow :: from ( self . tag_name )
454
- } ) ) ;
455
-
456
- let attrs_sep = if !self . first_attr_same_line
457
- && !ctx. options . prefer_attrs_single_line
458
- && self . attrs . len ( ) > 1
459
- && !ctx
460
- . options
461
- . max_attrs_per_line
462
- . map ( |value| value. get ( ) > 1 )
463
- . unwrap_or_default ( )
464
- {
465
- Doc :: hard_line ( )
466
- } else {
467
- Doc :: line_or_space ( )
468
- } ;
469
- let attrs = if let Some ( max) = ctx. options . max_attrs_per_line {
470
- // fix #2
471
- if self . attrs . is_empty ( ) {
472
- Doc :: line_or_nil ( )
473
- } else {
474
- Doc :: line_or_space ( )
455
+ docs. push ( Doc :: text ( formatted_tag_name. clone ( ) ) ) ;
456
+
457
+ match self . attrs . as_slice ( ) {
458
+ [ attr] if !is_whitespace_sensitive && !is_multi_line_attr ( attr) => {
459
+ docs. push ( Doc :: space ( ) ) ;
460
+ docs. push ( attr. doc ( ctx, & state) ) ;
461
+ if self_closing && is_empty {
462
+ docs. push ( Doc :: text ( " />" ) ) ;
463
+ return Doc :: list ( docs) ;
464
+ } else {
465
+ docs. push ( Doc :: text ( ">" ) ) ;
466
+ } ;
467
+ if self . void_element {
468
+ return Doc :: list ( docs) ;
469
+ }
475
470
}
476
- . concat ( itertools:: intersperse (
477
- self . attrs . chunks ( max. into ( ) ) . map ( |chunk| {
471
+ _ => {
472
+ let attrs_sep = if !self . first_attr_same_line
473
+ && !ctx. options . prefer_attrs_single_line
474
+ && self . attrs . len ( ) > 1
475
+ && !ctx
476
+ . options
477
+ . max_attrs_per_line
478
+ . map ( |value| value. get ( ) > 1 )
479
+ . unwrap_or_default ( )
480
+ {
481
+ Doc :: hard_line ( )
482
+ } else {
483
+ Doc :: line_or_space ( )
484
+ } ;
485
+ let attrs = if let Some ( max) = ctx. options . max_attrs_per_line {
486
+ // fix #2
487
+ if self . attrs . is_empty ( ) {
488
+ Doc :: line_or_nil ( )
489
+ } else {
490
+ Doc :: line_or_space ( )
491
+ }
492
+ . concat ( itertools:: intersperse (
493
+ self . attrs . chunks ( max. into ( ) ) . map ( |chunk| {
494
+ Doc :: list (
495
+ itertools:: intersperse (
496
+ chunk. iter ( ) . map ( |attr| attr. doc ( ctx, & state) ) ,
497
+ attrs_sep. clone ( ) ,
498
+ )
499
+ . collect ( ) ,
500
+ )
501
+ . group ( )
502
+ } ) ,
503
+ Doc :: hard_line ( ) ,
504
+ ) )
505
+ . nest ( ctx. indent_width )
506
+ } else {
478
507
Doc :: list (
479
- itertools:: intersperse (
480
- chunk. iter ( ) . map ( |attr| attr. doc ( ctx, & state) ) ,
481
- attrs_sep. clone ( ) ,
482
- )
483
- . collect ( ) ,
508
+ self . attrs
509
+ . iter ( )
510
+ . flat_map ( |attr| [ attrs_sep. clone ( ) , attr. doc ( ctx, & state) ] . into_iter ( ) )
511
+ . collect ( ) ,
484
512
)
485
- . group ( )
486
- } ) ,
487
- Doc :: hard_line ( ) ,
488
- ) )
489
- . nest ( ctx. indent_width )
490
- } else {
491
- Doc :: list (
492
- self . attrs
493
- . iter ( )
494
- . flat_map ( |attr| [ attrs_sep. clone ( ) , attr. doc ( ctx, & state) ] . into_iter ( ) )
495
- . collect ( ) ,
496
- )
497
- . nest ( ctx. indent_width )
498
- } ;
513
+ . nest ( ctx. indent_width )
514
+ } ;
499
515
500
- if self . void_element {
501
- docs. push ( attrs) ;
502
- if self_closing {
503
- docs. push ( Doc :: line_or_space ( ) ) ;
504
- docs. push ( Doc :: text ( "/>" ) ) ;
505
- } else {
506
- if !ctx. options . closing_bracket_same_line {
507
- docs. push ( Doc :: line_or_nil ( ) ) ;
508
- }
509
- docs. push ( Doc :: text ( ">" ) ) ;
510
- }
511
- return Doc :: list ( docs) . group ( ) ;
512
- }
513
- if self_closing && is_empty {
514
- docs. push ( attrs) ;
515
- docs. push ( Doc :: line_or_space ( ) ) ;
516
- docs. push ( Doc :: text ( "/>" ) ) ;
517
- return Doc :: list ( docs) . group ( ) ;
518
- }
519
- if ctx. options . closing_bracket_same_line {
520
- docs. push ( attrs. append ( Doc :: text ( ">" ) ) . group ( ) ) ;
521
- } else {
522
- // for #16
523
- if is_whitespace_sensitive
524
- && !self . attrs . is_empty ( ) // there're no attributes, so don't insert line break
525
- && self
526
- . children
527
- . first ( )
528
- . is_some_and ( |child| {
529
- if let NodeKind :: Text ( text_node) = & child. kind {
530
- !text_node. raw . starts_with ( |c : char | c. is_ascii_whitespace ( ) )
531
- } else {
532
- false
533
- }
534
- } )
535
- && self
536
- . children
537
- . last ( )
538
- . is_some_and ( |child| {
539
- if let NodeKind :: Text ( text_node) = & child. kind {
540
- !text_node. raw . ends_with ( |c : char | c. is_ascii_whitespace ( ) )
541
- } else {
542
- false
516
+ if self . void_element {
517
+ docs. push ( attrs) ;
518
+ if self_closing {
519
+ docs. push ( Doc :: line_or_space ( ) ) ;
520
+ docs. push ( Doc :: text ( "/>" ) ) ;
521
+ } else {
522
+ if !ctx. options . closing_bracket_same_line {
523
+ docs. push ( Doc :: line_or_nil ( ) ) ;
543
524
}
544
- } )
545
- {
546
- docs. push (
547
- attrs
548
- . group ( )
549
- . append ( Doc :: line_or_nil ( ) )
550
- . append ( Doc :: text ( ">" ) ) ,
551
- ) ;
552
- } else {
553
- docs. push (
554
- attrs
555
- . append ( Doc :: line_or_nil ( ) )
556
- . append ( Doc :: text ( ">" ) )
557
- . group ( ) ,
558
- ) ;
525
+ docs. push ( Doc :: text ( ">" ) ) ;
526
+ }
527
+ return Doc :: list ( docs) . group ( ) ;
528
+ }
529
+ if self_closing && is_empty {
530
+ docs. push ( attrs) ;
531
+ docs. push ( Doc :: line_or_space ( ) ) ;
532
+ docs. push ( Doc :: text ( "/>" ) ) ;
533
+ return Doc :: list ( docs) . group ( ) ;
534
+ }
535
+ if ctx. options . closing_bracket_same_line {
536
+ docs. push ( attrs. append ( Doc :: text ( ">" ) ) . group ( ) ) ;
537
+ } else {
538
+ // for #16
539
+ if is_whitespace_sensitive
540
+ && !self . attrs . is_empty ( ) // there're no attributes, so don't insert line break
541
+ && self
542
+ . children
543
+ . first ( )
544
+ . is_some_and ( |child| {
545
+ if let NodeKind :: Text ( text_node) = & child. kind {
546
+ !text_node. raw . starts_with ( |c : char | c. is_ascii_whitespace ( ) )
547
+ } else {
548
+ false
549
+ }
550
+ } )
551
+ && self
552
+ . children
553
+ . last ( )
554
+ . is_some_and ( |child| {
555
+ if let NodeKind :: Text ( text_node) = & child. kind {
556
+ !text_node. raw . ends_with ( |c : char | c. is_ascii_whitespace ( ) )
557
+ } else {
558
+ false
559
+ }
560
+ } )
561
+ {
562
+ docs. push (
563
+ attrs
564
+ . group ( )
565
+ . append ( Doc :: line_or_nil ( ) )
566
+ . append ( Doc :: text ( ">" ) ) ,
567
+ ) ;
568
+ } else {
569
+ docs. push (
570
+ attrs
571
+ . append ( Doc :: line_or_nil ( ) )
572
+ . append ( Doc :: text ( ">" ) )
573
+ . group ( ) ,
574
+ ) ;
575
+ }
576
+ }
559
577
}
560
578
}
561
579
@@ -756,11 +774,7 @@ impl<'s> DocGen<'s> for Element<'s> {
756
774
757
775
docs. push (
758
776
Doc :: text ( "</" )
759
- . append ( Doc :: text ( if should_lower_cased {
760
- Cow :: from ( self . tag_name . to_ascii_lowercase ( ) )
761
- } else {
762
- Cow :: from ( self . tag_name )
763
- } ) )
777
+ . append ( Doc :: text ( formatted_tag_name) )
764
778
. append ( Doc :: line_or_nil ( ) )
765
779
. append ( Doc :: text ( ">" ) )
766
780
. group ( ) ,
@@ -1966,6 +1980,25 @@ fn is_all_ascii_whitespace(s: &str) -> bool {
1966
1980
!s. is_empty ( ) && s. as_bytes ( ) . iter ( ) . all ( |byte| byte. is_ascii_whitespace ( ) )
1967
1981
}
1968
1982
1983
+ fn is_multi_line_attr ( attr : & Attribute ) -> bool {
1984
+ match attr {
1985
+ Attribute :: Native ( attr) => attr
1986
+ . value
1987
+ . map ( |( value, _) | value. trim ( ) . contains ( '\n' ) )
1988
+ . unwrap_or ( false ) ,
1989
+ Attribute :: VueDirective ( attr) => attr
1990
+ . value
1991
+ . map ( |( value, _) | value. contains ( '\n' ) )
1992
+ . unwrap_or ( false ) ,
1993
+ Attribute :: Astro ( attr) => attr. expr . 0 . contains ( '\n' ) ,
1994
+ Attribute :: Svelte ( attr) => attr. expr . 0 . contains ( '\n' ) ,
1995
+ Attribute :: JinjaComment ( comment) => comment. raw . contains ( '\n' ) ,
1996
+ Attribute :: JinjaTag ( tag) => tag. content . contains ( '\n' ) ,
1997
+ // Templating blocks usually span across multiple lines so let's just assume true.
1998
+ Attribute :: JinjaBlock ( ..) | Attribute :: VentoTagOrBlock ( ..) => true ,
1999
+ }
2000
+ }
2001
+
1969
2002
fn should_ignore_node < ' s , E , F > ( index : usize , nodes : & [ Node ] , ctx : & Ctx < ' s , E , F > ) -> bool
1970
2003
where
1971
2004
F : for < ' a > FnMut ( & ' a str , Hints ) -> Result < Cow < ' a , str > , E > ,
0 commit comments