Skip to content

Commit a745e6f

Browse files
authored
Improve jsx preserve output (#7439)
* Indent children * Print props on different lines * Improve opening tag when no props are present * Add additional test * Improve consistency of prop printing
1 parent 244cdad commit a745e6f

File tree

3 files changed

+241
-86
lines changed

3 files changed

+241
-86
lines changed

compiler/core/js_dump.ml

+124-51
Original file line numberDiff line numberDiff line change
@@ -1070,6 +1070,34 @@ and expression_desc cxt ~(level : int) f x : cxt =
10701070
P.string f "...";
10711071
expression ~level:13 cxt f e)
10721072

1073+
and print_indented_list (f : P.t) (parent_expr_level : int) (cxt : cxt)
1074+
(items : 'a list) (print_item_func : int -> cxt -> P.t -> 'a -> cxt) : cxt =
1075+
if List.length items = 0 then cxt
1076+
else
1077+
P.group f 1 (fun () ->
1078+
(* Increment indent level by 1 for this block of items *)
1079+
P.newline f;
1080+
(* Start the block on a new, fully indented line for the first item *)
1081+
let rec process_items current_cxt_for_fold remaining_items =
1082+
match remaining_items with
1083+
| [] ->
1084+
current_cxt_for_fold
1085+
(* Base case for recursion, though initial check avoids empty items *)
1086+
| [last_item] ->
1087+
(* Print the last item, but DO NOT print a newline after it *)
1088+
print_item_func parent_expr_level current_cxt_for_fold f last_item
1089+
| current_item :: next_items ->
1090+
let cxt_after_current =
1091+
print_item_func parent_expr_level current_cxt_for_fold f
1092+
current_item
1093+
in
1094+
P.newline f;
1095+
(* Add a newline AFTER the current item, to prepare for the NEXT item *)
1096+
process_items cxt_after_current next_items
1097+
in
1098+
(* Initial call to the recursive helper; initial check ensures items is not empty *)
1099+
process_items cxt items)
1100+
10731101
and print_jsx cxt ?(spread_props : J.expression option)
10741102
?(key : J.expression option) ~(level : int) f (fnName : string)
10751103
(tag : J.expression) (fields : (string * J.expression) list) : cxt =
@@ -1098,71 +1126,116 @@ and print_jsx cxt ?(spread_props : J.expression option)
10981126
else None)
10991127
fields
11001128
in
1101-
let print_props cxt =
1129+
let print_props cxt props =
11021130
(* If a key is present, should be printed before the spread props,
11031131
This is to ensure tools like ESBuild use the automatic JSX runtime *)
1104-
let cxt =
1105-
match key with
1106-
| None -> cxt
1107-
| Some key ->
1108-
P.string f " key={";
1109-
let cxt = expression ~level:0 cxt f key in
1110-
P.string f "} ";
1111-
cxt
1132+
let print_key key cxt =
1133+
P.string f "key={";
1134+
let cxt_k = expression ~level:0 cxt f key in
1135+
P.string f "} ";
1136+
cxt_k
11121137
in
1113-
let props = List.filter (fun (n, _) -> n <> "children") fields in
1114-
let cxt =
1115-
match spread_props with
1116-
| None -> cxt
1117-
| Some spread ->
1118-
P.string f " {...";
1119-
let cxt = expression ~level:0 cxt f spread in
1120-
P.string f "} ";
1121-
cxt
1138+
1139+
let print_spread_props spread cxt =
1140+
P.string f "{...";
1141+
let cxt = expression ~level:0 cxt f spread in
1142+
P.string f "} ";
1143+
cxt
1144+
in
1145+
1146+
let print_prop n x ctx =
1147+
let prop_name = Js_dump_property.property_key_string n in
1148+
P.string f prop_name;
1149+
P.string f "=";
1150+
P.string f "{";
1151+
let next_cxt = expression ~level:0 ctx f x in
1152+
P.string f "}";
1153+
next_cxt
11221154
in
1123-
if List.length props = 0 then cxt
1155+
let printable_props =
1156+
(match key with
1157+
| None -> []
1158+
| Some k -> [print_key k])
1159+
@ (match spread_props with
1160+
| None -> []
1161+
| Some spread -> [print_spread_props spread])
1162+
@ List.map (fun (n, x) -> print_prop n x) props
1163+
in
1164+
if List.length printable_props = 0 then (
1165+
match children_opt with
1166+
| Some _ -> cxt
1167+
| None ->
1168+
(* Put a space the tag name and /> *)
1169+
P.space f;
1170+
cxt)
11241171
else
1125-
(List.fold_left (fun acc (n, x) ->
1126-
P.space f;
1127-
let prop_name = Js_dump_property.property_key_string n in
1128-
1129-
P.string f prop_name;
1130-
P.string f "=";
1131-
P.string f "{";
1132-
let next = expression ~level:0 acc f x in
1133-
P.string f "}";
1134-
next))
1135-
cxt props
1172+
P.group f 1 (fun () ->
1173+
P.newline f;
1174+
let rec process_remaining_props acc_cxt printable_props =
1175+
match printable_props with
1176+
| [] -> acc_cxt
1177+
| print_prop :: [] -> print_prop acc_cxt
1178+
| print_prop :: tail ->
1179+
let next_cxt = print_prop acc_cxt in
1180+
P.newline f;
1181+
process_remaining_props next_cxt tail
1182+
in
1183+
process_remaining_props cxt printable_props)
11361184
in
1137-
match children_opt with
1138-
| None ->
1139-
P.string f "<";
1140-
let cxt = cxt |> print_tag |> print_props in
1141-
P.string f "/>";
1142-
cxt
1143-
| Some children ->
1144-
let child_is_jsx child =
1145-
match child.J.expression_desc with
1185+
1186+
let print_one_child expr_level_for_child current_cxt_for_child f_format
1187+
child_expr =
1188+
let child_is_jsx_itself =
1189+
match child_expr.J.expression_desc with
11461190
| J.Call (_, _, {call_transformed_jsx = is_jsx}) -> is_jsx
11471191
| _ -> false
11481192
in
1193+
if not child_is_jsx_itself then P.string f_format "{";
1194+
let next_cxt =
1195+
expression ~level:expr_level_for_child current_cxt_for_child f_format
1196+
child_expr
1197+
in
1198+
if not child_is_jsx_itself then P.string f_format "}";
1199+
next_cxt
1200+
in
1201+
1202+
let props = List.filter (fun (n, _) -> n <> "children") fields in
1203+
1204+
(* Actual printing of JSX element starts here *)
1205+
P.string f "<";
1206+
let cxt = print_tag cxt in
1207+
let cxt = print_props cxt props in
1208+
(* print_props handles its own block and updates cxt *)
11491209

