Skip to content

Commit db7eee6

Browse files
committed
Intial exploration of JSX ast
Map child expressions Initial mapping of Pexp_jsx_fragment to 0 Correct location in mapping Update analysis for jsx_fragment Remove unused code Print something for ml print Commit invalid test results for reference Try improve printing Correct fragment range, try and print comments Indent jsx Process comments from children inside fragment Attach comments to fragment tags Fix comment Improve comment formatting Print single element on same line Update comment WIP: Debug More debugging Works Fix some jsx printing Fix the test Clean up Update tests with location changes
1 parent 26a15aa commit db7eee6

29 files changed

+266
-198
lines changed

analysis/src/CompletionFrontEnd.ml

+2-1
Original file line numberDiff line numberDiff line change
@@ -1232,7 +1232,8 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor
12321232
then ValueOrField
12331233
else Value);
12341234
}))
1235-
| Pexp_construct ({txt = Lident ("::" | "()")}, _) ->
1235+
| Pexp_construct ({txt = Lident ("::" | "()")}, _) | Pexp_jsx_fragment _
1236+
->
12361237
(* Ignore list expressions, used in JSX, unit, and more *) ()
12371238
| Pexp_construct (lid, eOpt) -> (
12381239
let lidPath = flattenLidCheckDot lid in

analysis/src/Utils.ml

+5
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ let identifyPexp pexp =
111111
| Pexp_pack _ -> "Pexp_pack"
112112
| Pexp_extension _ -> "Pexp_extension"
113113
| Pexp_open _ -> "Pexp_open"
114+
| Pexp_jsx_fragment _ -> "Pexp_jsx_fragment"
114115

115116
let identifyPpat pat =
116117
match pat with
@@ -154,6 +155,10 @@ let isJsxComponent (vb : Parsetree.value_binding) =
154155
|> List.exists (function
155156
| {Location.txt = "react.component" | "jsx.component"}, _payload -> true
156157
| _ -> false)
158+
||
159+
match vb.pvb_expr.pexp_desc with
160+
| Parsetree.Pexp_jsx_fragment _ -> true
161+
| _ -> false
157162

158163
let checkName name ~prefix ~exact =
159164
if exact then name = prefix else startsWith name prefix

compiler/frontend/bs_ast_mapper.ml

+2
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,8 @@ module E = struct
366366
| Pexp_open (ovf, lid, e) ->
367367
open_ ~loc ~attrs ovf (map_loc sub lid) (sub.expr sub e)
368368
| Pexp_extension x -> extension ~loc ~attrs (sub.extension sub x)
369+
| Pexp_jsx_fragment (o, xs, c) ->
370+
jsx_fragment o (List.map (sub.expr sub) xs) c
369371
end
370372

371373
module P = struct

compiler/ml/ast_helper.ml

+27
Original file line numberDiff line numberDiff line change
@@ -180,8 +180,35 @@ module Exp = struct
180180
let pack ?loc ?attrs a = mk ?loc ?attrs (Pexp_pack a)
181181
let open_ ?loc ?attrs a b c = mk ?loc ?attrs (Pexp_open (a, b, c))
182182
let extension ?loc ?attrs a = mk ?loc ?attrs (Pexp_extension a)
183+
let jsx_fragment ?loc ?attrs a b c =
184+
mk ?loc ?attrs (Pexp_jsx_fragment (a, b, c))
183185

184186
let case lhs ?guard rhs = {pc_lhs = lhs; pc_guard = guard; pc_rhs = rhs}
187+
188+
let make_list_expression loc seq ext_opt =
189+
let rec handle_seq = function
190+
| [] -> (
191+
match ext_opt with
192+
| Some ext -> ext
193+
| None ->
194+
let loc = {loc with Location.loc_ghost = true} in
195+
let nil = Location.mkloc (Longident.Lident "[]") loc in
196+
construct ~loc nil None)
197+
| e1 :: el ->
198+
let exp_el = handle_seq el in
199+
let loc =
200+
Location.
201+
{
202+
loc_start = e1.Parsetree.pexp_loc.Location.loc_start;
203+
loc_end = exp_el.pexp_loc.loc_end;
204+
loc_ghost = false;
205+
}
206+
in
207+
let arg = tuple ~loc [e1; exp_el] in
208+
construct ~loc (Location.mkloc (Longident.Lident "::") loc) (Some arg)
209+
in
210+
let expr = handle_seq seq in
211+
{expr with pexp_loc = loc}
185212
end
186213

187214
module Mty = struct

compiler/ml/ast_helper.mli

+10
Original file line numberDiff line numberDiff line change
@@ -208,8 +208,18 @@ module Exp : sig
208208
val open_ :
209209
?loc:loc -> ?attrs:attrs -> override_flag -> lid -> expression -> expression
210210
val extension : ?loc:loc -> ?attrs:attrs -> extension -> expression
211+
val jsx_fragment :
212+
?loc:loc ->
213+
?attrs:attrs ->
214+
Lexing.position ->
215+
expression list ->
216+
Lexing.position ->
217+
expression
211218

212219
val case : pattern -> ?guard:expression -> expression -> case
220+
221+
val make_list_expression :
222+
Location.t -> expression list -> expression option -> expression
213223
end
214224

215225
(** Value declarations *)

compiler/ml/ast_iterator.ml

+1
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,7 @@ module E = struct
344344
iter_loc sub lid;
345345
sub.expr sub e
346346
| Pexp_extension x -> sub.extension sub x
347+
| Pexp_jsx_fragment (_, xs, _) -> List.iter (sub.expr sub) xs
347348
end
348349

349350
module P = struct

compiler/ml/ast_mapper.ml

+2
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,8 @@ module E = struct
329329
| Pexp_open (ovf, lid, e) ->
330330
open_ ~loc ~attrs ovf (map_loc sub lid) (sub.expr sub e)
331331
| Pexp_extension x -> extension ~loc ~attrs (sub.extension sub x)
332+
| Pexp_jsx_fragment (o, xs, c) ->
333+
jsx_fragment ~loc ~attrs o (List.map (sub.expr sub) xs) c
332334
end
333335

334336
module P = struct

compiler/ml/ast_mapper_to0.ml

+12
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,18 @@ module E = struct
407407
| Pexp_open (ovf, lid, e) ->
408408
open_ ~loc ~attrs ovf (map_loc sub lid) (sub.expr sub e)
409409
| Pexp_extension x -> extension ~loc ~attrs (sub.extension sub x)
410+
| Pexp_jsx_fragment (o, xs, c) ->
411+
(*
412+
The location of Pexp_jsx_fragment is from the start of < till the end of />.
413+
This is not the case in the old AST. There it is from >...</
414+
*)
415+
let loc = {loc with loc_start = o; loc_end = c} in
416+
let list_expr = Ast_helper.Exp.make_list_expression loc xs None in
417+
let mapped = sub.expr sub list_expr in
418+
let jsx_attr =
419+
sub.attribute sub (Location.mknoloc "JSX", Parsetree.PStr [])
420+
in
421+
{mapped with pexp_attributes = jsx_attr :: attrs}
410422
end
411423

412424
module P = struct

compiler/ml/depend.ml

+1
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,7 @@ let rec add_expr bv exp =
289289
| Pstr_eval ({pexp_desc = Pexp_construct (c, None)}, _) -> add bv c
290290
| _ -> handle_extension e)
291291
| Pexp_extension e -> handle_extension e
292+
| Pexp_jsx_fragment (_, xs, _) -> List.iter (add_expr bv) xs
292293

