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

Commit 0bbb5f8

Browse files
committed
fix ppx for forwardRef
1 parent d15b125 commit 0bbb5f8

File tree

7 files changed

+60
-35
lines changed

7 files changed

+60
-35
lines changed

cli/reactjs_jsx_ppx_v3.ml

Lines changed: 47 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ let isOptional str = match str with Optional _ -> true | _ -> false
1414

1515
let isLabelled str = match str with Labelled _ -> true | _ -> false
1616

17+
let isForwardRef = function
18+
| { pexp_desc = Pexp_ident { txt = (Ldot (Lident "React", "forwardRef")) } } -> true
19+
| _ -> false
20+
1721
let getLabel str = match str with Optional str | Labelled str -> str | Nolabel -> ""
1822

1923
let optionIdent = Lident "option"
@@ -34,12 +38,7 @@ let safeTypeFromValue valueStr =
3438

3539
let keyType loc = Typ.constr ~loc { loc; txt = Lident "string" } []
3640

37-
let refType loc = Typ.constr ~loc { loc; txt = Ldot (Lident "React", "ref") }
38-
[
39-
(Typ.constr ~loc { loc; txt = Ldot (Ldot (Lident "Js", "Nullable"), "t")}
40-
[(Typ.constr ~loc { loc; txt = Ldot (Lident "Dom", "element") } [])]
41-
)
42-
]
41+
let refType loc = (Typ.constr ~loc { loc; txt = Ldot (Ldot (Lident "ReactDOM", "Ref"), "currentDomRef") } [])
4342

4443
type 'a children = ListLiteral of 'a | Exact of 'a
4544