1150-
P.string f "<";
1151-
let cxt = cxt |> print_tag |> print_props in
1210+
let has_multiple_props = List.length props > 0 in
11521211

1212+
match children_opt with
1213+
| None ->
1214+
(* Self-closing tag *)
1215+
if has_multiple_props then P.newline f;
1216+
P.string f "/>";
1217+
cxt
1218+
| Some children ->
1219+
(* Tag with children *)
1220+
let has_children = List.length children > 0 in
1221+
(* Newline for ">" only if props themselves were multi-line. Children alone don't push ">" to a new line. *)
1222+
if has_multiple_props then P.newline f;
11531223
P.string f ">";
1154-
if List.length children > 0 then P.newline f;
11551224

1156-
let cxt =
1157-
List.fold_left
1158-
(fun acc e ->
1159-
if not (child_is_jsx e) then P.string f "{";
1160-
let next = expression ~level acc f e in
1161-
if not (child_is_jsx e) then P.string f "}";
1162-
P.newline f;
1163-
next)
1164-
cxt children
1225+
let cxt_after_children =
1226+
if has_children then
1227+
(* Only call print_indented_list if there are children *)
1228+
print_indented_list f level cxt children print_one_child
1229+
else cxt
1230+
(* No children, no change to context here, no newlines from children block *)
11651231
in
1232+
let cxt = cxt_after_children in
1233+
1234+
(* The closing "</tag>" goes on a new line if the opening part was multi-line (due to props)
1235+
OR if there were actual children printed (which always makes the element multi-line).
1236+
*)
1237+
let element_content_was_multiline = has_multiple_props || has_children in
1238+
if element_content_was_multiline then P.newline f;
11661239

11671240
P.string f "</";
11681241
let cxt = print_tag cxt in

0 commit comments

Comments
 (0)