293294
and add_cases bv cases = List.iter (add_case bv) cases
294295

compiler/ml/parsetree.ml

+8-2
Original file line numberDiff line numberDiff line change
@@ -313,8 +313,14 @@ and expression_desc =
313313
let open M in E
314314
let! open M in E *)
315315
| Pexp_extension of extension
316-
(* [%id] *)
317-
(* . *)
316+
(* [%id] *)
317+
(* . *)
318+
(* represents <> foo </> , the entire range is stored in the expression , we keep track of >, children and </ *)
319+
| Pexp_jsx_fragment of
320+
(* > *) Lexing.position
321+
* (* children *)
322+
expression list
323+
* (* </ *) Lexing.position
318324

319325
and case = {
320326
(* (P -> E) or (P when E0 -> E) *)

compiler/ml/pprintast.ml

+2
Original file line numberDiff line numberDiff line change
@@ -794,6 +794,8 @@ and simple_expr ctxt f x =
794794
let expression = expression ctxt in
795795
pp f fmt (pattern ctxt) s expression e1 direction_flag df expression e2
796796
expression e3
797+
| Pexp_jsx_fragment (_, xs, _) ->
798+
pp f "<>%a</>" (list (simple_expr ctxt)) xs
797799
| _ -> paren true (expression ctxt) f x
798800

799801
and attributes ctxt f l = List.iter (attribute ctxt f) l

compiler/ml/printast.ml

+3
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,9 @@ and expression i ppf x =
345345
| Pexp_extension (s, arg) ->
346346
line i ppf "Pexp_extension \"%s\"\n" s.txt;
347347
payload i ppf arg
348+
| Pexp_jsx_fragment (_, xs, _) ->
349+
line i ppf "Pexp_jsx_fragment";
350+
list i expression ppf xs
348351

349352
and value_description i ppf x =
350353
line i ppf "value_description %a %a\n" fmt_string_loc x.pval_name fmt_location

compiler/ml/typecore.ml

+4-1
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,8 @@ let iter_expression f e =
148148
| Pexp_match (e, pel) | Pexp_try (e, pel) ->
149149
expr e;
150150
List.iter case pel
151-
| Pexp_array el | Pexp_tuple el -> List.iter expr el
151+
| Pexp_array el | Pexp_tuple el | Pexp_jsx_fragment (_, el, _) ->
152+
List.iter expr el
152153
| Pexp_construct (_, eo) | Pexp_variant (_, eo) -> may expr eo
153154
| Pexp_record (iel, eo) ->
154155
may expr eo;
@@ -3208,6 +3209,8 @@ and type_expect_ ?type_clash_context ?in_function ?(recarg = Rejected) env sexp
32083209
| _ -> raise (Error (loc, env, Invalid_extension_constructor_payload)))
32093210
| Pexp_extension ext ->
32103211
raise (Error_forward (Builtin_attributes.error_of_extension ext))
3212+
| Pexp_jsx_fragment _ ->
3213+
failwith "Pexp_jsx_fragment is expected to be transformed at this point"
32113214
32123215
and type_function ?in_function ~arity ~async loc attrs env ty_expected_ l
32133216
caselist =

