@@ -6,6 +6,7 @@ mod stack;
66
77use std:: num:: NonZeroU8 ;
88
9+ use oxc_data_structures:: code_buffer:: { self , CodeBuffer } ;
910pub use printer_options:: * ;
1011use unicode_width:: UnicodeWidthChar ;
1112
@@ -41,7 +42,14 @@ pub struct Printer<'a> {
4142
4243impl < ' a > Printer < ' a > {
4344 pub fn new ( options : PrinterOptions ) -> Self {
44- Self { options, state : PrinterState :: default ( ) }
45+ let ( indent_char, indent_width) = match options. indent_style ( ) {
46+ IndentStyle :: Tab => ( code_buffer:: IndentChar :: Tab , 1 ) ,
47+ IndentStyle :: Space => {
48+ ( code_buffer:: IndentChar :: Space , options. indent_width ( ) . value ( ) as usize )
49+ }
50+ } ;
51+ let buffer = CodeBuffer :: with_indent ( indent_char, indent_width) ;
52+ Self { options, state : PrinterState { buffer, ..Default :: default ( ) } }
4553 }
4654
4755 /// Prints the passed in element as well as all its content
@@ -68,7 +76,7 @@ impl<'a> Printer<'a> {
6876 }
6977 }
7078
71- Ok ( Printed :: new ( self . state . buffer , None ) )
79+ Ok ( Printed :: new ( self . state . buffer . into_string ( ) , None ) )
7280 }
7381
7482 /// Prints a single element and push the following elements to queue
@@ -584,42 +592,61 @@ impl<'a> Printer<'a> {
584592
585593 fn print_text ( & mut self , text : Text ) {
586594 if !self . state . pending_indent . is_empty ( ) {
587- let ( indent_char, repeat_count) = match self . options . indent_style ( ) {
588- IndentStyle :: Tab => ( '\t' , 1 ) ,
589- IndentStyle :: Space => ( ' ' , self . options . indent_width ( ) . value ( ) ) ,
590- } ;
591-
592595 let indent = std:: mem:: take ( & mut self . state . pending_indent ) ;
593- let total_indent_char_count = indent. level ( ) as usize * repeat_count as usize ;
594-
595- self . state
596- . buffer
597- . reserve ( total_indent_char_count + indent. align ( ) as usize + text. len ( ) ) ;
598596
599- for _ in 0 ..total_indent_char_count {
600- self . print_char ( indent_char) ;
597+ match self . options . indent_style ( ) {
598+ IndentStyle :: Tab => {
599+ for _ in 0 ..indent. level ( ) {
600+ // SAFETY: `'\t'` is an valid ASCII character
601+ unsafe {
602+ self . state . buffer . print_byte_unchecked ( b'\t' ) ;
603+ }
604+ self . state . line_width += self . options . indent_width ( ) . value ( ) as usize ;
605+ }
606+ }
607+ IndentStyle :: Space => {
608+ #[ expect( clippy:: cast_lossless) ]
609+ let total = indent. level ( ) * self . options . indent_width ( ) . value ( ) as u16 ;
610+ for _ in 0 ..total {
611+ // SAFETY: `' '` is an valid ASCII character
612+ unsafe {
613+ self . state . buffer . print_byte_unchecked ( b' ' ) ;
614+ }
615+ self . state . line_width += 1 ;
616+ }
617+ }
601618 }
602619
603620 for _ in 0 ..indent. align ( ) {
604- self . print_char ( ' ' ) ;
621+ // SAFETY: `' '` is an valid ASCII character
622+ unsafe {
623+ self . state . buffer . print_byte_unchecked ( b' ' ) ;
624+ }
625+ self . state . line_width += 1 ;
605626 }
606627 }
607628
608629 // Print pending spaces
609630 if self . state . pending_space {
610- self . state . buffer . push ( ' ' ) ;
631+ // SAFETY: `' '` is an valid ASCII character
632+ unsafe {
633+ self . state . buffer . print_byte_unchecked ( b' ' ) ;
634+ }
611635 self . state . pending_space = false ;
612636 self . state . line_width += 1 ;
613637 }
614638
615639 match text {
616640 Text :: Token ( text) => {
617- self . state . buffer . push_str ( text) ;
641+ // SAFETY: `text` is a ASCII-only string
642+ unsafe {
643+ self . state . buffer . print_bytes_unchecked ( text. as_bytes ( ) ) ;
644+ }
618645 self . state . line_width += text. len ( ) ;
619646 }
620647 Text :: Text { text, width } => {
621648 if let Some ( width) = width. width ( ) {
622- self . state . buffer . push_str ( text) ;
649+ self . state . buffer . print_str ( text) ;
623650 self . state . line_width += width. value ( ) as usize ;
624651 } else {
625652 for char in text. chars ( ) {
@@ -634,19 +661,25 @@ impl<'a> Printer<'a> {
634661
635662 fn print_char ( & mut self , char : char ) {
636663 if char == '\n' {
637- self . state . buffer . push_str ( self . options . line_ending . as_str ( ) ) ;
664+ // SAFETY: `line_ending` is one of `\n`, `\r\n` or `\r`, all valid ASCII sequences
665+ unsafe {
666+ self . state . buffer . print_bytes_unchecked ( self . options . line_ending . as_bytes ( ) ) ;
667+ }
638668
639669 self . state . line_width = 0 ;
640670
641671 // Fit's only tests if groups up to the first line break fit.
642672 // The next group must re-measure if it still fits.
643673 self . state . measured_group_fits = false ;
644674 } else {
645- self . state . buffer . push ( char) ;
646-
647675 let char_width = if char == '\t' {
676+ // SAFETY: `'\t'` is an valid ASCII character
677+ unsafe {
678+ self . state . buffer . print_byte_unchecked ( b'\t' ) ;
679+ }
648680 self . options . indent_width ( ) . value ( ) as usize
649681 } else {
682+ self . state . buffer . print_char ( char) ;
650683 char. width ( ) . unwrap_or ( 0 )
651684 } ;
652685
@@ -677,7 +710,7 @@ enum FillPairLayout {
677710/// position the printer currently is.
678711#[ derive( Default , Debug ) ]
679712struct PrinterState < ' a > {
680- buffer : String ,
713+ buffer : CodeBuffer ,
681714 pending_indent : Indention ,
682715 pending_space : bool ,
683716 measured_group_fits : bool ,
0 commit comments