From 4da9a31cb35953472420bbe738fa9c386d065a21 Mon Sep 17 00:00:00 2001 From: Shulhi Sapli <913103+shulhi@users.noreply.github.com> Date: Thu, 23 Oct 2025 20:27:10 +0800 Subject: [PATCH 1/8] WIP --- compiler/syntax/src/res_printer.ml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/compiler/syntax/src/res_printer.ml b/compiler/syntax/src/res_printer.ml index 7132e16dd9..de5b04fe5c 100644 --- a/compiler/syntax/src/res_printer.ml +++ b/compiler/syntax/src/res_printer.ml @@ -3223,7 +3223,14 @@ and print_expression ~state (e : Parsetree.expression) cmt_tbl = * b: 2, * }` -> record is written on multiple lines, break the group *) let force_break = - e.pexp_loc.loc_start.pos_lnum < e.pexp_loc.loc_end.pos_lnum + match (spread_expr, rows) with + | Some expr, _ -> + (* If there's a spread, compare with spread expression's location *) + e.pexp_loc.loc_start.pos_lnum < expr.pexp_loc.loc_start.pos_lnum + | None, first_row :: _ -> + (* Otherwise, compare with the first row's location *) + e.pexp_loc.loc_start.pos_lnum < first_row.lid.loc.loc_start.pos_lnum + | None, [] -> false in let punning_allowed = match (spread_expr, rows) with From d50570d6f8cabec4cb178dd56fba517fe7d6ce92 Mon Sep 17 00:00:00 2001 From: Shulhi Sapli <913103+shulhi@users.noreply.github.com> Date: Thu, 23 Oct 2025 21:33:17 +0800 Subject: [PATCH 2/8] Update tests --- .../data/ast-mapping/expected/JSXElements.res.txt | 14 ++------------ .../data/printer/comments/expected/expr.res.txt | 5 +---- .../data/printer/expr/expected/record.res.txt | 12 ++++++++++++ tests/syntax_tests/data/printer/expr/record.res | 8 ++++++++ 4 files changed, 23 insertions(+), 16 deletions(-) diff --git a/tests/syntax_tests/data/ast-mapping/expected/JSXElements.res.txt b/tests/syntax_tests/data/ast-mapping/expected/JSXElements.res.txt index b09e877523..f151681291 100644 --- a/tests/syntax_tests/data/ast-mapping/expected/JSXElements.res.txt +++ b/tests/syntax_tests/data/ast-mapping/expected/JSXElements.res.txt @@ -2,19 +2,9 @@ let emptyUnary = ReactDOM.jsx("input", {}) let emptyNonunary = ReactDOM.jsx("div", {}) -let emptyUnaryWithAttributes = ReactDOM.jsx( - "input", - { - type_: "text", - }, -) +let emptyUnaryWithAttributes = ReactDOM.jsx("input", {type_: "text"}) -let emptyNonunaryWithAttributes = ReactDOM.jsx( - "div", - { - className: "container", - }, -) +let emptyNonunaryWithAttributes = ReactDOM.jsx("div", {className: "container"}) let elementWithChildren = ReactDOM.jsxs( "div", diff --git a/tests/syntax_tests/data/printer/comments/expected/expr.res.txt b/tests/syntax_tests/data/printer/comments/expected/expr.res.txt index 7d4a0b3811..cbe763a3ab 100644 --- a/tests/syntax_tests/data/printer/comments/expected/expr.res.txt +++ b/tests/syntax_tests/data/printer/comments/expected/expr.res.txt @@ -121,10 +121,7 @@ let user = /* before */ { /* c4 */ "age" /* c5 */: /* c6 */ 31 /* c7 */, } // after -let spreadUser = { - /* before */ ...user1 /* after */, - /* c0 */ age /* c1 */: /* c2 */ 32 /* c3 */, -} +let spreadUser = {/* before */ ...user1 /* after */, /* c0 */ age /* c1 */: /* c2 */ 32 /* c3 */} // Pexp_field let x = /* before */ user /* c0 */./* c1 */ name /* c2 */ diff --git a/tests/syntax_tests/data/printer/expr/expected/record.res.txt b/tests/syntax_tests/data/printer/expr/expected/record.res.txt index fb16fe7113..1c81ed5a10 100644 --- a/tests/syntax_tests/data/printer/expr/expected/record.res.txt +++ b/tests/syntax_tests/data/printer/expr/expected/record.res.txt @@ -103,3 +103,15 @@ let optParen = {x: 3, y: ?foo(bar)} let optParen = {x: 3, y: ?(foo->bar)} let optParen = {x: 3, y: ?(() => 3)} let optParen = {x: 3, y: ?-3} + +let x = {a: 2} +let x = { + a: 2, + bbbbbbbbbbbbbbbbbbbbb: 22222222222, + ccccccccccccccccccccc: 22222222222222222, + ddddddddddddddddddddd: 2222222222222, +} +let x = {a: 2} +let x = { + a: 2, +} diff --git a/tests/syntax_tests/data/printer/expr/record.res b/tests/syntax_tests/data/printer/expr/record.res index 82336fc734..3c5c70d48d 100644 --- a/tests/syntax_tests/data/printer/expr/record.res +++ b/tests/syntax_tests/data/printer/expr/record.res @@ -93,3 +93,11 @@ let optParen = { x:3, y: ? (foo(bar)) } let optParen = { x:3, y: ? (foo->bar) } let optParen = { x:3, y: ? (()=>3) } let optParen = { x:3, y: ? (-3) } + +let x = {a: 2} +let x = {a: 2, bbbbbbbbbbbbbbbbbbbbb: 22222222222, ccccccccccccccccccccc: 22222222222222222, ddddddddddddddddddddd: 2222222222222} +let x = {a: 2 +} +let x = { + a: 2 +} From a22a0a92e5ed0d7c84c5bf438d02cbac0fbd57c9 Mon Sep 17 00:00:00 2001 From: Shulhi Sapli <913103+shulhi@users.noreply.github.com> Date: Fri, 24 Oct 2025 15:51:00 +0800 Subject: [PATCH 3/8] Change how force_break is handled in type declaration printing --- compiler/syntax/src/res_printer.ml | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/compiler/syntax/src/res_printer.ml b/compiler/syntax/src/res_printer.ml index de5b04fe5c..20fb8869cc 100644 --- a/compiler/syntax/src/res_printer.ml +++ b/compiler/syntax/src/res_printer.ml @@ -1282,7 +1282,7 @@ and print_type_declaration ~state ~name ~equal_sign ~rec_flag i manifest; Doc.concat [Doc.space; Doc.text equal_sign; Doc.space]; print_private_flag td.ptype_private; - print_record_declaration ~state lds cmt_tbl; + print_record_declaration ~record_loc:td.ptype_loc ~state lds cmt_tbl; ] | Ptype_variant cds -> let manifest = @@ -1370,8 +1370,8 @@ and print_type_declaration2 ?inline_record_definitions ~state ~rec_flag manifest; Doc.concat [Doc.space; Doc.text equal_sign; Doc.space]; print_private_flag td.ptype_private; - print_record_declaration ?inline_record_definitions ~state lds - cmt_tbl; + print_record_declaration ?inline_record_definitions + ~record_loc:td.ptype_loc ~state lds cmt_tbl; ] | Ptype_variant cds -> let manifest = @@ -1465,11 +1465,15 @@ and print_type_param ~state (param : Parsetree.core_type * Asttypes.variance) Doc.concat [printed_variance; print_typ_expr ~state typ cmt_tbl] and print_record_declaration ?check_break_from_loc ?inline_record_definitions - ~state (lds : Parsetree.label_declaration list) cmt_tbl = + ?record_loc ~state (lds : Parsetree.label_declaration list) cmt_tbl = let force_break = - match (check_break_from_loc, lds, List.rev lds) with + match (check_break_from_loc, record_loc, lds) with | Some loc, _, _ -> loc.Location.loc_start.pos_lnum < loc.loc_end.pos_lnum - | _, first :: _, last :: _ -> + | None, Some loc, first :: _ -> + (* Check if first field is on a different line than the opening brace *) + loc.loc_start.pos_lnum < first.Parsetree.pld_loc.loc_start.pos_lnum + | None, None, first :: _ -> + let last = List.hd (List.rev lds) in first.pld_loc.loc_start.pos_lnum < last.pld_loc.loc_end.pos_lnum | _, _, _ -> false in From 2b42d0068c28139b6e6123d8171f8d97ff4d5dc7 Mon Sep 17 00:00:00 2001 From: Shulhi Sapli <913103+shulhi@users.noreply.github.com> Date: Fri, 24 Oct 2025 16:16:41 +0800 Subject: [PATCH 4/8] Fix type declaration with spread --- compiler/syntax/src/res_printer.ml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/compiler/syntax/src/res_printer.ml b/compiler/syntax/src/res_printer.ml index 20fb8869cc..45d4e51efa 100644 --- a/compiler/syntax/src/res_printer.ml +++ b/compiler/syntax/src/res_printer.ml @@ -1466,15 +1466,21 @@ and print_type_param ~state (param : Parsetree.core_type * Asttypes.variance) and print_record_declaration ?check_break_from_loc ?inline_record_definitions ?record_loc ~state (lds : Parsetree.label_declaration list) cmt_tbl = + let get_field_start_line (ld : Parsetree.label_declaration) = + (* For spread fields (...), use the type location instead of pld_loc + because pld_loc may incorrectly include preceding whitespace *) + if ld.pld_name.txt = "..." then ld.pld_type.ptyp_loc.loc_start.pos_lnum + else ld.pld_loc.loc_start.pos_lnum + in let force_break = match (check_break_from_loc, record_loc, lds) with | Some loc, _, _ -> loc.Location.loc_start.pos_lnum < loc.loc_end.pos_lnum | None, Some loc, first :: _ -> (* Check if first field is on a different line than the opening brace *) - loc.loc_start.pos_lnum < first.Parsetree.pld_loc.loc_start.pos_lnum + loc.loc_start.pos_lnum < get_field_start_line first | None, None, first :: _ -> let last = List.hd (List.rev lds) in - first.pld_loc.loc_start.pos_lnum < last.pld_loc.loc_end.pos_lnum + get_field_start_line first < last.pld_loc.loc_end.pos_lnum | _, _, _ -> false in Doc.breakable_group ~force_break From ad321b1e69654dd97810d73535781ed89f496dc9 Mon Sep 17 00:00:00 2001 From: Shulhi Sapli <913103+shulhi@users.noreply.github.com> Date: Sat, 25 Oct 2025 19:58:43 +0800 Subject: [PATCH 5/8] Update tests --- .../data/printer/expr/expected/record.res.txt | 17 +++++++++++++++++ tests/syntax_tests/data/printer/expr/record.res | 11 +++++++++++ 2 files changed, 28 insertions(+) diff --git a/tests/syntax_tests/data/printer/expr/expected/record.res.txt b/tests/syntax_tests/data/printer/expr/expected/record.res.txt index 1c81ed5a10..d33ef76db5 100644 --- a/tests/syntax_tests/data/printer/expr/expected/record.res.txt +++ b/tests/syntax_tests/data/printer/expr/expected/record.res.txt @@ -97,6 +97,23 @@ type tt = {x: int, y?: string} type ttt = {x: int, y?: string} +type x = {a: int} + +type x = { + aaaaaaaaaaaa: int, + bbbbbbbbbbbb: int, + cccccccccccc: int, + dddddddddddd: int, + eeeeeeeeeeee: int, + ffffffffffffffff: int, +} + +type y = {a: int} + +type z = { + a: int, +} + let optParen = {x: 3, y: ?(someBool ? Some("") : None)} let optParen = {x: 3, y: ?(3 + 4)} let optParen = {x: 3, y: ?foo(bar)} diff --git a/tests/syntax_tests/data/printer/expr/record.res b/tests/syntax_tests/data/printer/expr/record.res index 3c5c70d48d..6e3e36db02 100644 --- a/tests/syntax_tests/data/printer/expr/record.res +++ b/tests/syntax_tests/data/printer/expr/record.res @@ -87,6 +87,17 @@ type tt = {x:int, y?: string} type ttt = {x:int, y?: string} +type x = {a: int} + +type x = {aaaaaaaaaaaa: int, bbbbbbbbbbbb: int, cccccccccccc: int, dddddddddddd: int, eeeeeeeeeeee: int, ffffffffffffffff: int} + +type y = {a: int +} + +type z = { + a: int, +} + let optParen = { x:3, y: ? (someBool ? Some("") : None) } let optParen = { x:3, y: ? (3+4) } let optParen = { x:3, y: ? (foo(bar)) } From 80ad971c7c5a69bc8790c00617b3ba5c35911563 Mon Sep 17 00:00:00 2001 From: Shulhi Sapli <913103+shulhi@users.noreply.github.com> Date: Sat, 25 Oct 2025 19:59:41 +0800 Subject: [PATCH 6/8] Update more tests (ppx) --- .../ppx/react/expected/aliasProps.res.txt | 35 +++++++++++++++---- .../ppx/react/expected/asyncAwait.res.txt | 8 +++-- .../ppx/react/expected/commentAtTop.res.txt | 4 ++- .../react/expected/defaultValueProp.res.txt | 18 +++++++--- .../expected/externalWithCustomName.res.txt | 5 ++- .../react/expected/fileLevelConfig.res.txt | 4 ++- .../ppx/react/expected/forwardRef.resi.txt | 16 ++++++--- .../data/ppx/react/expected/interface.res.txt | 4 ++- .../ppx/react/expected/interface.resi.txt | 4 ++- .../react/expected/interfaceWithRef.res.txt | 6 +++- .../react/expected/interfaceWithRef.resi.txt | 6 +++- .../ppx/react/expected/mangleKeyword.res.txt | 10 ++++-- .../data/ppx/react/expected/newtype.res.txt | 29 +++++++++++---- .../expected/optimizeAutomaticMode.res.txt | 4 ++- .../data/ppx/react/expected/topLevel.res.txt | 5 ++- .../ppx/react/expected/typeConstraint.res.txt | 5 ++- .../ppx/react/expected/uncurriedProps.res.txt | 8 +++-- .../data/ppx/react/expected/v4.res.txt | 25 +++++++++---- .../printer/comments/expected/typexpr.res.txt | 4 ++- .../printer/typexpr/expected/bsObject.res.txt | 4 ++- 20 files changed, 159 insertions(+), 45 deletions(-) diff --git a/tests/syntax_tests/data/ppx/react/expected/aliasProps.res.txt b/tests/syntax_tests/data/ppx/react/expected/aliasProps.res.txt index efb6c9b53d..f942516aff 100644 --- a/tests/syntax_tests/data/ppx/react/expected/aliasProps.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/aliasProps.res.txt @@ -2,7 +2,10 @@ module C0 = { @res.jsxComponentProps - type props<'priority, 'text> = {priority: 'priority, text?: 'text} + type props<'priority, 'text> = { + priority: 'priority, + text?: 'text, + } let make = ({priority: _, text: ?__text, _}: props<_, _>) => { let text = switch __text { @@ -20,7 +23,10 @@ module C0 = { module C1 = { @res.jsxComponentProps - type props<'priority, 'text> = {priority: 'priority, text?: 'text} + type props<'priority, 'text> = { + priority: 'priority, + text?: 'text, + } let make = ({priority: p, text: ?__text, _}: props<_, _>) => { let text = switch __text { @@ -38,7 +44,9 @@ module C1 = { module C2 = { @res.jsxComponentProps - type props<'foo> = {foo?: 'foo} + type props<'foo> = { + foo?: 'foo, + } let make = ({foo: ?__bar, _}: props<_>) => { let bar = switch __bar { @@ -56,7 +64,11 @@ module C2 = { module C3 = { @res.jsxComponentProps - type props<'foo, 'a, 'b> = {foo?: 'foo, a?: 'a, b: 'b} + type props<'foo, 'a, 'b> = { + foo?: 'foo, + a?: 'a, + b: 'b, + } let make = ({foo: ?__bar, a: ?__a, b, _}: props<_, _, _>) => { let bar = switch __bar { @@ -82,7 +94,10 @@ module C3 = { module C4 = { @res.jsxComponentProps - type props<'a, 'x> = {a: 'a, x?: 'x} + type props<'a, 'x> = { + a: 'a, + x?: 'x, + } let make = ({a: b, x: ?__x, _}: props<_, _>) => { let x = switch __x { @@ -100,7 +115,10 @@ module C4 = { module C5 = { @res.jsxComponentProps - type props<'a, 'z> = {a: 'a, z?: 'z} + type props<'a, 'z> = { + a: 'a, + z?: 'z, + } let make = ({a: (x, y), z: ?__z, _}: props<_, _>) => { let z = switch __z { @@ -124,7 +142,10 @@ module C6 = { let make: React.component } @res.jsxComponentProps - type props<'comp, 'x> = {comp: 'comp, x: 'x} + type props<'comp, 'x> = { + comp: 'comp, + x: 'x, + } let make = ({comp: module(Comp: Comp), x: (a, b), _}: props<_, _>): React.element => React.jsx(Comp.make, {}) diff --git a/tests/syntax_tests/data/ppx/react/expected/asyncAwait.res.txt b/tests/syntax_tests/data/ppx/react/expected/asyncAwait.res.txt index ba012e9dee..d77b11decd 100644 --- a/tests/syntax_tests/data/ppx/react/expected/asyncAwait.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/asyncAwait.res.txt @@ -2,7 +2,9 @@ let f = a => Js.Promise.resolve(a + a) module C0 = { @res.jsxComponentProps - type props<'a> = {a: 'a} + type props<'a> = { + a: 'a, + } let make = async ({a, _}: props<_>) => { let a = await f(a) @@ -17,7 +19,9 @@ module C0 = { module C1 = { @res.jsxComponentProps - type props<'status> = {status: 'status} + type props<'status> = { + status: 'status, + } let make = async ({status, _}: props<_>): React.element => { switch status { diff --git a/tests/syntax_tests/data/ppx/react/expected/commentAtTop.res.txt b/tests/syntax_tests/data/ppx/react/expected/commentAtTop.res.txt index 1ddf0492f9..e8242d70cb 100644 --- a/tests/syntax_tests/data/ppx/react/expected/commentAtTop.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/commentAtTop.res.txt @@ -1,5 +1,7 @@ @res.jsxComponentProps -type props<'msg> = {msg: 'msg} // test React JSX file +type props<'msg> = { + msg: 'msg, // test React JSX file +} let make = ({msg, _}: props<_>): React.element => { ReactDOM.jsx("div", {children: ?ReactDOM.someElement({msg->React.string})}) diff --git a/tests/syntax_tests/data/ppx/react/expected/defaultValueProp.res.txt b/tests/syntax_tests/data/ppx/react/expected/defaultValueProp.res.txt index 29680945ce..22f854f911 100644 --- a/tests/syntax_tests/data/ppx/react/expected/defaultValueProp.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/defaultValueProp.res.txt @@ -1,6 +1,9 @@ module C0 = { @res.jsxComponentProps - type props<'a, 'b> = {a?: 'a, b?: 'b} + type props<'a, 'b> = { + a?: 'a, + b?: 'b, + } let make = ({a: ?__a, b: ?__b, _}: props<_, _>) => { let a = switch __a { | Some(a) => a @@ -20,7 +23,10 @@ module C0 = { module C1 = { @res.jsxComponentProps - type props<'a, 'b> = {a?: 'a, b: 'b} + type props<'a, 'b> = { + a?: 'a, + b: 'b, + } let make = ({a: ?__a, b, _}: props<_, _>) => { let a = switch __a { @@ -39,7 +45,9 @@ module C1 = { module C2 = { let a = "foo" @res.jsxComponentProps - type props<'a> = {a?: 'a} + type props<'a> = { + a?: 'a, + } let make = ({a: ?__a, _}: props<_>) => { let a = switch __a { @@ -57,7 +65,9 @@ module C2 = { module C3 = { @res.jsxComponentProps - type props<'disabled> = {disabled?: 'disabled} + type props<'disabled> = { + disabled?: 'disabled, + } let make = ({disabled: ?__everythingDisabled, _}: props) => { let everythingDisabled = switch __everythingDisabled { diff --git a/tests/syntax_tests/data/ppx/react/expected/externalWithCustomName.res.txt b/tests/syntax_tests/data/ppx/react/expected/externalWithCustomName.res.txt index 9bfbac700a..dd98fc72ff 100644 --- a/tests/syntax_tests/data/ppx/react/expected/externalWithCustomName.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/externalWithCustomName.res.txt @@ -2,7 +2,10 @@ module Foo = { @res.jsxComponentProps @live - type props<'a, 'b> = {a: 'a, b: 'b} + type props<'a, 'b> = { + a: 'a, + b: 'b, + } @module("Foo") external component: React.component> = "component" diff --git a/tests/syntax_tests/data/ppx/react/expected/fileLevelConfig.res.txt b/tests/syntax_tests/data/ppx/react/expected/fileLevelConfig.res.txt index 177190cbe8..77cde0caea 100644 --- a/tests/syntax_tests/data/ppx/react/expected/fileLevelConfig.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/fileLevelConfig.res.txt @@ -2,7 +2,9 @@ module V4A = { @res.jsxComponentProps - type props<'msg> = {msg: 'msg} + type props<'msg> = { + msg: 'msg, + } let make = ({msg, _}: props<_>): React.element => { ReactDOM.jsx("div", {children: ?ReactDOM.someElement({msg->React.string})}) diff --git a/tests/syntax_tests/data/ppx/react/expected/forwardRef.resi.txt b/tests/syntax_tests/data/ppx/react/expected/forwardRef.resi.txt index 82ba473328..47cd6f1010 100644 --- a/tests/syntax_tests/data/ppx/react/expected/forwardRef.resi.txt +++ b/tests/syntax_tests/data/ppx/react/expected/forwardRef.resi.txt @@ -14,7 +14,9 @@ module V4C: { module ForwardRef: { @res.jsxComponentProps - type props<'ref> = {ref?: 'ref} + type props<'ref> = { + ref?: 'ref, + } let make: React.component>>> } @@ -34,7 +36,9 @@ module V4CUncurried: { module ForwardRef: { @res.jsxComponentProps - type props<'ref> = {ref?: 'ref} + type props<'ref> = { + ref?: 'ref, + } let make: React.component>>> } @@ -56,7 +60,9 @@ module V4A: { module ForwardRef: { @res.jsxComponentProps - type props<'ref> = {ref?: 'ref} + type props<'ref> = { + ref?: 'ref, + } let make: React.component>>> } @@ -76,7 +82,9 @@ module V4AUncurried: { module ForwardRef: { @res.jsxComponentProps - type props<'ref> = {ref?: 'ref} + type props<'ref> = { + ref?: 'ref, + } let make: React.component>>> } diff --git a/tests/syntax_tests/data/ppx/react/expected/interface.res.txt b/tests/syntax_tests/data/ppx/react/expected/interface.res.txt index e28bfe5472..df0f7137fb 100644 --- a/tests/syntax_tests/data/ppx/react/expected/interface.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/interface.res.txt @@ -1,6 +1,8 @@ module A = { @res.jsxComponentProps - type props<'x> = {x: 'x} + type props<'x> = { + x: 'x, + } let make = ({x, _}: props<_>): React.element => React.string(x) let make = { let \"Interface$A" = (props: props<_>) => make(props) diff --git a/tests/syntax_tests/data/ppx/react/expected/interface.resi.txt b/tests/syntax_tests/data/ppx/react/expected/interface.resi.txt index 633ffa3c00..d0cc914abf 100644 --- a/tests/syntax_tests/data/ppx/react/expected/interface.resi.txt +++ b/tests/syntax_tests/data/ppx/react/expected/interface.resi.txt @@ -1,6 +1,8 @@ module A: { @res.jsxComponentProps - type props<'x> = {x: 'x} + type props<'x> = { + x: 'x, + } let make: React.component> } diff --git a/tests/syntax_tests/data/ppx/react/expected/interfaceWithRef.res.txt b/tests/syntax_tests/data/ppx/react/expected/interfaceWithRef.res.txt index 7dd865bc9c..e0d0cbb87c 100644 --- a/tests/syntax_tests/data/ppx/react/expected/interfaceWithRef.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/interfaceWithRef.res.txt @@ -1,4 +1,8 @@ -@res.jsxComponentProps type props<'x, 'ref> = {x: 'x, ref?: 'ref} +@res.jsxComponentProps +type props<'x, 'ref> = { + x: 'x, + ref?: 'ref, +} let make = ( {x, _}: props, ref: Js.Nullable.t, diff --git a/tests/syntax_tests/data/ppx/react/expected/interfaceWithRef.resi.txt b/tests/syntax_tests/data/ppx/react/expected/interfaceWithRef.resi.txt index 7f1895b91c..462fa9d640 100644 --- a/tests/syntax_tests/data/ppx/react/expected/interfaceWithRef.resi.txt +++ b/tests/syntax_tests/data/ppx/react/expected/interfaceWithRef.resi.txt @@ -1,2 +1,6 @@ -@res.jsxComponentProps type props<'x, 'ref> = {x: 'x, ref?: 'ref} +@res.jsxComponentProps +type props<'x, 'ref> = { + x: 'x, + ref?: 'ref, +} let make: React.component> diff --git a/tests/syntax_tests/data/ppx/react/expected/mangleKeyword.res.txt b/tests/syntax_tests/data/ppx/react/expected/mangleKeyword.res.txt index faee40442f..f1e40360ec 100644 --- a/tests/syntax_tests/data/ppx/react/expected/mangleKeyword.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/mangleKeyword.res.txt @@ -2,7 +2,10 @@ module C4A0 = { @res.jsxComponentProps - type props<'T_open, 'T_type> = {@as("open") _open: 'T_open, @as("type") _type: 'T_type} + type props<'T_open, 'T_type> = { + @as("open") _open: 'T_open, + @as("type") _type: 'T_type, + } let make = ({@as("open") _open, @as("type") _type, _}: props<_, string>): React.element => React.string(_open) @@ -14,7 +17,10 @@ module C4A0 = { } module C4A1 = { @res.jsxComponentProps @live - type props<'T_open, 'T_type> = {@as("open") _open: 'T_open, @as("type") _type: 'T_type} + type props<'T_open, 'T_type> = { + @as("open") _open: 'T_open, + @as("type") _type: 'T_type, + } external make: React.component> = "default" } diff --git a/tests/syntax_tests/data/ppx/react/expected/newtype.res.txt b/tests/syntax_tests/data/ppx/react/expected/newtype.res.txt index f26904a16b..2fdecb8674 100644 --- a/tests/syntax_tests/data/ppx/react/expected/newtype.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/newtype.res.txt @@ -2,7 +2,11 @@ module V4A = { @res.jsxComponentProps - type props<'a, 'b, 'c> = {a: 'a, b: 'b, c: 'c} + type props<'a, 'b, 'c> = { + a: 'a, + b: 'b, + c: 'c, + } let make = (type a, {a, b, c, _}: props>, 'a>): React.element => ReactDOM.jsx("div", {}) @@ -15,7 +19,11 @@ module V4A = { module V4A1 = { @res.jsxComponentProps - type props<'a, 'b, 'c> = {a: 'a, b: 'b, c: 'c} + type props<'a, 'b, 'c> = { + a: 'a, + b: 'b, + c: 'c, + } let make = (type x y, {a, b, c, _}: props, 'a>): React.element => ReactDOM.jsx("div", {}) @@ -32,7 +40,9 @@ module type T = { module V4A2 = { @res.jsxComponentProps - type props<'foo> = {foo: 'foo} + type props<'foo> = { + foo: 'foo, + } let make = (type a, {foo: (foo: module(T with type t = a)), _}: props<_>): React.element => { module T = unpack(foo) @@ -47,7 +57,9 @@ module V4A2 = { module V4A3 = { @res.jsxComponentProps - type props<'foo> = {foo: 'foo} + type props<'foo> = { + foo: 'foo, + } let make = (type a, {foo, _}: props<_>): React.element => { module T = unpack(foo: T with type t = a) @@ -60,7 +72,10 @@ module V4A3 = { } } @res.jsxComponentProps -type props<'x, 'q> = {x: 'x, q: 'q} +type props<'x, 'q> = { + x: 'x, + q: 'q, +} let make = ({x, q, _}: props<('a, 'b), 'a>): React.element => [fst(x), q] let make = { @@ -73,7 +88,9 @@ let make = { module Uncurried = { @res.jsxComponentProps - type props<'foo> = {foo?: 'foo} + type props<'foo> = { + foo?: 'foo, + } let make = (type a, {?foo, _}: props<_>): React.element => React.null let make = { diff --git a/tests/syntax_tests/data/ppx/react/expected/optimizeAutomaticMode.res.txt b/tests/syntax_tests/data/ppx/react/expected/optimizeAutomaticMode.res.txt index 690eb73eba..dfb3506590 100644 --- a/tests/syntax_tests/data/ppx/react/expected/optimizeAutomaticMode.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/optimizeAutomaticMode.res.txt @@ -5,7 +5,9 @@ module User = { let format = user => "Dr." ++ user.lastName @res.jsxComponentProps - type props<'doctor> = {doctor: 'doctor} + type props<'doctor> = { + doctor: 'doctor, + } let make = ({doctor, _}: props<_>): React.element => { ReactDOM.jsx("h1", {id: "h1", children: ?ReactDOM.someElement({React.string(format(doctor))})}) diff --git a/tests/syntax_tests/data/ppx/react/expected/topLevel.res.txt b/tests/syntax_tests/data/ppx/react/expected/topLevel.res.txt index 2d8fe42ab1..9330346a96 100644 --- a/tests/syntax_tests/data/ppx/react/expected/topLevel.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/topLevel.res.txt @@ -2,7 +2,10 @@ module V4A = { @res.jsxComponentProps - type props<'a, 'b> = {a: 'a, b: 'b} + type props<'a, 'b> = { + a: 'a, + b: 'b, + } let make = ({a, b, _}: props<_, _>) => { Js.log("This function should be named 'TopLevel.react'") diff --git a/tests/syntax_tests/data/ppx/react/expected/typeConstraint.res.txt b/tests/syntax_tests/data/ppx/react/expected/typeConstraint.res.txt index dbec7ea6a0..68a3b0278b 100644 --- a/tests/syntax_tests/data/ppx/react/expected/typeConstraint.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/typeConstraint.res.txt @@ -2,7 +2,10 @@ module V4A = { @res.jsxComponentProps - type props<'a, 'b> = {a: 'a, b: 'b} + type props<'a, 'b> = { + a: 'a, + b: 'b, + } let make = (type a, {a, b, _}: props<_, _>): React.element => ReactDOM.jsx("div", {}) let make = { diff --git a/tests/syntax_tests/data/ppx/react/expected/uncurriedProps.res.txt b/tests/syntax_tests/data/ppx/react/expected/uncurriedProps.res.txt index cbb9dd4494..c029c8c784 100644 --- a/tests/syntax_tests/data/ppx/react/expected/uncurriedProps.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/uncurriedProps.res.txt @@ -1,6 +1,8 @@ @@jsxConfig({version: 4}) @res.jsxComponentProps -type props<'a> = {a?: 'a} +type props<'a> = { + a?: 'a, +} let make = ({a: ?__a, _}: props unit>) => { let a = switch __a { @@ -27,7 +29,9 @@ func(~callback=(str, a, b) => { module Foo = { @res.jsxComponentProps - type props<'callback> = {callback?: 'callback} + type props<'callback> = { + callback?: 'callback, + } let make = ({callback: ?__callback, _}: props<(string, bool, bool) => unit>) => { let callback = switch __callback { diff --git a/tests/syntax_tests/data/ppx/react/expected/v4.res.txt b/tests/syntax_tests/data/ppx/react/expected/v4.res.txt index e5f9b37a7a..a6f05b0e17 100644 --- a/tests/syntax_tests/data/ppx/react/expected/v4.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/v4.res.txt @@ -1,5 +1,8 @@ @res.jsxComponentProps -type props<'x, 'y> = {x: 'x, y: 'y} // Component with type constraint +type props<'x, 'y> = { + x: 'x, // Component with type constraint + y: 'y, +} let make = ({x, y, _}: props): React.element => React.string(x ++ y) let make = { let \"V4" = (props: props<_>) => make(props) @@ -9,7 +12,9 @@ let make = { module AnotherName = { @res.jsxComponentProps type // Component with another name than "make" - props<'x> = {x: 'x} + props<'x> = { + x: 'x, + } let anotherName = ({x, _}: props<_>): React.element => React.string(x) let anotherName = { @@ -21,7 +26,9 @@ module AnotherName = { module Uncurried = { @res.jsxComponentProps - type props<'x> = {x: 'x} + type props<'x> = { + x: 'x, + } let make = ({x, _}: props<_>): React.element => React.string(x) let make = { @@ -33,21 +40,27 @@ module Uncurried = { module type TUncurried = { @res.jsxComponentProps - type props<'x> = {x: 'x} + type props<'x> = { + x: 'x, + } let make: React.component> } module E = { @res.jsxComponentProps @live - type props<'x> = {x: 'x} + type props<'x> = { + x: 'x, + } external make: React.component> = "default" } module EUncurried = { @res.jsxComponentProps @live - type props<'x> = {x: 'x} + type props<'x> = { + x: 'x, + } external make: React.component> = "default" } diff --git a/tests/syntax_tests/data/printer/comments/expected/typexpr.res.txt b/tests/syntax_tests/data/printer/comments/expected/typexpr.res.txt index e3a39acf24..a0e5607359 100644 --- a/tests/syntax_tests/data/printer/comments/expected/typexpr.res.txt +++ b/tests/syntax_tests/data/printer/comments/expected/typexpr.res.txt @@ -26,7 +26,9 @@ type t = /* c0 */ module(/* c1 */ Hashmap /* c2 */ with type t = /* c0 */ string /* c1 */ as 'x // after // Ptyp_poly -type fn = {f: /* c0 */ 'a /* c1 */ 'b /* c2 */. /* c3 */ string /* c4 */} +type fn = { + f: /* c0 */ 'a /* c1 */ 'b /* c2 */. /* c3 */ string /* c4 */, +} type fn = { /* comment1 */ diff --git a/tests/syntax_tests/data/printer/typexpr/expected/bsObject.res.txt b/tests/syntax_tests/data/printer/typexpr/expected/bsObject.res.txt index e7c3c0092f..f854d53857 100644 --- a/tests/syntax_tests/data/printer/typexpr/expected/bsObject.res.txt +++ b/tests/syntax_tests/data/printer/typexpr/expected/bsObject.res.txt @@ -53,4 +53,6 @@ type t = {.} type t = private {.} type t = constr<{.}, {.}, {.}> -type t = {hr: React.component<{.}>} +type t = { + hr: React.component<{.}>, +} From 57aa27b206c892da38bb7c33e1d3cc68eb22efb3 Mon Sep 17 00:00:00 2001 From: Shulhi Sapli <913103+shulhi@users.noreply.github.com> Date: Sun, 26 Oct 2025 19:47:00 +0800 Subject: [PATCH 7/8] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bc54aba4f..5acdb3ed8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ - Rewatch: plain output when not running in tty. https://github.com/rescript-lang/rescript/pull/7970 - Streamline rewatch help texts. https://github.com/rescript-lang/rescript/pull/7973 - Rewatch: Reduced build progress output from 7 steps to 3 for cleaner, less verbose logging. https://github.com/rescript-lang/rescript/pull/7971 +- Formatter: Improve multiline printing of record types and values. https://github.com/rescript-lang/rescript/pull/7993 #### :house: Internal From ac43816b613dff5f64fcf3007e5818a0aa696792 Mon Sep 17 00:00:00 2001 From: Florian Verdonck Date: Sun, 9 Nov 2025 05:15:56 +0100 Subject: [PATCH 8/8] Add note on the formatter philosophy (#2) --- docs/Formatter.md | 48 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 docs/Formatter.md diff --git a/docs/Formatter.md b/docs/Formatter.md new file mode 100644 index 0000000000..a157b52a42 --- /dev/null +++ b/docs/Formatter.md @@ -0,0 +1,48 @@ +# ReScript Formatter + +## Philosophy + +The ReScript formatter is **opinionated**. Formatting decisions are made by the core team based on our collective judgment and vision for the language. We do not aim to accommodate every stylistic preference or engage in extended debates about formatting choices. + +The formatter currently has **no configuration settings**, and we aspire to keep it that way. This ensures that ReScript code looks consistent across all projects and teams, eliminating style debates and configuration overhead. + +## Decision Making + +- **Core team consensus is final**: When the core team reaches consensus on a formatting decision, that decision stands. There is no requirement for community-wide agreement or extensive discussion. + +- **Community input is welcome but not binding**: We appreciate suggestions and feedback from the community, but these can be closed without extensive justification if the core team is not aligned with the proposal. + +- **No endless style discussions**: We are not interested in protracted debates about formatting preferences. The formatter exists to provide consistent, automated formatting—not to serve as a platform for style negotiations. + +## Prior Decisions + +The following are examples of formatting decisions the core team has made. This list is not exhaustive, and these decisions do not create binding precedents for future discussions. The core team retains full discretion to make different decisions in similar cases. + +- **Smart linebreaks for pipe chains**: The formatter preserves user-introduced linebreaks in pipe chains (`->`), allowing users to control multiline formatting. See [forum announcement](https://forum.rescript-lang.org/t/ann-smart-linebreaks-for-pipe-chains/4734). + +- **Preserve multilineness for records**: The formatter preserves multiline formatting for record types and values when users introduce linebreaks. See [issue #7961](https://github.com/rescript-lang/rescript/issues/7961). + +**Important**: These examples are provided for reference only. They do not establish rules or precedents that constrain future formatting decisions. The core team may choose different approaches in similar situations based on current consensus. + +## Guidelines for Contributors + +### Submitting Formatting Issues + +- You may open issues to report bugs or propose improvements +- Understand that proposals may be closed if they don't align with core team vision +- Avoid reopening closed issues unless there's new technical information +- Respect that "the core team isn't feeling it" is a valid reason for closure + +### What We Consider + +- Technical correctness and consistency +- Alignment with ReScript's design philosophy +- Maintainability and simplicity of the formatter implementation +- Core team consensus + +### What We Generally Avoid + +- Style preferences that don't align with our vision +- Using comparisons to other formatters as the sole justification for changes (while we may align with other formatters on many decisions, we make choices based on our own judgment, not because another formatter does it) +- Requests that would significantly complicate the formatter implementation +- Debates about subjective formatting choices