From 701313d0e0a438c6853483de0577511647775182 Mon Sep 17 00:00:00 2001 From: tsnobip Date: Tue, 22 Sep 2026 13:30:22 +0200 Subject: [PATCH 1/4] format JSX expression children with explicit braces --- CHANGELOG.md | 1 + compiler/syntax/src/res_comments_table.ml | 20 ++- compiler/syntax/src/res_parens.ml | 32 +--- compiler/syntax/src/res_parens.mli | 3 + compiler/syntax/src/res_printer.ml | 35 ++++- packages/dev-playground/src/Main.res | 42 +++--- .../dev-playground/src/SourceHighlight.res | 2 +- ...jsx_custom_component_children.res.expected | 4 +- .../jsx_maybe_missing_fragment.res.expected | 6 +- ...x_type_mismatch_array_element.res.expected | 4 +- .../jsx_type_mismatch_array_raw.res.expected | 4 +- .../jsx_type_mismatch_float.res.expected | 4 +- .../jsx_type_mismatch_int.res.expected | 4 +- .../jsx_type_mismatch_option.res.expected | 4 +- .../jsx_type_mismatch_string.res.expected | 4 +- .../missing_required_prop.res.expected | 4 +- ...g_required_prop_when_children.res.expected | 6 +- ...quired_prop_when_single_child.res.expected | 4 +- .../react_component_with_props.res.expected | 2 +- .../jsx_custom_component_children.res | 4 +- .../fixtures/jsx_maybe_missing_fragment.res | 4 +- .../fixtures/jsx_plain_function_component.res | 2 +- .../jsx_type_mismatch_array_element.res | 2 +- .../fixtures/jsx_type_mismatch_array_raw.res | 2 +- .../fixtures/jsx_type_mismatch_float.res | 2 +- .../fixtures/jsx_type_mismatch_int.res | 2 +- .../fixtures/jsx_type_mismatch_option.res | 2 +- .../fixtures/jsx_type_mismatch_string.res | 2 +- .../fixtures/missing_required_prop.res | 4 +- .../missing_required_prop_when_children.res | 6 +- ...issing_required_prop_when_single_child.res | 4 +- .../fixtures/react_component_with_props.res | 2 +- .../typescript-react-example/src/Hooks.res | 21 ++- .../reason/expected/bracedJsx.res.txt | 2 +- .../conversion/reason/expected/braces.res.txt | 2 +- .../reason/expected/jsxProps.res.txt | 2 +- .../conversion/reason/expected/string.res.txt | 2 +- .../printer/comments/expected/jsx.res.txt | 2 +- .../printer/expr/expected/asyncAwait.res.txt | 2 +- .../data/printer/expr/expected/braced.res.txt | 6 +- .../printer/expr/expected/callback.res.txt | 4 +- .../data/printer/expr/expected/coerce.res.txt | 2 +- .../printer/expr/expected/exoticIdent.res.txt | 4 +- .../data/printer/expr/expected/jsx.res.txt | 66 ++++----- .../printer/expr/expected/jsxChildren.res.txt | 138 ++++++++++++++++++ .../data/printer/expr/expected/switch.res.txt | 8 +- .../expr/expected/underscoreApply.res.txt | 2 +- .../data/printer/expr/jsxChildren.res | 74 ++++++++++ .../printer/other/expected/fatSlider.res.txt | 2 +- .../data/printer/other/expected/home.res.txt | 4 +- .../other/expected/signaturePicker.res.txt | 2 +- tests/syntax_tests/res_test.ml | 68 +++++++++ tests/tests/src/async_jsx.res | 2 +- tests/tests/src/jsx_preserve_test.res | 20 +-- .../FormatDocstringsTest2.res.expected | 4 +- ...ocstringsTest2.res.extracted.json.expected | 2 +- 56 files changed, 479 insertions(+), 185 deletions(-) create mode 100644 tests/syntax_tests/data/printer/expr/expected/jsxChildren.res.txt create mode 100644 tests/syntax_tests/data/printer/expr/jsxChildren.res diff --git a/CHANGELOG.md b/CHANGELOG.md index d444142eb85..f3d68417dc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ #### :nail_care: Polish - Avoid running `rescript-schema-ppx` and `sury-ppx` on source files without an `@schema` annotation. https://github.com/rescript-lang/rescript/pull/8662 +- Format JSX expression children with explicit braces and no surrounding inline spaces, preparing for future literal text support. Existing unbraced children remain valid syntax. https://github.com/rescript-lang/rescript/pull/8666 #### :house: Internal diff --git a/compiler/syntax/src/res_comments_table.ml b/compiler/syntax/src/res_comments_table.ml index c60c3a76021..3b94a4d4ce7 100644 --- a/compiler/syntax/src/res_comments_table.ml +++ b/compiler/syntax/src/res_comments_table.ml @@ -1604,7 +1604,14 @@ and walk_expression expr t comments = jsx_fragment_closing = _closing_lesser_than; }) -> let opening_token = {expr.pexp_loc with loc_end = opening_greater_than} in - let on_same_line, rest = partition_by_on_same_line opening_token comments in + let on_same_line, rest = + match children with + | child :: _ -> + partition_adjacent_trailing_before_next_token_on_same_line opening_token + (get_loc (Expression child)) + comments + | [] -> partition_by_on_same_line opening_token comments + in attach t.trailing opening_token on_same_line; let xs = children |> List.map (fun e -> Expression e) in walk_list xs t rest @@ -1696,9 +1703,16 @@ and walk_expression expr t comments = rest in - (* comments after '>' on the same line should be attached to '>' *) + (* Only comments before the first child belong to '>'; comments inside a + braced child on the same line must stay with that child. *) let after_opening_greater_than, rest = - partition_by_on_same_line opening_greater_than_loc rest + match children with + | child :: _ -> + partition_adjacent_trailing_before_next_token_on_same_line + opening_greater_than_loc + (get_loc (Expression child)) + rest + | [] -> partition_by_on_same_line opening_greater_than_loc rest in attach t.trailing opening_greater_than_loc after_opening_greater_than; diff --git a/compiler/syntax/src/res_parens.ml b/compiler/syntax/src/res_parens.ml index b24c5fbede3..42324d7035f 100644 --- a/compiler/syntax/src/res_parens.ml +++ b/compiler/syntax/src/res_parens.ml @@ -348,39 +348,17 @@ let jsx_child_expr expr = match expr.Parsetree.pexp_desc with | Parsetree.Pexp_let _ | Pexp_sequence _ | Pexp_letexception _ | Pexp_letmodule _ | Pexp_open _ -> + (* These expressions already print as braced blocks. *) Nothing | _ -> ( let opt_braces, _ = Parsetree_viewer.process_braces_attr expr in match opt_braces with | Some ({Location.loc = braces_loc}, _) -> Braced braces_loc | _ -> ( - match expr with - | { - Parsetree.pexp_desc = - Pexp_constant (Pconst_integer (x, _) | Pconst_float (x, _)); - pexp_attributes = []; - } - when starts_with_minus x -> - Parenthesized - | _ when Parsetree_viewer.expr_is_await expr -> Parenthesized - | { - Parsetree.pexp_desc = - ( Pexp_ident _ | Pexp_constant _ | Pexp_regexp _ | Pexp_field _ - | Pexp_construct _ | Pexp_variant _ | Pexp_array _ | Pexp_pack _ - | Pexp_record _ | Pexp_object_literal _ | Pexp_extension _ - | Pexp_letmodule _ | Pexp_letexception _ | Pexp_open _ - | Pexp_sequence _ | Pexp_let _ | Pexp_jsx_element _ ); - pexp_attributes = []; - } -> - Nothing - | { - Parsetree.pexp_desc = - Pexp_constraint - ({pexp_desc = Pexp_pack _}, {ptyp_desc = Ptyp_package _}); - pexp_attributes = []; - } -> - Nothing - | {pexp_desc = Pexp_jsx_element _} -> Nothing + match expr.pexp_desc with + | Pexp_jsx_element _ -> Nothing + (* JSX child expressions use braces even when the legacy grammar accepts + them bare. Records need an outer pair around their own braces. *) | _ -> Parenthesized)) let binary_expr expr = diff --git a/compiler/syntax/src/res_parens.mli b/compiler/syntax/src/res_parens.mli index 8d304823f41..c0360934841 100644 --- a/compiler/syntax/src/res_parens.mli +++ b/compiler/syntax/src/res_parens.mli @@ -23,6 +23,9 @@ val field_expr : Parsetree.expression -> kind val ternary_operand : Parsetree.expression -> kind val jsx_prop_expr : Parsetree.expression -> kind + +(* JSX children use braces for [Parenthesized]; [Nothing] is reserved for JSX + elements and expressions that already print as braced blocks. *) val jsx_child_expr : Parsetree.expression -> kind val binary_expr : Parsetree.expression -> kind diff --git a/compiler/syntax/src/res_printer.ml b/compiler/syntax/src/res_printer.ml index 7854dbe7091..d0d5c215be5 100644 --- a/compiler/syntax/src/res_printer.ml +++ b/compiler/syntax/src/res_printer.ml @@ -4661,7 +4661,7 @@ and print_jsx_container_tag ~state tag_name Doc.concat [ Doc.indent - (Doc.concat [Doc.line; print_jsx_children ~state children cmt_tbl]); + (Doc.concat [line_sep; print_jsx_children ~state children cmt_tbl]); line_sep; ] in @@ -4752,7 +4752,7 @@ and print_jsx_fragment ~state (opening_greater_than : Lexing.position) [ opening; Doc.indent - (Doc.concat [Doc.line; print_jsx_children ~state children cmt_tbl]); + (Doc.concat [line_sep; print_jsx_children ~state children cmt_tbl]); (if has_children then line_sep else Doc.nil); closing; ]) @@ -4766,7 +4766,7 @@ and get_line_sep_for_jsx_children (children : Parsetree.jsx_children) = | _ -> false) children then Doc.hard_line - else Doc.line + else Doc.soft_line and print_jsx_children ~state (children : Parsetree.jsx_children) cmt_tbl = let open Parsetree in @@ -4784,21 +4784,44 @@ and print_jsx_children ~state (children : Parsetree.jsx_children) cmt_tbl = in let sep = get_line_sep_for_jsx_children children in let print_expr (expr : Parsetree.expression) = + let wrapping = Parens.jsx_child_expr expr in let leading_line_comment_present = has_leading_line_comment cmt_tbl expr.pexp_loc in + let trailing_line_comment_present = + has_trailing_single_line_comment cmt_tbl expr.pexp_loc + in + let leading_doc, trailing_doc = + match wrapping with + | Parenthesized -> + (* Consume outer comments before printing: identifiers and records can + share their location with a subnode that would otherwise take them. *) + let leading = + print_leading_comments Doc.nil cmt_tbl.leading expr.pexp_loc + in + let trailing = + print_trailing_comments Doc.nil cmt_tbl.trailing expr.pexp_loc + in + (leading, trailing) + | Nothing | Braced _ -> (Doc.nil, Doc.nil) + in let expr_doc = print_expression_with_comments ~state expr cmt_tbl in let add_parens_or_braces expr_doc = (* {(20: int)} make sure that we also protect the expression inside *) let inner_doc = if Parens.braced_expr expr then add_parens expr_doc else expr_doc in - if leading_line_comment_present then add_braces inner_doc + if + wrapping <> Parens.Parenthesized + && (leading_line_comment_present || trailing_line_comment_present) + then add_braces (Doc.concat [inner_doc; Doc.break_parent]) else Doc.concat [Doc.lbrace; inner_doc; Doc.rbrace] in - match Parens.jsx_child_expr expr with + match wrapping with | Nothing -> print_comments expr_doc cmt_tbl (get_loc expr) - | Parenthesized -> add_parens_or_braces expr_doc + | Parenthesized -> + (* Comments outside a bare child stay outside its newly inserted braces. *) + Doc.concat [leading_doc; add_parens_or_braces expr_doc; trailing_doc] | Braced braces_loc -> print_comments (add_parens_or_braces expr_doc) cmt_tbl braces_loc in diff --git a/packages/dev-playground/src/Main.res b/packages/dev-playground/src/Main.res index 9a37f0ad1b3..6ffe5f805fe 100644 --- a/packages/dev-playground/src/Main.res +++ b/packages/dev-playground/src/Main.res @@ -439,7 +439,7 @@ module Problems = { @jsx.component let make = (~compileResult: Signal.t>) => {
-
{View.text("Problems")}
+
{View.text("Problems")}
         {View.signalText(() =>
           switch Signal.get(compileResult) {
@@ -479,7 +479,7 @@ module SettingsPanel = {
       Computed.make(() =>
         CompilerApi.selectableCompilerVersions(
           Signal.get(config).compilerVersion,
-        )->Array.map(version => )
+        )->Array.map(version => )
       ),
     )
 
@@ -488,9 +488,7 @@ module SettingsPanel = {
         Signal.get(activeTab) === Settings ? "settings-panel" : "settings-panel hidden-panel"}
     >
       
- +
- +
{View.signalText(() => switch Signal.get(compilerInfo) { @@ -515,7 +513,7 @@ module SettingsPanel = {
- +
- + Signal.get(config).warnFlags} @@ -571,7 +569,7 @@ module SettingsPanel = { compileNow() }} /> - +
- +
-
{View.text("Source Map")}
+
{View.text("Source Map")}
- + @@ -636,7 +634,7 @@ module SettingsPanel = { {View.text("Include sources content")}
- + Signal.get(config).sourceMapMode === Disabled} @@ -666,10 +664,10 @@ module SettingsPanel = { compileNow() }} /> - +
- +
{View.signalText(() => switch Signal.get(compilerInfo) { @@ -1257,7 +1255,7 @@ module App = {
-

{View.text("ReScript Developer Playground")}

+

{View.text("ReScript Developer Playground")}

@@ -1268,7 +1266,7 @@ module App = { >
-

{View.text("Source")}

+

{View.text("Source")}