@@ -388,9 +388,7 @@ impl JsEmitter {
388388 /// Emit multiple statements to a string.
389389 pub fn emit_statements < ' a > ( & self , stmts : & [ OutputStatement < ' a > ] ) -> String {
390390 let mut ctx = EmitterContext :: new ( ) ;
391- for stmt in stmts {
392- self . visit_statement ( stmt, & mut ctx) ;
393- }
391+ self . visit_all_statements ( stmts, & mut ctx) ;
394392 ctx. to_source ( )
395393 }
396394
@@ -420,9 +418,7 @@ impl JsEmitter {
420418 generated_file : Option < & str > ,
421419 ) -> ( String , Option < oxc_sourcemap:: SourceMap > ) {
422420 let mut ctx = EmitterContext :: with_source_file ( source_file) ;
423- for stmt in stmts {
424- self . visit_statement ( stmt, & mut ctx) ;
425- }
421+ self . visit_all_statements ( stmts, & mut ctx) ;
426422 ctx. to_source_with_map ( generated_file)
427423 }
428424
@@ -440,6 +436,32 @@ impl JsEmitter {
440436 }
441437 }
442438
439+ /// Visit all statements, adding blank lines between top-level const/function declarations.
440+ ///
441+ /// This matches Angular's output formatting which adds a blank line between:
442+ /// - Consecutive const declarations
443+ /// - Consecutive function declarations
444+ /// - Between const and function declarations
445+ fn visit_all_statements < ' a > ( & self , stmts : & [ OutputStatement < ' a > ] , ctx : & mut EmitterContext ) {
446+ for ( i, stmt) in stmts. iter ( ) . enumerate ( ) {
447+ // Add blank line between top-level declarations (const/function)
448+ if i > 0 {
449+ let prev_stmt = & stmts[ i - 1 ] ;
450+ let needs_blank_line = matches ! (
451+ ( prev_stmt, stmt) ,
452+ ( OutputStatement :: DeclareVar ( _) , OutputStatement :: DeclareVar ( _) )
453+ | ( OutputStatement :: DeclareVar ( _) , OutputStatement :: DeclareFunction ( _) )
454+ | ( OutputStatement :: DeclareFunction ( _) , OutputStatement :: DeclareFunction ( _) )
455+ | ( OutputStatement :: DeclareFunction ( _) , OutputStatement :: DeclareVar ( _) )
456+ ) ;
457+ if needs_blank_line {
458+ ctx. newline ( ) ;
459+ }
460+ }
461+ self . visit_statement ( stmt, ctx) ;
462+ }
463+ }
464+
443465 fn visit_declare_var_stmt ( & self , stmt : & DeclareVarStmt < ' _ > , ctx : & mut EmitterContext ) {
444466 // Print leading comment if present
445467 // See: packages/compiler/src/output/abstract_emitter.ts:218-235
@@ -657,7 +679,7 @@ impl JsEmitter {
657679 }
658680 OutputExpression :: Comma ( e) => {
659681 ctx. print ( "(" ) ;
660- self . visit_all_expressions ( & e. parts , ctx, "," ) ;
682+ self . visit_all_expressions ( & e. parts , ctx, ", " ) ;
661683 ctx. print ( ")" ) ;
662684 }
663685 OutputExpression :: Function ( e) => {
@@ -679,7 +701,7 @@ impl JsEmitter {
679701 OutputExpression :: ArrowFunction ( e) => {
680702 ctx. print_with_span ( "(" , source_span) ;
681703 self . visit_params ( & e. params , ctx) ;
682- ctx. print ( ") =>" ) ;
704+ ctx. print ( ") => " ) ;
683705 match & e. body {
684706 ArrowFunctionBody :: Expression ( body_expr) => {
685707 // Check if the body is an object literal (needs parens)
@@ -717,14 +739,14 @@ impl JsEmitter {
717739 }
718740 // Map the function call to its source location
719741 ctx. print_with_span ( "(" , source_span) ;
720- self . visit_all_expressions ( & e. args , ctx, "," ) ;
742+ self . visit_all_expressions ( & e. args , ctx, ", " ) ;
721743 ctx. print ( ")" ) ;
722744 }
723745 OutputExpression :: Instantiate ( e) => {
724746 ctx. print_with_span ( "new " , source_span) ;
725747 self . visit_expression ( & e. class_expr , ctx) ;
726748 ctx. print ( "(" ) ;
727- self . visit_all_expressions ( & e. args , ctx, "," ) ;
749+ self . visit_all_expressions ( & e. args , ctx, ", " ) ;
728750 ctx. print ( ")" ) ;
729751 }
730752 OutputExpression :: DynamicImport ( e) => {
@@ -802,7 +824,7 @@ impl JsEmitter {
802824
803825 fn visit_literal_array < ' a > ( & self , entries : & [ OutputExpression < ' a > ] , ctx : & mut EmitterContext ) {
804826 ctx. print ( "[" ) ;
805- self . visit_all_expressions ( entries, ctx, "," ) ;
827+ self . visit_all_expressions ( entries, ctx, ", " ) ;
806828 ctx. print ( "]" ) ;
807829 }
808830
@@ -818,33 +840,37 @@ impl JsEmitter {
818840 entries : & [ super :: ast:: LiteralMapEntry < ' a > ] ,
819841 ctx : & mut EmitterContext ,
820842 ) {
821- ctx. print ( "{" ) ;
843+ if entries. is_empty ( ) {
844+ ctx. print ( "{}" ) ;
845+ return ;
846+ }
847+ ctx. print ( "{ " ) ;
822848 let mut incremented_indent = false ;
823849 for ( i, entry) in entries. iter ( ) . enumerate ( ) {
824850 if i > 0 {
825851 // Check line length and break if needed
826852 if ctx. line_length ( ) > LINE_LENGTH_LIMIT {
827- ctx. println ( "," ) ;
853+ ctx. println ( ", " ) ;
828854 if !incremented_indent {
829855 // Continuation lines are marked with double indent
830856 ctx. inc_indent ( ) ;
831857 ctx. inc_indent ( ) ;
832858 incremented_indent = true ;
833859 }
834860 } else {
835- ctx. print ( "," ) ;
861+ ctx. print ( ", " ) ;
836862 }
837863 }
838864 let key = escape_identifier ( & entry. key , self . escape_dollar_in_strings , entry. quoted ) ;
839865 ctx. print ( & key) ;
840- ctx. print ( ":" ) ;
866+ ctx. print ( ": " ) ;
841867 self . visit_expression ( & entry. value , ctx) ;
842868 }
843869 if incremented_indent {
844870 ctx. dec_indent ( ) ;
845871 ctx. dec_indent ( ) ;
846872 }
847- ctx. print ( "}" ) ;
873+ ctx. print ( " }" ) ;
848874 }
849875
850876 fn visit_template_literal (
@@ -950,7 +976,7 @@ impl JsEmitter {
950976 fn visit_params ( & self , params : & [ FnParam < ' _ > ] , ctx : & mut EmitterContext ) {
951977 for ( i, param) in params. iter ( ) . enumerate ( ) {
952978 if i > 0 {
953- ctx. print ( "," ) ;
979+ ctx. print ( ", " ) ;
954980 }
955981 ctx. print ( & param. name ) ;
956982 }
@@ -1047,18 +1073,18 @@ fn unary_operator_to_str(op: UnaryOperator) -> &'static str {
10471073/// Escape a string for JavaScript output.
10481074fn escape_string ( input : & str , escape_dollar : bool ) -> String {
10491075 let mut result = String :: with_capacity ( input. len ( ) + 2 ) ;
1050- result. push ( '\' ' ) ;
1076+ result. push ( '" ' ) ;
10511077 for c in input. chars ( ) {
10521078 match c {
1053- '\'' => result. push_str ( "\\ ' " ) ,
1079+ '"' => result. push_str ( "\\ \" " ) ,
10541080 '\\' => result. push_str ( "\\ \\ " ) ,
10551081 '\n' => result. push_str ( "\\ n" ) ,
10561082 '\r' => result. push_str ( "\\ r" ) ,
10571083 '$' if escape_dollar => result. push_str ( "\\ $" ) ,
10581084 _ => result. push ( c) ,
10591085 }
10601086 }
1061- result. push ( '\' ' ) ;
1087+ result. push ( '" ' ) ;
10621088 result
10631089}
10641090
@@ -1136,7 +1162,7 @@ mod tests {
11361162 LiteralExpr { value : LiteralValue :: String ( Atom :: from ( "hello" ) ) , source_span : None } ,
11371163 & alloc,
11381164 ) ) ;
1139- assert_eq ! ( emitter. emit_expression( & expr) , "' hello' " ) ;
1165+ assert_eq ! ( emitter. emit_expression( & expr) , "\" hello\" " ) ;
11401166 }
11411167
11421168 #[ test]
@@ -1152,11 +1178,12 @@ mod tests {
11521178
11531179 #[ test]
11541180 fn test_escape_string ( ) {
1155- assert_eq ! ( escape_string( "hello" , false ) , "'hello'" ) ;
1156- assert_eq ! ( escape_string( "it's" , false ) , "'it\\ 's'" ) ;
1157- assert_eq ! ( escape_string( "line\n break" , false ) , "'line\\ nbreak'" ) ;
1158- assert_eq ! ( escape_string( "$var" , true ) , "'\\ $var'" ) ;
1159- assert_eq ! ( escape_string( "$var" , false ) , "'$var'" ) ;
1181+ assert_eq ! ( escape_string( "hello" , false ) , "\" hello\" " ) ;
1182+ assert_eq ! ( escape_string( "it's" , false ) , "\" it's\" " ) ;
1183+ assert_eq ! ( escape_string( "say \" hi\" " , false ) , "\" say \\ \" hi\\ \" \" " ) ;
1184+ assert_eq ! ( escape_string( "line\n break" , false ) , "\" line\\ nbreak\" " ) ;
1185+ assert_eq ! ( escape_string( "$var" , true ) , "\" \\ $var\" " ) ;
1186+ assert_eq ! ( escape_string( "$var" , false ) , "\" $var\" " ) ;
11601187 }
11611188
11621189 // ========================================================================
0 commit comments