Skip to content
This repository was archived by the owner on Jun 15, 2023. It is now read-only.

Commit 32ee826

Browse files
committed
add parsing and jsx ppx for spread props
1 parent 3f93b1a commit 32ee826

File tree

4 files changed

+144
-7
lines changed

4 files changed

+144
-7
lines changed

cli/reactjs_jsx_ppx_v3.ml

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,115 @@ type 'a children = ListLiteral of 'a | Exact of 'a
3333

3434
type componentConfig = { propsName : string }
3535

36+
(* structure ref in order to avoid too many arg drilling *)
37+
let impl: Parsetree.structure ref = ref []
38+
39+
(* signature ref in order to avoid too many arg drilling *)
40+
let intf: Parsetree.signature ref = ref []
41+
42+
(* List.filter_map in 4.08.0 *)
43+
let filterMap f =
44+
let rec aux accu = function
45+
| [] -> List.rev accu
46+
| x :: l ->
47+
match f x with
48+
| None -> aux accu l
49+
| Some v -> aux (v :: accu) l
50+
in
51+
aux []
52+
53+
(* fold the Longident.t to string for record fields to be spread. Maybe Lident is needed. *)
54+
let stringOfLid lid = Longident.flatten lid.txt |> List.fold_left (fun acc x -> acc ^ x) ""
55+
56+
(* Look up the record to be spread and extract the fields *)
57+
let findRecordFields { pexp_desc } =
58+
let rec findRecordFieldsAux structure labels =
59+
match labels with
60+
| [] -> raise (Invalid_argument "JSX: spread props missing")
61+
(* foo *)
62+
| [ label ] ->
63+
structure
64+
|> filterMap (fun { pstr_desc } ->
65+
match pstr_desc with
66+
| Pstr_value (_, vbs) -> (
67+
let matched_vbs =
68+
vbs
69+
|> List.filter (fun { pvb_pat = { ppat_desc } } ->
70+
match ppat_desc with
71+
| Ppat_var { Location.txt } -> txt = label
72+
| _ -> false)
73+
in
74+
match matched_vbs with
75+
| [] -> None
76+
| [ { pvb_expr = { pexp_desc } } ] | { pvb_expr = { pexp_desc } } :: _ ->
77+
begin
78+
match pexp_desc with
79+
| Pexp_record (fields, _) -> Some fields
80+
| _ -> None
81+
end)
82+
| _ -> None)
83+
(* Foo.name *)
84+
| label :: labels ->
85+
structure
86+
|> filterMap (fun { pstr_desc } ->
87+
match pstr_desc with
88+
(* module Foo = ... *)
89+
| Pstr_module
90+
{
91+
pmb_name;
92+
pmb_expr = { pmod_desc = Pmod_structure structure };
93+
} ->
94+
if pmb_name.txt = label then
95+
Some (findRecordFieldsAux structure labels)
96+
else None
97+
(* module Foo: Foo = ... *)
98+
| Pstr_module
99+
{
100+
pmb_name;
101+
pmb_expr =
102+
{
103+
pmod_desc =
104+
Pmod_constraint
105+
({ pmod_desc = Pmod_structure structure }, _);
106+
};
107+
} ->
108+
if pmb_name.txt = label then
109+
Some (findRecordFieldsAux structure labels)
110+
else None
111+
| _ -> None)
112+
|> List.concat
113+
in
114+
(* foo, foo.name, Foo.name *)
115+
match pexp_desc with
116+
| Pexp_ident lid
117+
| Pexp_field (_, lid) ->
118+
begin
119+
let labels = Longident.flatten lid.txt in
120+
let recordFields = findRecordFieldsAux !impl labels in
121+
(* last record fields of list is the closest one *)
122+
try recordFields |> List.rev |> List.hd with _ -> raise (Invalid_argument "JSX: can't find the spread prop record")
123+
end
124+
| _ -> raise (Invalid_argument "JSX: can't find the spread prop record")
125+
126+
(* spread props if exists *)
127+
let propsWithSpreadProps callArguments =
128+
let unitRef = ref None in
129+
let rec removeLastPositionUnitAux props acc =
130+
match props with
131+
| [] -> []
132+
| [ (Nolabel, { pexp_desc = Pexp_construct ({ txt = Lident "()" }, None) }) as u ] -> unitRef := Some u; acc
133+
| (Nolabel, _) :: _rest -> raise (Invalid_argument "JSX: found non-labelled argument before the last position")
134+
| prop :: rest -> removeLastPositionUnitAux rest (prop :: acc)
135+
in
136+
let props, propsToSpread = removeLastPositionUnitAux callArguments [] |> List.rev |> List.partition (fun (label, _) -> label <> labelled "spreadProps") in
137+
let spreadProps = propsToSpread
138+
|> List.map (fun (_, expression) -> expression)
139+
|> List.map findRecordFields
140+
|> List.concat
141+
|> List.map (fun (lid, expression) -> (labelled @@ stringOfLid lid, expression))
142+
in
143+
match !unitRef with Some u -> props @ spreadProps @ [ u ] | None -> props @ spreadProps
144+
36145
(* if children is a list, convert it to an array while mapping each element. If not, just map over it, as usual *)
37146
let transformChildrenIfListUpper ~loc ~mapper theList =
38147
let rec transformChildren_ theList accum =
@@ -809,6 +918,7 @@ let jsxMapper () =
809918
in
810919