compiler/syntax/src/jsx_v4.ml

+55-82
Original file line numberDiff line numberDiff line change
@@ -1530,8 +1530,62 @@ let transform_jsx_call ~config mapper call_expression call_arguments
15301530
"JSX: `createElement` should be preceeded by a simple, direct module \
15311531
name."
15321532

1533-
let expr ~config mapper expression =
1533+
let expr ~(config : Jsx_common.jsx_config) mapper expression =
15341534
match expression with
1535+
| {
1536+
pexp_desc = Pexp_jsx_fragment (_, xs, _);
1537+
pexp_loc = loc;
1538+
pexp_attributes = attrs;
1539+
} ->
1540+
let loc = {loc with loc_ghost = true} in
1541+
let fragment =
1542+
match config.mode with
1543+
| "automatic" ->
1544+
Exp.ident ~loc {loc; txt = module_access_name config "jsxFragment"}
1545+
| "classic" | _ ->
1546+
Exp.ident ~loc {loc; txt = Ldot (Lident "React", "fragment")}
1547+
in
1548+
let record_of_children children =
1549+
Exp.record [(Location.mknoloc (Lident "children"), children, false)] None
1550+
in
1551+
let apply_jsx_array expr =
1552+
Exp.apply
1553+
(Exp.ident
1554+
{txt = module_access_name config "array"; loc = Location.none})
1555+
[(Nolabel, expr)]
1556+
in
1557+
let children_props =
1558+
match xs with
1559+
| [] -> empty_record ~loc:Location.none
1560+
| [child] -> record_of_children (mapper.expr mapper child)
1561+
| _ -> (
1562+
match config.mode with
1563+
| "automatic" ->
1564+
record_of_children
1565+
@@ apply_jsx_array (Exp.array (List.map (mapper.expr mapper) xs))
1566+
| "classic" | _ -> empty_record ~loc:Location.none)
1567+
in
1568+
let args =
1569+
(nolabel, fragment) :: (nolabel, children_props)
1570+
::
1571+
(match config.mode with
1572+
| "classic" when List.length xs > 1 ->
1573+
[(nolabel, Exp.array (List.map (mapper.expr mapper) xs))]
1574+
| _ -> [])
1575+
in
1576+
Exp.apply ~loc ~attrs
1577+
(* ReactDOM.createElement *)
1578+
(match config.mode with
1579+
| "automatic" ->
1580+
if List.length xs > 1 then
1581+
Exp.ident ~loc {loc; txt = module_access_name config "jsxs"}
1582+
else Exp.ident ~loc {loc; txt = module_access_name config "jsx"}
1583+
| "classic" | _ ->
1584+
if List.length xs > 1 then
1585+
Exp.ident ~loc
1586+
{loc; txt = Ldot (Lident "React", "createElementVariadic")}
1587+
else Exp.ident ~loc {loc; txt = Ldot (Lident "React", "createElement")})
1588+
args
15351589
(* Does the function application have the @JSX attribute? *)
15361590
| {
15371591
pexp_desc = Pexp_apply {funct = call_expression; args = call_arguments};
@@ -1549,87 +1603,6 @@ let expr ~config mapper expression =
15491603
| _, non_jsx_attributes ->
15501604
transform_jsx_call ~config mapper call_expression call_arguments pexp_loc
15511605
non_jsx_attributes)
1552-
(* is it a list with jsx attribute? Reason <>foo</> desugars to [@JSX][foo]*)
1553-
| {
1554-
pexp_desc =
1555-
( Pexp_construct
1556-
({txt = Lident "::"; loc}, Some {pexp_desc = Pexp_tuple _})
1557-
| Pexp_construct ({txt = Lident "[]"; loc}, None) );
1558-
pexp_attributes;
1559-
} as list_items -> (
1560-
let jsx_attribute, non_jsx_attributes =
1561-
List.partition
1562-
(fun (attribute, _) -> attribute.txt = "JSX")
1563-
pexp_attributes
1564-
in
1565-
match (jsx_attribute, non_jsx_attributes) with
1566-
(* no JSX attribute *)
1567-
| [], _ -> default_mapper.expr mapper expression
1568-
| _, non_jsx_attributes ->
1569-
let loc = {loc with loc_ghost = true} in
1570-
let fragment =
1571-
match config.mode with
1572-
| "automatic" ->
1573-
Exp.ident ~loc {loc; txt = module_access_name config "jsxFragment"}
1574-
| "classic" | _ ->
1575-
Exp.ident ~loc {loc; txt = Ldot (Lident "React", "fragment")}
1576-
in
1577-
let children_expr = transform_children_if_list ~mapper list_items in
1578-
let record_of_children children =
1579-
Exp.record
1580-
[(Location.mknoloc (Lident "children"), children, false)]
1581-
None
1582-
in
1583-
let apply_jsx_array expr =
1584-
Exp.apply
1585-
(Exp.ident
1586-
{txt = module_access_name config "array"; loc = Location.none})
1587-
[(Nolabel, expr)]
1588-
in
1589-
let count_of_children = function
1590-
| {pexp_desc = Pexp_array children} -> List.length children
1591-
| _ -> 0
1592-
in
1593-
let transform_children_to_props children_expr =
1594-
match children_expr with
1595-
| {pexp_desc = Pexp_array children} -> (
1596-
match children with
1597-
| [] -> empty_record ~loc:Location.none
1598-
| [child] -> record_of_children child
1599-
| _ -> (
1600-
match config.mode with
1601-
| "automatic" -> record_of_children @@ apply_jsx_array children_expr
1602-
| "classic" | _ -> empty_record ~loc:Location.none))
1603-
| _ -> (
1604-
match config.mode with
1605-
| "automatic" -> record_of_children @@ apply_jsx_array children_expr
1606-
| "classic" | _ -> empty_record ~loc:Location.none)
1607-
in
1608-
let args =
1609-
(nolabel, fragment)
1610-
:: (nolabel, transform_children_to_props children_expr)
1611-
::
1612-
(match config.mode with
1613-
| "classic" when count_of_children children_expr > 1 ->
1614-
[(nolabel, children_expr)]
1615-
| _ -> [])
1616-
in
1617-
Exp.apply
1618-
~loc (* throw away the [@JSX] attribute and keep the others, if any *)
1619-
~attrs:non_jsx_attributes
1620-
(* ReactDOM.createElement *)
1621-
(match config.mode with
1622-
| "automatic" ->
1623-
if count_of_children children_expr > 1 then
1624-
Exp.ident ~loc {loc; txt = module_access_name config "jsxs"}
1625-
else Exp.ident ~loc {loc; txt = module_access_name config "jsx"}
1626-
| "classic" | _ ->
1627-
if count_of_children children_expr > 1 then
1628-
Exp.ident ~loc
1629-
{loc; txt = Ldot (Lident "React", "createElementVariadic")}
1630-
else
1631-
Exp.ident ~loc {loc; txt = Ldot (Lident "React", "createElement")})
1632-
args)
16331606
(* Delegate to the default mapper, a deep identity traversal *)
16341607
| e -> default_mapper.expr mapper e
16351608

compiler/syntax/src/res_ast_debugger.ml

+5
Original file line numberDiff line numberDiff line change
@@ -707,6 +707,11 @@ module SexpAst = struct
707707
]
708708
| Pexp_extension ext ->
709709
Sexp.list [Sexp.atom "Pexp_extension"; extension ext]
710+
| Pexp_jsx_fragment (_, xs, _) ->
711+
Sexp.list
712+
[
713+
Sexp.atom "Pexp_jsx_fragment"; Sexp.list (map_empty ~f:expression xs);
714+
]
710715
in
711716
Sexp.list [Sexp.atom "expression"; desc]
712717

0 commit comments

Comments
 (0)