@@ -570,9 +569,10 @@ let jsxMapper () =
570569
pattern,
571570
({ pexp_desc = Pexp_fun _ } as internalExpression) );
572571
} ->
573-
let wrap, hasUnit, exp = spelunkForFunExpression internalExpression in
572+
let wrap, hasUnit, hasForwardRef, exp = spelunkForFunExpression internalExpression in
574573
( wrap,
575574
hasUnit,
575+
hasForwardRef,
576576
unerasableIgnoreExp { expression with pexp_desc = Pexp_fun (label, default, pattern, exp) } )
577577
(* let make = (()) => ... *)
578578
(* let make = (_) => ... *)
@@ -584,13 +584,13 @@ let jsxMapper () =
584584
{ ppat_desc = Ppat_construct ({ txt = Lident "()" }, _) | Ppat_any },
585585
_internalExpression );
586586
} ->
587-
((fun a -> a), true, expression)
587+
((fun a -> a), true, false, expression)
588588
(* let make = (~prop) => ... *)
589589
| { pexp_desc = Pexp_fun ((Labelled _ | Optional _), _default, _pattern, _internalExpression) } ->
590-
((fun a -> a), false, unerasableIgnoreExp expression)
590+
((fun a -> a), false, false, unerasableIgnoreExp expression)
591591
(* let make = (prop) => ... *)
592592
| { pexp_desc = Pexp_fun (_nolabel, _default, pattern, _internalExpression) } ->
593-
if hasApplication.contents then ((fun a -> a), false, unerasableIgnoreExp expression)
593+
if !hasApplication then ((fun a -> a), false, false, unerasableIgnoreExp expression)
594594
else
595595
Location.raise_errorf ~loc:pattern.ppat_loc
596596
"React: props need to be labelled arguments.\n\
@@ -599,28 +599,35 @@ let jsxMapper () =
599599
(* let make = {let foo = bar in (~prop) => ...} *)
600600
| { pexp_desc = Pexp_let (recursive, vbs, internalExpression) } ->
601601
(* here's where we spelunk! *)
602-
let wrap, hasUnit, exp = spelunkForFunExpression internalExpression in
603-
(wrap, hasUnit, { expression with pexp_desc = Pexp_let (recursive, vbs, exp) })
602+
let wrap, hasUnit, hasForwardRef, exp = spelunkForFunExpression internalExpression in
603+
(wrap, hasUnit, hasForwardRef, { expression with pexp_desc = Pexp_let (recursive, vbs, exp) })
604604
(* let make = React.forwardRef((~prop) => ...) *)
605605
| { pexp_desc = Pexp_apply (wrapperExpression, [ (Nolabel, internalExpression) ]) } ->
606606
let () = hasApplication := true in
607-
let _, hasUnit, exp = spelunkForFunExpression internalExpression in
608-
((fun exp -> Exp.apply wrapperExpression [ (nolabel, exp) ]), hasUnit, exp)
607+
let _, hasUnit, _, exp = spelunkForFunExpression internalExpression in
608+
let hasForwardRef = isForwardRef wrapperExpression in
609+
((fun exp -> Exp.apply wrapperExpression [ (nolabel, exp) ]), hasUnit, hasForwardRef, exp)
609610
| { pexp_desc = Pexp_sequence (wrapperExpression, internalExpression) } ->
610-
let wrap, hasUnit, exp = spelunkForFunExpression internalExpression in
611-
(wrap, hasUnit, { expression with pexp_desc = Pexp_sequence (wrapperExpression, exp) })
612-
| e -> ((fun a -> a), false, e)
611+
let wrap, hasUnit, hasForwardRef, exp = spelunkForFunExpression internalExpression in
612+
(wrap, hasUnit, hasForwardRef, { expression with pexp_desc = Pexp_sequence (wrapperExpression, exp) })
613+
| e -> ((fun a -> a), false, false, e)
613614
in
614-
let wrapExpression, hasUnit, expression = spelunkForFunExpression expression in
615-
(wrapExpressionWithBinding wrapExpression, hasUnit, expression)
615+
let wrapExpression, hasUnit, hasForwardRef, expression = spelunkForFunExpression expression in
616+
(wrapExpressionWithBinding wrapExpression, hasUnit, hasForwardRef, expression)
616617
in
617-
let bindingWrapper, _hasUnit, expression = modifiedBinding binding in
618+
let bindingWrapper, _hasUnit, hasForwardRef, expression = modifiedBinding binding in
618619
(* do stuff here! *)
619620
let namedArgList, _newtypes, _forwardRef =
620621
recursivelyTransformNamedArgsForMake mapper (modifiedBindingOld binding) [] []
621622
in
622623
let namedTypeList = List.fold_left argToType [] namedArgList in
624+
(* let _ = ref *)
623625
let vbIgnoreUnusedRef = Vb.mk (Pat.any ()) (Exp.ident (Location.mknoloc (Lident "ref"))) in
626+
(* let ref = ref->Js.Nullable.fromOption *)
627+
let vbRefFromOption = Vb.mk (Pat.var @@ Location.mknoloc "ref")
628+
(Exp.apply (Exp.ident (Location.mknoloc (Ldot (Ldot (Lident "Js", "Nullable"), "fromOption"))))
629+
[(Nolabel, Exp.ident (Location.mknoloc @@ Lident "ref"))])
630+
in
624631
let namedArgWithDefaultValueList = List.filter_map argWithDefaultValue namedArgList in
625632
let vbMatch ((label, default)) =
626633
Vb.mk (Pat.var (Location.mknoloc label))
@@ -640,19 +647,31 @@ let jsxMapper () =
640647
makePropsRecordType "props" emptyLoc
641648
((true, "key", [], keyType emptyLoc) :: (true, "ref", [], refType pstr_loc) :: namedTypeList)
642649
in
643-
let innerExpression = Exp.apply (Exp.ident (Location.mknoloc @@ Lident "make"))
644-
[(Nolabel, Exp.ident (Location.mknoloc @@ Lident "props"))]
650+
let innerExpression = if hasForwardRef then
651+
Exp.apply (Exp.ident @@ Location.mknoloc @@ Lident "make")
652+
[(Nolabel, Exp.record
653+
[ (Location.mknoloc @@ Lident "ref", Exp.apply ~attrs:optionalAttr
654+
(Exp.ident (Location.mknoloc (Ldot (Ldot (Lident "Js", "Nullable"), "toOption"))))
655+
[ (Nolabel, Exp.ident (Location.mknoloc @@ Lident "ref")) ])
656+
]
657+
(Some (Exp.ident (Location.mknoloc @@ Lident "props"))))]
658+
else Exp.apply (Exp.ident (Location.mknoloc @@ Lident "make"))
659+
[(Nolabel, Exp.ident (Location.mknoloc @@ Lident "props"))]
645660
in
646661
let fullExpression =
647662
(* React component name should start with uppercase letter *)
648663
(* let make = { let \"App" = props => make(props); \"App" } *)
664+
(* let make = React.forwardRef({
665+
let \"App" = (props, ref) => make({...props, ref: @optional (Js.Nullabel.toOption(ref))})
666+
})*)
649667
Exp.fun_ nolabel None
650668
(match namedTypeList with
651669
| [] -> (Pat.var @@ Location.mknoloc "props")
652670
| _ -> (Pat.constraint_
653671
(Pat.var @@ Location.mknoloc "props")
654672
(Typ.constr (Location.mknoloc @@ Lident "props")([Typ.any ()]))))
655-
innerExpression
673+
(if hasForwardRef then Exp.fun_ nolabel None (Pat.var @@ Location.mknoloc "ref") innerExpression
674+
else innerExpression)
656675
in
657676
let fullExpression =
658677
match fullModuleName with
@@ -667,7 +686,10 @@ let jsxMapper () =
667686
| Pexp_fun (_arg_label, _default, { ppat_desc = Ppat_construct ({ txt = Lident "()" }, _) | Ppat_any }, expr) ->
668687
(patterns, expr)
669688
| Pexp_fun (arg_label, _default, { ppat_loc }, expr) ->
670-
returnedExpression (({loc = ppat_loc; txt = Lident (getLabel arg_label)}, Pat.var { txt = getLabel arg_label; loc = ppat_loc}) :: patterns) expr
689+
if isLabelled arg_label || isOptional arg_label then
690+
returnedExpression (({loc = ppat_loc; txt = Lident (getLabel arg_label)}, Pat.var { txt = getLabel arg_label; loc = ppat_loc}) :: patterns) expr
691+
else
692+
returnedExpression patterns expr
671693
| _ -> (patterns, expr)
672694
in
673695
let patternsWithLid, expression = returnedExpression [] expression in
@@ -677,6 +699,7 @@ let jsxMapper () =
677699
let expression = if List.length vbMatchList = 0 then expression else (Exp.let_ Nonrecursive vbMatchList expression) in
678700
(* add let _ = ref to ignore unused warning *)
679701
let expression = Exp.let_ Nonrecursive [ vbIgnoreUnusedRef ] expression in
702+
let expression = Exp.let_ Nonrecursive [ vbRefFromOption ] expression in
680703
let expression = Exp.fun_ Nolabel None
681704
begin
682705
Pat.constraint_ pattern

tests/ppx/react/expected/commentAtTop.res.txt

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
1-
type props<'msg> = { // test React JSX file
2-
@optional key: string,
3-
@optional ref: React.ref<Js.Nullable.t<Dom.element>>,
4-
msg: 'msg,
5-
}
1+
type props<'msg> = {@optional key: string, @optional ref: ReactDOM.Ref.currentDomRef, msg: 'msg} // test React JSX file
62