811920
let transformJsxCall mapper callExpression callArguments attrs =
921+
let callArguments = propsWithSpreadProps callArguments in
812922
match callExpression.pexp_desc with
813923
| Pexp_ident caller -> (
814924
match caller with
@@ -898,11 +1008,13 @@ let jsxMapper () =
8981008
[@@raises Invalid_argument, Failure]
8991009

9001010
let rewrite_implementation (code : Parsetree.structure) : Parsetree.structure =
1011+
impl := code;
9011012
let mapper = jsxMapper () in
9021013
mapper.structure mapper code
9031014
[@@raises Invalid_argument, Failure]
9041015

9051016
let rewrite_signature (code : Parsetree.signature) : Parsetree.signature =
1017+
intf := code;
9061018
let mapper = jsxMapper () in
9071019
mapper.signature mapper code
9081020
[@@raises Invalid_argument, Failure]

cli/reactjs_jsx_ppx_v3.mli

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,14 @@
2222
transform `[@JSX] [foo]` into
2323
`ReactDOMRe.createElement(ReasonReact.fragment, [|foo|])`
2424
v3:
25-
transform `[@JSX] div(~props1=a, ~props2=b, ~children=[foo, bar], ())` into
26-
`ReactDOMRe.createDOMElementVariadic("div", ReactDOMRe.domProps(~props1=1, ~props2=b), [|foo, bar|])`.
25+
transform `[@JSX] div(~props1=a, ~props2=b, ~spreadProps=props3 ~children=[foo, bar], ())` into
26+
`ReactDOMRe.createDOMElementVariadic("div", ReactDOMRe.domProps(~props1=1, ~props2=b, ~props31=c1, ~props32=c2), [|foo, bar|])`.
2727
transform the upper-cased case
28-
`[@JSX] Foo.createElement(~key=a, ~ref=b, ~foo=bar, ~children=[], ())` into
29-
`React.createElement(Foo.make, Foo.makeProps(~key=a, ~ref=b, ~foo=bar, ()))`
28+
`[@JSX] Foo.createElement(~key=a, ~ref=b, ~foo=bar, ~spreadProps=baz ~children=[], ())` into
29+
`React.createElement(Foo.make, Foo.makeProps(~key=a, ~ref=b, ~foo=bar, ~baz1=c, ~baz2=d, ()))`
3030
transform the upper-cased case
31-
`[@JSX] Foo.createElement(~foo=bar, ~children=[foo, bar], ())` into
32-
`React.createElementVariadic(Foo.make, Foo.makeProps(~foo=bar, ~children=React.null, ()), [|foo, bar|])`
31+
`[@JSX] Foo.createElement(~foo=bar, ~spreadProps=baz, ~children=[foo, bar], ())` into
32+
`React.createElementVariadic(Foo.make, Foo.makeProps(~foo=bar, ~baz1=a, ~baz2=b ~children=React.null, ()), [|foo, bar|])`
3333
transform `[@JSX] [foo]` into
3434
`ReactDOMRe.createElement(ReasonReact.fragment, [|foo|])`
3535
*)

src/res_core.ml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2657,6 +2657,7 @@ and parseJsxFragment p =
26572657
* | ?lident
26582658
* | lident = jsx_expr
26592659
* | lident = ?jsx_expr
2660+
* | {...jsx_expr}
26602661
*)
26612662
and parseJsxProp p =
26622663
match p.Parser.token with
@@ -2694,6 +2695,30 @@ and parseJsxProp p =
26942695
in
26952696
Some (label, attrExpr)
26962697
end
2698+
(* {...props} *)
2699+
| Lbrace ->
2700+
begin
2701+
Parser.next p;
2702+
match p.Parser.token with
2703+
| DotDotDot ->
2704+
begin
2705+
Parser.next p;
2706+
let loc = mkLoc p.Parser.startPos p.prevEndPos in
2707+
let propLocAttr = (Location.mkloc "ns.namedArgLoc" loc, Parsetree.PStr []) in
2708+
let attrExpr =
2709+
let e = parsePrimaryExpr ~operand:(parseAtomicExpr p) p in
2710+
{e with pexp_attributes = propLocAttr::e.pexp_attributes}
2711+
in
2712+
(* using label "spreadProps" to distinguish from others *)
2713+
let label = Asttypes.Labelled "spreadProps" in
2714+
match p.Parser.token with
2715+
| Rbrace ->
2716+
Parser.next p;
2717+
Some (label, attrExpr)
2718+
|_ -> None
2719+
end
2720+
| _ -> None
2721+
end
26972722
| _ ->
26982723
None
26992724

src/res_grammar.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ let isExprStart = function
176176
| _ -> false
177177

178178
let isJsxAttributeStart = function
179-
| Token.Lident _ | Question -> true
179+
| Token.Lident _ | Question | Lbrace -> true
180180
| _ -> false
181181

182182
let isStructureItemStart = function

0 commit comments

Comments
 (0)