@@ -764,9 +764,14 @@ impl JsEmitter {
764764 ctx. print ( ") =>" ) ;
765765 match & e. body {
766766 ArrowFunctionBody :: Expression ( body_expr) => {
767- // Check if the body is an object literal (needs parens)
768- let is_object_literal =
769- matches ! ( body_expr. as_ref( ) , OutputExpression :: LiteralMap ( _) ) ;
767+ // Check if the body is an object literal (needs parens).
768+ // Also unwrap Parenthesized wrapper, which comes from converting
769+ // OXC's ParenthesizedExpression (e.g. `() => ({ key: val })`).
770+ let inner = match body_expr. as_ref ( ) {
771+ OutputExpression :: Parenthesized ( p) => p. expr . as_ref ( ) ,
772+ other => other,
773+ } ;
774+ let is_object_literal = matches ! ( inner, OutputExpression :: LiteralMap ( _) ) ;
770775 if is_object_literal {
771776 ctx. print ( "(" ) ;
772777 }
@@ -2616,4 +2621,145 @@ mod tests {
26162621 let output = emitter. emit_statement ( & stmt) ;
26172622 assert_eq ! ( output, "function foo() {\n }" ) ;
26182623 }
2624+
2625+ // ========================================================================
2626+ // Arrow Function Object Literal Paren Tests (issue #43)
2627+ // ========================================================================
2628+
2629+ #[ test]
2630+ fn test_emit_arrow_function_direct_object_literal_body ( ) {
2631+ use super :: super :: ast:: {
2632+ ArrowFunctionBody , ArrowFunctionExpr , LiteralMapEntry , LiteralMapExpr ,
2633+ } ;
2634+
2635+ let emitter = JsEmitter :: new ( ) ;
2636+ let alloc = Allocator :: default ( ) ;
2637+
2638+ // Build: () =>({showMenu:signal(true)})
2639+ let signal_call = OutputExpression :: InvokeFunction ( Box :: new_in (
2640+ super :: super :: ast:: InvokeFunctionExpr {
2641+ fn_expr : Box :: new_in (
2642+ OutputExpression :: ReadVar ( Box :: new_in (
2643+ ReadVarExpr { name : Atom :: from ( "signal" ) , source_span : None } ,
2644+ & alloc,
2645+ ) ) ,
2646+ & alloc,
2647+ ) ,
2648+ args : {
2649+ let mut args = oxc_allocator:: Vec :: new_in ( & alloc) ;
2650+ args. push ( OutputExpression :: Literal ( Box :: new_in (
2651+ LiteralExpr { value : LiteralValue :: Boolean ( true ) , source_span : None } ,
2652+ & alloc,
2653+ ) ) ) ;
2654+ args
2655+ } ,
2656+ pure : false ,
2657+ optional : false ,
2658+ source_span : None ,
2659+ } ,
2660+ & alloc,
2661+ ) ) ;
2662+
2663+ let mut entries = oxc_allocator:: Vec :: new_in ( & alloc) ;
2664+ entries. push ( LiteralMapEntry {
2665+ key : Atom :: from ( "showMenu" ) ,
2666+ value : signal_call,
2667+ quoted : false ,
2668+ } ) ;
2669+
2670+ let obj_literal = OutputExpression :: LiteralMap ( Box :: new_in (
2671+ LiteralMapExpr { entries, source_span : None } ,
2672+ & alloc,
2673+ ) ) ;
2674+
2675+ let params = oxc_allocator:: Vec :: new_in ( & alloc) ;
2676+
2677+ let expr = OutputExpression :: ArrowFunction ( Box :: new_in (
2678+ ArrowFunctionExpr {
2679+ params,
2680+ body : ArrowFunctionBody :: Expression ( Box :: new_in ( obj_literal, & alloc) ) ,
2681+ source_span : None ,
2682+ } ,
2683+ & alloc,
2684+ ) ) ;
2685+
2686+ let output = emitter. emit_expression ( & expr) ;
2687+ // Object literal body must be wrapped in parens to avoid ambiguity with block
2688+ assert ! ( output. contains( "=>({" ) , "Expected parens around object literal, got: {output}" ) ;
2689+ assert ! ( output. ends_with( ")" ) , "Expected closing paren, got: {output}" ) ;
2690+ }
2691+
2692+ #[ test]
2693+ fn test_emit_arrow_function_parenthesized_object_literal_body ( ) {
2694+ use super :: super :: ast:: {
2695+ ArrowFunctionBody , ArrowFunctionExpr , LiteralMapEntry , LiteralMapExpr ,
2696+ ParenthesizedExpr ,
2697+ } ;
2698+
2699+ let emitter = JsEmitter :: new ( ) ;
2700+ let alloc = Allocator :: default ( ) ;
2701+
2702+ // Build: () =>({showMenu:signal(true)})
2703+ // But the body is Parenthesized(LiteralMap(...)) as would come from OXC AST conversion
2704+ let signal_call = OutputExpression :: InvokeFunction ( Box :: new_in (
2705+ super :: super :: ast:: InvokeFunctionExpr {
2706+ fn_expr : Box :: new_in (
2707+ OutputExpression :: ReadVar ( Box :: new_in (
2708+ ReadVarExpr { name : Atom :: from ( "signal" ) , source_span : None } ,
2709+ & alloc,
2710+ ) ) ,
2711+ & alloc,
2712+ ) ,
2713+ args : {
2714+ let mut args = oxc_allocator:: Vec :: new_in ( & alloc) ;
2715+ args. push ( OutputExpression :: Literal ( Box :: new_in (
2716+ LiteralExpr { value : LiteralValue :: Boolean ( true ) , source_span : None } ,
2717+ & alloc,
2718+ ) ) ) ;
2719+ args
2720+ } ,
2721+ pure : false ,
2722+ optional : false ,
2723+ source_span : None ,
2724+ } ,
2725+ & alloc,
2726+ ) ) ;
2727+
2728+ let mut entries = oxc_allocator:: Vec :: new_in ( & alloc) ;
2729+ entries. push ( LiteralMapEntry {
2730+ key : Atom :: from ( "showMenu" ) ,
2731+ value : signal_call,
2732+ quoted : false ,
2733+ } ) ;
2734+
2735+ let obj_literal = OutputExpression :: LiteralMap ( Box :: new_in (
2736+ LiteralMapExpr { entries, source_span : None } ,
2737+ & alloc,
2738+ ) ) ;
2739+
2740+ // Wrap in Parenthesized (this is what convert_oxc_expression produces)
2741+ let parenthesized = OutputExpression :: Parenthesized ( Box :: new_in (
2742+ ParenthesizedExpr { expr : Box :: new_in ( obj_literal, & alloc) , source_span : None } ,
2743+ & alloc,
2744+ ) ) ;
2745+
2746+ let params = oxc_allocator:: Vec :: new_in ( & alloc) ;
2747+
2748+ let expr = OutputExpression :: ArrowFunction ( Box :: new_in (
2749+ ArrowFunctionExpr {
2750+ params,
2751+ body : ArrowFunctionBody :: Expression ( Box :: new_in ( parenthesized, & alloc) ) ,
2752+ source_span : None ,
2753+ } ,
2754+ & alloc,
2755+ ) ) ;
2756+
2757+ let output = emitter. emit_expression ( & expr) ;
2758+ // Even when wrapped in Parenthesized, the object literal body must have parens
2759+ assert ! (
2760+ output. contains( "=>({" ) ,
2761+ "Expected parens around parenthesized object literal, got: {output}"
2762+ ) ;
2763+ assert ! ( output. ends_with( ")" ) , "Expected closing paren, got: {output}" ) ;
2764+ }
26192765}
0 commit comments