73
let make = ({msg, ref}: props<'msg>) => {
4+
let ref = Js.Nullable.fromOption(ref)
85
let _ = ref
96

107
{

tests/ppx/react/expected/externalWithCustomName.res.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
module Foo = {
22
type props<'a, 'b> = {
33
@optional key: string,
4-
@optional ref: React.ref<Js.Nullable.t<Dom.element>>,
4+
@optional ref: ReactDOM.Ref.currentDomRef,
55
a: 'a,
66
b: 'b,
77
}

tests/ppx/react/expected/innerModule.res.txt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
module Bar = {
22
type props<'a, 'b> = {
33
@optional key: string,
4-
@optional ref: React.ref<Js.Nullable.t<Dom.element>>,
4+
@optional ref: ReactDOM.Ref.currentDomRef,
55
a: 'a,
66
b: 'b,
77
}
88
let make = ({a, b, ref}: props<'a, 'b>) => {
9+
let ref = Js.Nullable.fromOption(ref)
910
let _ = ref
1011

1112
Js.log("This function should be named `InnerModule.react$Bar`")
@@ -17,12 +18,13 @@ module Bar = {
1718
}
1819
type props<'a, 'b> = {
1920
@optional key: string,
20-
@optional ref: React.ref<Js.Nullable.t<Dom.element>>,
21+
@optional ref: ReactDOM.Ref.currentDomRef,
2122
a: 'a,
2223
b: 'b,
2324
}
2425

2526
let component = ({a, b, ref}: props<'a, 'b>) => {
27+
let ref = Js.Nullable.fromOption(ref)
2628
let _ = ref
2729

2830
Js.log("This function should be named `InnerModule.react$Bar$component`")

tests/ppx/react/expected/newtype.res.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
type props<'a, 'b, 'c> = {
22
@optional key: string,
3-
@optional ref: React.ref<Js.Nullable.t<Dom.element>>,
3+
@optional ref: ReactDOM.Ref.currentDomRef,
44
a: 'a,
55
b: 'b,
66
c: 'c,
77
}
88
let make = ({ref}: props<'a, 'b, 'c>) => {
9+
let ref = Js.Nullable.fromOption(ref)
910
let _ = ref
1011
(type a, ~a: a, ~b: array<option<[#Foo(a)]>>, ~c: 'a, _) =>
1112
ReactDOMRe.createDOMElementVariadic("div", [])

tests/ppx/react/expected/topLevel.res.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
type props<'a, 'b> = {
22
@optional key: string,
3-
@optional ref: React.ref<Js.Nullable.t<Dom.element>>,
3+
@optional ref: ReactDOM.Ref.currentDomRef,
44
a: 'a,
55
b: 'b,
66
}
77
let make = ({a, b, ref}: props<'a, 'b>) => {
8+
let ref = Js.Nullable.fromOption(ref)
89
let _ = ref
910

1011
Js.log("This function should be named 'TopLevel.react'")

tests/ppx/react/expected/typeConstraint.res.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
type props<'a, 'b> = {
22
@optional key: string,
3-
@optional ref: React.ref<Js.Nullable.t<Dom.element>>,
3+
@optional ref: ReactDOM.Ref.currentDomRef,
44
a: 'a,
55
b: 'b,
66
}
77
let make: 'a. (~a: 'a, ~b: 'a, 'a) => React.element = ({ref}: props<'a, 'b>) => {
8+
let ref = Js.Nullable.fromOption(ref)
89
let _ = ref
910
(type a): ((~a: a, ~b: a, a) => React.element) =>
1011
(~a, ~b, _) => ReactDOMRe.createDOMElementVariadic("div", [])

0 commit comments

Comments
 (0)