From 4a240114153ab50d2048fc44486666967f383604 Mon Sep 17 00:00:00 2001 From: Woonki Moon Date: Mon, 27 Jun 2022 20:05:38 +0900 Subject: [PATCH 01/94] add pattern constraint in destructuring args --- cli/reactjs_jsx_ppx_v4.ml | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/cli/reactjs_jsx_ppx_v4.ml b/cli/reactjs_jsx_ppx_v4.ml index 544086f5..47735973 100644 --- a/cli/reactjs_jsx_ppx_v4.ml +++ b/cli/reactjs_jsx_ppx_v4.ml @@ -985,8 +985,20 @@ let transformComponentDefinition nestedModules mapper structure returnStructures | Pexp_fun (arg_label, _default, {ppat_loc}, expr) -> if isLabelled arg_label || isOptional arg_label then returnedExpression - (( {loc = ppat_loc; txt = Lident (getLabel arg_label)}, - Pat.var {txt = getLabel arg_label; loc = ppat_loc} ) + ((if isOptional arg_label then + (* { name: @optional name } *) + ( {loc = ppat_loc; txt = Lident (getLabel arg_label)}, + Pat.constraint_ + (Pat.var {txt = getLabel arg_label; loc = ppat_loc}) + (Typ.constr ~attrs:optionalAttr + { + txt = Lident (getLabel arg_label); + loc = Location.none; + } + []) ) + else + ( {loc = ppat_loc; txt = Lident (getLabel arg_label)}, + Pat.var {txt = getLabel arg_label; loc = ppat_loc} )) :: patterns) expr else returnedExpression patterns expr @@ -1005,7 +1017,7 @@ let transformComponentDefinition nestedModules mapper structure returnStructures let pattern = match patternsWithLid with | [] -> Pat.any () - | _ -> Pat.record patternsWithLid Closed + | _ -> Pat.record patternsWithLid Open in (* add patttern matching for optional prop value *) let expression = From b1706cc27d0a4e14a5bb8d957038f8ce7fe2cd97 Mon Sep 17 00:00:00 2001 From: Woonki Moon Date: Mon, 27 Jun 2022 23:51:59 +0900 Subject: [PATCH 02/94] fix pattern in destructuring args --- cli/reactjs_jsx_ppx_v4.ml | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/cli/reactjs_jsx_ppx_v4.ml b/cli/reactjs_jsx_ppx_v4.ml index 47735973..f9a490c6 100644 --- a/cli/reactjs_jsx_ppx_v4.ml +++ b/cli/reactjs_jsx_ppx_v4.ml @@ -988,14 +988,8 @@ let transformComponentDefinition nestedModules mapper structure returnStructures ((if isOptional arg_label then (* { name: @optional name } *) ( {loc = ppat_loc; txt = Lident (getLabel arg_label)}, - Pat.constraint_ - (Pat.var {txt = getLabel arg_label; loc = ppat_loc}) - (Typ.constr ~attrs:optionalAttr - { - txt = Lident (getLabel arg_label); - loc = Location.none; - } - []) ) + Pat.var ~attrs:optionalAttr + {txt = getLabel arg_label; loc = ppat_loc} ) else ( {loc = ppat_loc; txt = Lident (getLabel arg_label)}, Pat.var {txt = getLabel arg_label; loc = ppat_loc} )) From e2b588368d98ea4c108df0598618d9658f70b5d4 Mon Sep 17 00:00:00 2001 From: Woonki Moon Date: Tue, 28 Jun 2022 07:41:16 +0900 Subject: [PATCH 03/94] new jsx transform for lower case --- cli/reactjs_jsx_ppx_v4.ml | 176 ++++++++++++++++++++++++++------------ 1 file changed, 119 insertions(+), 57 deletions(-) diff --git a/cli/reactjs_jsx_ppx_v4.ml b/cli/reactjs_jsx_ppx_v4.ml index f9a490c6..3b3cfd56 100644 --- a/cli/reactjs_jsx_ppx_v4.ml +++ b/cli/reactjs_jsx_ppx_v4.ml @@ -388,15 +388,19 @@ let transformUppercaseCall3 jsxRuntime modulePath mapper loc attrs let jsxExpr, key = match (!childrenArg, keyProp) with | None, (_, keyExpr) :: _ -> - ( Exp.ident ~loc {loc; txt = Ldot (Lident "React", "jsxKeyed")}, + ( Exp.ident ~loc + {loc; txt = Ldot (Ldot (Lident "Js", "React"), "jsxKeyed")}, [(nolabel, keyExpr)] ) | None, [] -> - (Exp.ident ~loc {loc; txt = Ldot (Lident "React", "jsx")}, []) + ( Exp.ident ~loc {loc; txt = Ldot (Ldot (Lident "Js", "React"), "jsx")}, + [] ) | Some _, (_, keyExpr) :: _ -> - ( Exp.ident ~loc {loc; txt = Ldot (Lident "React", "jsxsKeyed")}, + ( Exp.ident ~loc + {loc; txt = Ldot (Ldot (Lident "Js", "React"), "jsxsKeyed")}, [(nolabel, keyExpr)] ) | Some _, [] -> - (Exp.ident ~loc {loc; txt = Ldot (Lident "React", "jsxs")}, []) + ( Exp.ident ~loc {loc; txt = Ldot (Ldot (Lident "Js", "React"), "jsxs")}, + [] ) in Exp.apply ~loc ~attrs jsxExpr ([(nolabel, Exp.ident ~loc {txt = ident; loc}); (nolabel, props)] @ key) @@ -425,61 +429,119 @@ let transformUppercaseCall3 jsxRuntime modulePath mapper loc attrs ]) [@@raises Invalid_argument] -let transformLowercaseCall3 _jsxRuntime mapper loc attrs _callExpression +let transformLowercaseCall3 jsxRuntime mapper loc attrs callExpression callArguments id = - let children, nonChildrenProps = extractChildren ~loc callArguments in - (* keep the v3 *) - (* let record = recordFromProps callExpression nonChildrenProps in *) let componentNameExpr = constantString ~loc id in - let childrenExpr = transformChildrenIfList ~loc ~mapper children in - let createElementCall = - match children with - (* [@JSX] div(~children=[a]), coming from
a
*) - | { - pexp_desc = - ( Pexp_construct ({txt = Lident "::"}, Some {pexp_desc = Pexp_tuple _}) - | Pexp_construct ({txt = Lident "[]"}, None) ); - } -> - "createDOMElementVariadic" - (* [@JSX] div(~children= value), coming from
...(value)
*) - | _ -> - raise - (Invalid_argument - "A spread as a DOM element's children don't make sense written \ - together. You can simply remove the spread.") - in - let args = - match nonChildrenProps with - | [_justTheUnitArgumentAtEnd] -> - [ - (* "div" *) - (nolabel, componentNameExpr); - (* [|moreCreateElementCallsHere|] *) - (nolabel, childrenExpr); - ] - | nonEmptyProps -> - let propsCall = - Exp.apply ~loc - (Exp.ident ~loc {loc; txt = Ldot (Lident "ReactDOMRe", "domProps")}) - (nonEmptyProps - |> List.map (fun (label, expression) -> - (label, mapper.expr mapper expression))) - in - [ - (* "div" *) - (nolabel, componentNameExpr); - (* ReactDOMRe.domProps(~className=blabla, ~foo=bar, ()) *) - (labelled "props", propsCall); - (* [|moreCreateElementCallsHere|] *) - (nolabel, childrenExpr); - ] - in - Exp.apply - ~loc (* throw away the [@JSX] attribute and keep the others, if any *) - ~attrs - (* ReactDOMRe.createElement *) - (Exp.ident ~loc {loc; txt = Ldot (Lident "ReactDOMRe", createElementCall)}) - args + match jsxRuntime with + (* the new jsx transform *) + | "automatic" -> + let children, nonChildrenProps = + extractChildren ~removeLastPositionUnit:true ~loc callArguments + in + let argsForMake = nonChildrenProps in + let childrenExpr = transformChildrenIfListUpper ~loc ~mapper children in + let recursivelyTransformedArgsForMake = + argsForMake + |> List.map (fun (label, expression) -> + (label, mapper.expr mapper expression)) + in + let childrenArg = ref None in + let args = + recursivelyTransformedArgsForMake + @ + match childrenExpr with + | Exact children -> [(labelled "children", children)] + | ListLiteral {pexp_desc = Pexp_array list} when list = [] -> [] + | ListLiteral expression -> + (* this is a hack to support react components that introspect into their children *) + childrenArg := Some expression; + [(labelled "children", expression)] + in + let isEmptyRecord {pexp_desc} = + match pexp_desc with + | Pexp_record (labelDecls, _) when List.length labelDecls = 0 -> true + | _ -> false + in + let record = recordFromProps ~removeKey:true callExpression args in + let props = + if isEmptyRecord record then recordWithOnlyKey ~loc else record + in + let keyProp = + args |> List.filter (fun (arg_label, _) -> "key" = getLabel arg_label) + in + let jsxExpr, key = + match (!childrenArg, keyProp) with + | None, (_, keyExpr) :: _ -> + ( Exp.ident ~loc + {loc; txt = Ldot (Ldot (Lident "Js", "React"), "jsxKeyedDom")}, + [(nolabel, keyExpr)] ) + | None, [] -> + ( Exp.ident ~loc + {loc; txt = Ldot (Ldot (Lident "Js", "React"), "jsxDom")}, + [] ) + | Some _, (_, keyExpr) :: _ -> + ( Exp.ident ~loc + {loc; txt = Ldot (Ldot (Lident "Js", "React"), "jsxsKeyedDom")}, + [(nolabel, keyExpr)] ) + | Some _, [] -> + ( Exp.ident ~loc + {loc; txt = Ldot (Ldot (Lident "Js", "React"), "jsxsDom")}, + [] ) + in + Exp.apply ~loc ~attrs jsxExpr + ([(nolabel, componentNameExpr); (nolabel, props)] @ key) + | _ -> + let children, nonChildrenProps = extractChildren ~loc callArguments in + let childrenExpr = transformChildrenIfList ~loc ~mapper children in + let createElementCall = + match children with + (* [@JSX] div(~children=[a]), coming from
a
*) + | { + pexp_desc = + ( Pexp_construct ({txt = Lident "::"}, Some {pexp_desc = Pexp_tuple _}) + | Pexp_construct ({txt = Lident "[]"}, None) ); + } -> + "createDOMElementVariadic" + (* [@JSX] div(~children= value), coming from
...(value)
*) + | _ -> + raise + (Invalid_argument + "A spread as a DOM element's children don't make sense written \ + together. You can simply remove the spread.") + in + let args = + match nonChildrenProps with + | [_justTheUnitArgumentAtEnd] -> + [ + (* "div" *) + (nolabel, componentNameExpr); + (* [|moreCreateElementCallsHere|] *) + (nolabel, childrenExpr); + ] + | nonEmptyProps -> + let propsCall = + Exp.apply ~loc + (Exp.ident ~loc {loc; txt = Ldot (Lident "ReactDOMRe", "domProps")}) + (nonEmptyProps + |> List.map (fun (label, expression) -> + (label, mapper.expr mapper expression))) + in + [ + (* "div" *) + (nolabel, componentNameExpr); + (* ReactDOMRe.domProps(~className=blabla, ~foo=bar, ()) *) + (labelled "props", propsCall); + (* [|moreCreateElementCallsHere|] *) + (nolabel, childrenExpr); + ] + in + Exp.apply + ~loc (* throw away the [@JSX] attribute and keep the others, if any *) + ~attrs + (* ReactDOMRe.createElement *) + (Exp.ident ~loc + {loc; txt = Ldot (Lident "ReactDOMRe", createElementCall)}) + args [@@raises Invalid_argument] let rec recursivelyTransformNamedArgsForMake mapper expr args newtypes = From 7e73183030878c20cf2c7b36babd590178835f44 Mon Sep 17 00:00:00 2001 From: Woonki Moon Date: Tue, 28 Jun 2022 07:45:23 +0900 Subject: [PATCH 04/94] clean up applying optional attribute --- cli/reactjs_jsx_ppx_v4.ml | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/cli/reactjs_jsx_ppx_v4.ml b/cli/reactjs_jsx_ppx_v4.ml index 3b3cfd56..37b4300f 100644 --- a/cli/reactjs_jsx_ppx_v4.ml +++ b/cli/reactjs_jsx_ppx_v4.ml @@ -1047,14 +1047,10 @@ let transformComponentDefinition nestedModules mapper structure returnStructures | Pexp_fun (arg_label, _default, {ppat_loc}, expr) -> if isLabelled arg_label || isOptional arg_label then returnedExpression - ((if isOptional arg_label then - (* { name: @optional name } *) - ( {loc = ppat_loc; txt = Lident (getLabel arg_label)}, - Pat.var ~attrs:optionalAttr - {txt = getLabel arg_label; loc = ppat_loc} ) - else - ( {loc = ppat_loc; txt = Lident (getLabel arg_label)}, - Pat.var {txt = getLabel arg_label; loc = ppat_loc} )) + (( {loc = ppat_loc; txt = Lident (getLabel arg_label)}, + Pat.var + ~attrs:(if isOptional arg_label then optionalAttr else []) + {txt = getLabel arg_label; loc = ppat_loc} ) :: patterns) expr else returnedExpression patterns expr From 1d820d8ebe83d7a96f6817f9905a0e5507eb6c68 Mon Sep 17 00:00:00 2001 From: Woonki Moon Date: Tue, 28 Jun 2022 07:58:49 +0900 Subject: [PATCH 05/94] fix tests --- .../expected/commentAtTop.res_v4_auto.txt | 4 +- .../expected/commentAtTop.res_v4_cls.txt | 2 +- .../externalWithCustomName.res_v4_auto.txt | 2 +- .../react/expected/forwardRef.res_v4_auto.txt | 37 +++++++++++-------- .../react/expected/forwardRef.res_v4_cls.txt | 2 +- .../expected/innerModule.res_v4_auto.txt | 8 ++-- .../react/expected/innerModule.res_v4_cls.txt | 4 +- .../react/expected/newtype.res_v4_auto.txt | 2 +- .../react/expected/topLevel.res_v4_auto.txt | 4 +- .../react/expected/topLevel.res_v4_cls.txt | 2 +- .../expected/typeConstraint.res_v4_auto.txt | 2 +- 11 files changed, 37 insertions(+), 32 deletions(-) diff --git a/tests/ppx/react/expected/commentAtTop.res_v4_auto.txt b/tests/ppx/react/expected/commentAtTop.res_v4_auto.txt index 995073bc..034385c0 100644 --- a/tests/ppx/react/expected/commentAtTop.res_v4_auto.txt +++ b/tests/ppx/react/expected/commentAtTop.res_v4_auto.txt @@ -1,7 +1,7 @@ type props<'msg> = {key?: string, msg: 'msg} // test React JSX file -let make = ({msg}: props<'msg>) => { - ReactDOMRe.createDOMElementVariadic("div", [{msg->React.string}]) +let make = ({msg, _}: props<'msg>) => { + Js.React.jsxDom("div", {children: {msg->React.string}}) } let make = { let \"CommentAtTop" = (props: props<_>) => make(props) diff --git a/tests/ppx/react/expected/commentAtTop.res_v4_cls.txt b/tests/ppx/react/expected/commentAtTop.res_v4_cls.txt index 995073bc..65c67e50 100644 --- a/tests/ppx/react/expected/commentAtTop.res_v4_cls.txt +++ b/tests/ppx/react/expected/commentAtTop.res_v4_cls.txt @@ -1,6 +1,6 @@ type props<'msg> = {key?: string, msg: 'msg} // test React JSX file -let make = ({msg}: props<'msg>) => { +let make = ({msg, _}: props<'msg>) => { ReactDOMRe.createDOMElementVariadic("div", [{msg->React.string}]) } let make = { diff --git a/tests/ppx/react/expected/externalWithCustomName.res_v4_auto.txt b/tests/ppx/react/expected/externalWithCustomName.res_v4_auto.txt index 49e3e620..44c2f01a 100644 --- a/tests/ppx/react/expected/externalWithCustomName.res_v4_auto.txt +++ b/tests/ppx/react/expected/externalWithCustomName.res_v4_auto.txt @@ -4,4 +4,4 @@ module Foo = { external component: React.componentLike, React.element> = "component" } -let t = React.jsx(Foo.component, {a: 1, b: "1"}) +let t = Js.React.jsx(Foo.component, {a: 1, b: "1"}) diff --git a/tests/ppx/react/expected/forwardRef.res_v4_auto.txt b/tests/ppx/react/expected/forwardRef.res_v4_auto.txt index ce4b16bb..8b950112 100644 --- a/tests/ppx/react/expected/forwardRef.res_v4_auto.txt +++ b/tests/ppx/react/expected/forwardRef.res_v4_auto.txt @@ -5,25 +5,25 @@ module FancyInput = { children: 'children, ref?: ReactDOM.Ref.currentDomRef, } - let make = ({className, children, ref}: props<'className, 'children>) => { + let make = ({?className, children, ref, _}: props<'className, 'children>) => { let ref = Js.Nullable.fromOption(ref) let _ = ref - ReactDOMRe.createDOMElementVariadic( + Js.React.jsxsDom( "div", - [ - ReactDOMRe.createDOMElementVariadic( - "input", - ~props=ReactDOMRe.domProps( - ~type_="text", - ~className?, - ~ref=?{Js.Nullable.toOption(ref)->Belt.Option.map(ReactDOM.Ref.domRef)}, - (), + { + children: [ + Js.React.jsxDom( + "input", + { + type_: "text", + ?className, + ref: ?Js.Nullable.toOption(ref)->Belt.Option.map(ReactDOM.Ref.domRef), + }, ), - [], - ), - children, - ], + children, + ], + }, ) } let make = React.forwardRef({ @@ -37,9 +37,14 @@ type props = {key?: string} let make = (_: props) => { let input = React.useRef(Js.Nullable.null) - ReactDOMRe.createDOMElementVariadic( + Js.React.jsxDom( "div", - [React.jsx(FancyInput.make, {ref: input, children: {React.string("Click to focus")}})], + { + children: Js.React.jsx( + FancyInput.make, + {ref: input, children: {React.string("Click to focus")}}, + ), + }, ) } let make = { diff --git a/tests/ppx/react/expected/forwardRef.res_v4_cls.txt b/tests/ppx/react/expected/forwardRef.res_v4_cls.txt index 872e550b..0ae05c54 100644 --- a/tests/ppx/react/expected/forwardRef.res_v4_cls.txt +++ b/tests/ppx/react/expected/forwardRef.res_v4_cls.txt @@ -5,7 +5,7 @@ module FancyInput = { children: 'children, ref?: ReactDOM.Ref.currentDomRef, } - let make = ({className, children, ref}: props<'className, 'children>) => { + let make = ({?className, children, ref, _}: props<'className, 'children>) => { let ref = Js.Nullable.fromOption(ref) let _ = ref diff --git a/tests/ppx/react/expected/innerModule.res_v4_auto.txt b/tests/ppx/react/expected/innerModule.res_v4_auto.txt index ca414b0e..7a4ea4ff 100644 --- a/tests/ppx/react/expected/innerModule.res_v4_auto.txt +++ b/tests/ppx/react/expected/innerModule.res_v4_auto.txt @@ -1,8 +1,8 @@ module Bar = { type props<'a, 'b> = {key?: string, a: 'a, b: 'b} - let make = ({a, b}: props<'a, 'b>) => { + let make = ({a, b, _}: props<'a, 'b>) => { Js.log("This function should be named `InnerModule.react$Bar`") - ReactDOMRe.createDOMElementVariadic("div", []) + Js.React.jsxDom("div", {key: ?None}) } let make = { let \"InnerModule$Bar" = (props: props<_>) => make(props) @@ -10,9 +10,9 @@ module Bar = { } type props<'a, 'b> = {key?: string, a: 'a, b: 'b} - let component = ({a, b}: props<'a, 'b>) => { + let component = ({a, b, _}: props<'a, 'b>) => { Js.log("This function should be named `InnerModule.react$Bar$component`") - ReactDOMRe.createDOMElementVariadic("div", []) + Js.React.jsxDom("div", {key: ?None}) } let component = { let \"InnerModule$Bar$component" = (props: props<_>) => make(props) diff --git a/tests/ppx/react/expected/innerModule.res_v4_cls.txt b/tests/ppx/react/expected/innerModule.res_v4_cls.txt index ca414b0e..acbf4822 100644 --- a/tests/ppx/react/expected/innerModule.res_v4_cls.txt +++ b/tests/ppx/react/expected/innerModule.res_v4_cls.txt @@ -1,6 +1,6 @@ module Bar = { type props<'a, 'b> = {key?: string, a: 'a, b: 'b} - let make = ({a, b}: props<'a, 'b>) => { + let make = ({a, b, _}: props<'a, 'b>) => { Js.log("This function should be named `InnerModule.react$Bar`") ReactDOMRe.createDOMElementVariadic("div", []) } @@ -10,7 +10,7 @@ module Bar = { } type props<'a, 'b> = {key?: string, a: 'a, b: 'b} - let component = ({a, b}: props<'a, 'b>) => { + let component = ({a, b, _}: props<'a, 'b>) => { Js.log("This function should be named `InnerModule.react$Bar$component`") ReactDOMRe.createDOMElementVariadic("div", []) } diff --git a/tests/ppx/react/expected/newtype.res_v4_auto.txt b/tests/ppx/react/expected/newtype.res_v4_auto.txt index 460c8382..900026ce 100644 --- a/tests/ppx/react/expected/newtype.res_v4_auto.txt +++ b/tests/ppx/react/expected/newtype.res_v4_auto.txt @@ -1,6 +1,6 @@ type props<'a, 'b, 'c> = {key?: string, a: 'a, b: 'b, c: 'c} let make = (_: props<'a, 'b, 'c>, type a, ~a: a, ~b: array>, ~c: 'a, _) => - ReactDOMRe.createDOMElementVariadic("div", []) + Js.React.jsxDom("div", {key: ?None}) let make = { let \"Newtype" = (props: props<_>) => make(props) \"Newtype" diff --git a/tests/ppx/react/expected/topLevel.res_v4_auto.txt b/tests/ppx/react/expected/topLevel.res_v4_auto.txt index f05a7b1d..82b12972 100644 --- a/tests/ppx/react/expected/topLevel.res_v4_auto.txt +++ b/tests/ppx/react/expected/topLevel.res_v4_auto.txt @@ -1,7 +1,7 @@ type props<'a, 'b> = {key?: string, a: 'a, b: 'b} -let make = ({a, b}: props<'a, 'b>) => { +let make = ({a, b, _}: props<'a, 'b>) => { Js.log("This function should be named 'TopLevel.react'") - ReactDOMRe.createDOMElementVariadic("div", []) + Js.React.jsxDom("div", {key: ?None}) } let make = { let \"TopLevel" = (props: props<_>) => make(props) diff --git a/tests/ppx/react/expected/topLevel.res_v4_cls.txt b/tests/ppx/react/expected/topLevel.res_v4_cls.txt index f05a7b1d..e4cc45c7 100644 --- a/tests/ppx/react/expected/topLevel.res_v4_cls.txt +++ b/tests/ppx/react/expected/topLevel.res_v4_cls.txt @@ -1,5 +1,5 @@ type props<'a, 'b> = {key?: string, a: 'a, b: 'b} -let make = ({a, b}: props<'a, 'b>) => { +let make = ({a, b, _}: props<'a, 'b>) => { Js.log("This function should be named 'TopLevel.react'") ReactDOMRe.createDOMElementVariadic("div", []) } diff --git a/tests/ppx/react/expected/typeConstraint.res_v4_auto.txt b/tests/ppx/react/expected/typeConstraint.res_v4_auto.txt index 50156ade..17363a9b 100644 --- a/tests/ppx/react/expected/typeConstraint.res_v4_auto.txt +++ b/tests/ppx/react/expected/typeConstraint.res_v4_auto.txt @@ -1,7 +1,7 @@ type props<'a, 'b> = {key?: string, a: 'a, b: 'b} let make: 'a. (~a: 'a, ~b: 'a, 'a) => React.element = (_: props<'a, 'b>, type a): ( (~a: a, ~b: a, a) => React.element -) => (~a, ~b, _) => ReactDOMRe.createDOMElementVariadic("div", []) +) => (~a, ~b, _) => Js.React.jsxDom("div", {key: ?None}) let make = { let \"TypeConstraint" = (props: props<_>) => make(props) \"TypeConstraint" From d8bec6582686021069e756f29b9b07d0e8acb0c9 Mon Sep 17 00:00:00 2001 From: Woonki Moon Date: Tue, 28 Jun 2022 13:18:37 +0900 Subject: [PATCH 06/94] update ppx v4 spec with new jsx transform --- cli/JSXV4.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/cli/JSXV4.md b/cli/JSXV4.md index 2d463c53..015f144f 100644 --- a/cli/JSXV4.md +++ b/cli/JSXV4.md @@ -70,6 +70,34 @@ React.createElement(Comp.make, {x, y:7, @optional z}) React.createElement(Comp.make, {x, key: "7"}) ``` +**The new JSX transform** + +The JSX PPX V4 supports [the new JSX transform](https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html) of React.js. + +It affects only the application. + +```rescript + +// is converted to +Js.React.jsx(Comp.make, {x}) +``` + +```rescript +
+// is converted to +Js.React.jsxDom("div", { name: "div" }) +``` + +The props type of dom elements, e.g. `div`, is inferred to `Js.React.domProps`. + +```rescript +type domProps = { + key?: string, + id?: string, + ... +} +``` + **Interface And External** ```rescript From 3d1aee35fb7681a96a52be372a448dfb7439ee5c Mon Sep 17 00:00:00 2001 From: Woonki Moon Date: Wed, 29 Jun 2022 10:59:14 +0900 Subject: [PATCH 07/94] add JSX V4 upgrade doc --- cli/JSXV4UPGRADE.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 cli/JSXV4UPGRADE.md diff --git a/cli/JSXV4UPGRADE.md b/cli/JSXV4UPGRADE.md new file mode 100644 index 00000000..b1cfd68e --- /dev/null +++ b/cli/JSXV4UPGRADE.md @@ -0,0 +1,44 @@ +**JSX V4 upgrade path** + +1. No upgrading rescript-react && no changes in bsconfig.json + + JSX V3 by default. + +2. No upgrading rescript-react && new configuration added in bsconfig.json + + a. JSX V3 + + ```json + "react": { + "jsx": 3, + "runtime": "classic" + } + ``` + + The `react.runtime` affects nothing, no matter of `"classic"` or `"automatic"`. + + b. JSX V4 with classic mode + + ```json + "react": { + "jsx": 4, + "runtime": "classic" + } + ``` + + c. JSX V4 with new JSX mode (Experimental) + + ```json + "react": { + "jsx": 4, + "runtime": "automatic" + } + ``` + + JSX V4 with `Js.React` which needs the peer-dependecy of React v17.\* or higher. It may break the project with dependencies which are using the explicit types of `rescript-react`. + + > The existing configuration `reason.react-jsx` will be ignored by the new one. + +3. Upgrading rescript-react + +JSX V3 and V4 will work with the upgraded rescript-react. The new JSX mode is no longer the experimental feature. The new JSX mode will be triggered by `react.runtime` configuration in bsconfig.json. From 0fa928f29c3d56371a61030a0c6b4edc57926c27 Mon Sep 17 00:00:00 2001 From: woonki Date: Wed, 29 Jun 2022 23:53:16 +0900 Subject: [PATCH 08/94] Update cli/JSXV4UPGRADE.md Co-authored-by: Patrick Ecker --- cli/JSXV4UPGRADE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/JSXV4UPGRADE.md b/cli/JSXV4UPGRADE.md index b1cfd68e..b454aa46 100644 --- a/cli/JSXV4UPGRADE.md +++ b/cli/JSXV4UPGRADE.md @@ -15,7 +15,7 @@ } ``` - The `react.runtime` affects nothing, no matter of `"classic"` or `"automatic"`. + **Note:** When using `jsx` v3, the `runtime` option will be ignored. b. JSX V4 with classic mode From ffa3127f154fcd52dc72bd2f65e40b18b541f378 Mon Sep 17 00:00:00 2001 From: Woonki Moon Date: Thu, 30 Jun 2022 01:57:40 +0900 Subject: [PATCH 09/94] apply Js.React.array fn to children --- cli/reactjs_jsx_ppx_v4.ml | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/cli/reactjs_jsx_ppx_v4.ml b/cli/reactjs_jsx_ppx_v4.ml index 37b4300f..dcb47053 100644 --- a/cli/reactjs_jsx_ppx_v4.ml +++ b/cli/reactjs_jsx_ppx_v4.ml @@ -346,7 +346,17 @@ let transformUppercaseCall3 jsxRuntime modulePath mapper loc attrs (* this is a hack to support react components that introspect into their children *) childrenArg := Some expression; match jsxRuntime with - | "automatic" -> [(labelled "children", expression)] + | "automatic" -> + [ + ( labelled "children", + Exp.apply + (Exp.ident + { + txt = Ldot (Ldot (Lident "Js", "React"), "array"); + loc = Location.none; + }) + [(Nolabel, expression)] ); + ] | _ -> [ ( labelled "children", @@ -455,7 +465,16 @@ let transformLowercaseCall3 jsxRuntime mapper loc attrs callExpression | ListLiteral expression -> (* this is a hack to support react components that introspect into their children *) childrenArg := Some expression; - [(labelled "children", expression)] + [ + ( labelled "children", + Exp.apply + (Exp.ident + { + txt = Ldot (Ldot (Lident "Js", "React"), "array"); + loc = Location.none; + }) + [(Nolabel, expression)] ); + ] in let isEmptyRecord {pexp_desc} = match pexp_desc with From 76fff14c9da26596831f82eca4500f3832201174 Mon Sep 17 00:00:00 2001 From: Woonki Moon Date: Thu, 30 Jun 2022 01:59:06 +0900 Subject: [PATCH 10/94] fix test --- tests/ppx/react/expected/forwardRef.res_v4_auto.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ppx/react/expected/forwardRef.res_v4_auto.txt b/tests/ppx/react/expected/forwardRef.res_v4_auto.txt index 8b950112..ff561cbd 100644 --- a/tests/ppx/react/expected/forwardRef.res_v4_auto.txt +++ b/tests/ppx/react/expected/forwardRef.res_v4_auto.txt @@ -12,7 +12,7 @@ module FancyInput = { Js.React.jsxsDom( "div", { - children: [ + children: Js.React.array([ Js.React.jsxDom( "input", { @@ -22,7 +22,7 @@ module FancyInput = { }, ), children, - ], + ]), }, ) } From 73d7de9348bdb9acb9459e1aa1d40f5d45786fa1 Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Thu, 30 Jun 2022 17:48:57 +0200 Subject: [PATCH 11/94] Update JSXV4UPGRADE.md --- cli/JSXV4UPGRADE.md | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/cli/JSXV4UPGRADE.md b/cli/JSXV4UPGRADE.md index b454aa46..cf59ef9d 100644 --- a/cli/JSXV4UPGRADE.md +++ b/cli/JSXV4UPGRADE.md @@ -1,44 +1,40 @@ **JSX V4 upgrade path** -1. No upgrading rescript-react && no changes in bsconfig.json +1. No need to upgrade rescript-react or change bsconfig.json JSX V3 by default. -2. No upgrading rescript-react && new configuration added in bsconfig.json +2. New opt-in configuration added in bsconfig.json a. JSX V3 ```json - "react": { - "jsx": 3, + "jsx": { + "version": 3, "runtime": "classic" } ``` **Note:** When using `jsx` v3, the `runtime` option will be ignored. - b. JSX V4 with classic mode + b. JSX V4 with classic mode (generate calls to `React.createElement` just as with V3) ```json - "react": { - "jsx": 4, + "jsx": { + "version": 4, "runtime": "classic" } ``` - c. JSX V4 with new JSX mode (Experimental) + c. JSX V4 with experimental JSX mode (generate calls `jsx` functions) ```json - "react": { - "jsx": 4, + "jsx": { + "version": 4, "runtime": "automatic" } ``` - JSX V4 with `Js.React` which needs the peer-dependecy of React v17.\* or higher. It may break the project with dependencies which are using the explicit types of `rescript-react`. + JSX V4 with `"automatic"` runtme needs React v17.\* or higher as a peer dependency. It will require a new version of`rescript-react`. - > The existing configuration `reason.react-jsx` will be ignored by the new one. - -3. Upgrading rescript-react - -JSX V3 and V4 will work with the upgraded rescript-react. The new JSX mode is no longer the experimental feature. The new JSX mode will be triggered by `react.runtime` configuration in bsconfig.json. + > The existing configuration `reason.react-jsx` will be ignored if the new configuration is present. From 8b042ee755eb36af59037d1221ca527ead5b7513 Mon Sep 17 00:00:00 2001 From: Woonki Moon Date: Fri, 1 Jul 2022 00:01:06 +0900 Subject: [PATCH 12/94] new JSX mode --- cli/reactjs_jsx_ppx_v4.ml | 37 ++++++++++--------------------------- 1 file changed, 10 insertions(+), 27 deletions(-) diff --git a/cli/reactjs_jsx_ppx_v4.ml b/cli/reactjs_jsx_ppx_v4.ml index dcb47053..092feb35 100644 --- a/cli/reactjs_jsx_ppx_v4.ml +++ b/cli/reactjs_jsx_ppx_v4.ml @@ -351,10 +351,7 @@ let transformUppercaseCall3 jsxRuntime modulePath mapper loc attrs ( labelled "children", Exp.apply (Exp.ident - { - txt = Ldot (Ldot (Lident "Js", "React"), "array"); - loc = Location.none; - }) + {txt = Ldot (Lident "Jsx", "array"); loc = Location.none}) [(Nolabel, expression)] ); ] | _ -> @@ -398,19 +395,14 @@ let transformUppercaseCall3 jsxRuntime modulePath mapper loc attrs let jsxExpr, key = match (!childrenArg, keyProp) with | None, (_, keyExpr) :: _ -> - ( Exp.ident ~loc - {loc; txt = Ldot (Ldot (Lident "Js", "React"), "jsxKeyed")}, + ( Exp.ident ~loc {loc; txt = Ldot (Lident "Jsx", "jsxKeyed")}, [(nolabel, keyExpr)] ) - | None, [] -> - ( Exp.ident ~loc {loc; txt = Ldot (Ldot (Lident "Js", "React"), "jsx")}, - [] ) + | None, [] -> (Exp.ident ~loc {loc; txt = Ldot (Lident "Jsx", "jsx")}, []) | Some _, (_, keyExpr) :: _ -> - ( Exp.ident ~loc - {loc; txt = Ldot (Ldot (Lident "Js", "React"), "jsxsKeyed")}, + ( Exp.ident ~loc {loc; txt = Ldot (Lident "Jsx", "jsxsKeyed")}, [(nolabel, keyExpr)] ) | Some _, [] -> - ( Exp.ident ~loc {loc; txt = Ldot (Ldot (Lident "Js", "React"), "jsxs")}, - [] ) + (Exp.ident ~loc {loc; txt = Ldot (Lident "Jsx", "jsxs")}, []) in Exp.apply ~loc ~attrs jsxExpr ([(nolabel, Exp.ident ~loc {txt = ident; loc}); (nolabel, props)] @ key) @@ -469,10 +461,7 @@ let transformLowercaseCall3 jsxRuntime mapper loc attrs callExpression ( labelled "children", Exp.apply (Exp.ident - { - txt = Ldot (Ldot (Lident "Js", "React"), "array"); - loc = Location.none; - }) + {txt = Ldot (Lident "Jsx", "array"); loc = Location.none}) [(Nolabel, expression)] ); ] in @@ -491,21 +480,15 @@ let transformLowercaseCall3 jsxRuntime mapper loc attrs callExpression let jsxExpr, key = match (!childrenArg, keyProp) with | None, (_, keyExpr) :: _ -> - ( Exp.ident ~loc - {loc; txt = Ldot (Ldot (Lident "Js", "React"), "jsxKeyedDom")}, + ( Exp.ident ~loc {loc; txt = Ldot (Lident "JsxDOM", "jsxKeyed")}, [(nolabel, keyExpr)] ) | None, [] -> - ( Exp.ident ~loc - {loc; txt = Ldot (Ldot (Lident "Js", "React"), "jsxDom")}, - [] ) + (Exp.ident ~loc {loc; txt = Ldot (Lident "JsxDOM", "jsx")}, []) | Some _, (_, keyExpr) :: _ -> - ( Exp.ident ~loc - {loc; txt = Ldot (Ldot (Lident "Js", "React"), "jsxsKeyedDom")}, + ( Exp.ident ~loc {loc; txt = Ldot (Lident "JsxDOM", "jsxsKeyed")}, [(nolabel, keyExpr)] ) | Some _, [] -> - ( Exp.ident ~loc - {loc; txt = Ldot (Ldot (Lident "Js", "React"), "jsxsDom")}, - [] ) + (Exp.ident ~loc {loc; txt = Ldot (Lident "JsxDOM", "jsxs")}, []) in Exp.apply ~loc ~attrs jsxExpr ([(nolabel, componentNameExpr); (nolabel, props)] @ key) From 2e96188d5418d56c56980b05d5c449a5efd389c4 Mon Sep 17 00:00:00 2001 From: Woonki Moon Date: Fri, 1 Jul 2022 01:36:31 +0900 Subject: [PATCH 13/94] missing ns.optional for forwardRef --- cli/reactjs_jsx_ppx_v4.ml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cli/reactjs_jsx_ppx_v4.ml b/cli/reactjs_jsx_ppx_v4.ml index 092feb35..b3fa99b4 100644 --- a/cli/reactjs_jsx_ppx_v4.ml +++ b/cli/reactjs_jsx_ppx_v4.ml @@ -1064,7 +1064,8 @@ let transformComponentDefinition nestedModules mapper structure returnStructures @ if hasForwardRef then [ - (Location.mknoloc (Lident "ref"), Pat.var (Location.mknoloc "ref")); + ( Location.mknoloc (Lident "ref"), + Pat.var ~attrs:optionalAttr (Location.mknoloc "ref") ); ] else [] in From 57b7a74ae15ffa74ad029c045a5bb3d982d80e94 Mon Sep 17 00:00:00 2001 From: Woonki Moon Date: Fri, 1 Jul 2022 01:38:30 +0900 Subject: [PATCH 14/94] fix tests --- .../react/expected/commentAtTop.res_v4_auto.txt | 2 +- .../externalWithCustomName.res_v4_auto.txt | 2 +- .../react/expected/forwardRef.res_v4_auto.txt | 17 ++++++----------- .../react/expected/forwardRef.res_v4_cls.txt | 2 +- .../react/expected/innerModule.res_v4_auto.txt | 4 ++-- .../ppx/react/expected/newtype.res_v4_auto.txt | 2 +- .../ppx/react/expected/topLevel.res_v4_auto.txt | 2 +- .../expected/typeConstraint.res_v4_auto.txt | 2 +- 8 files changed, 14 insertions(+), 19 deletions(-) diff --git a/tests/ppx/react/expected/commentAtTop.res_v4_auto.txt b/tests/ppx/react/expected/commentAtTop.res_v4_auto.txt index 034385c0..b01bd2d6 100644 --- a/tests/ppx/react/expected/commentAtTop.res_v4_auto.txt +++ b/tests/ppx/react/expected/commentAtTop.res_v4_auto.txt @@ -1,7 +1,7 @@ type props<'msg> = {key?: string, msg: 'msg} // test React JSX file let make = ({msg, _}: props<'msg>) => { - Js.React.jsxDom("div", {children: {msg->React.string}}) + JsxDOM.jsx("div", {children: {msg->React.string}}) } let make = { let \"CommentAtTop" = (props: props<_>) => make(props) diff --git a/tests/ppx/react/expected/externalWithCustomName.res_v4_auto.txt b/tests/ppx/react/expected/externalWithCustomName.res_v4_auto.txt index 44c2f01a..695ea930 100644 --- a/tests/ppx/react/expected/externalWithCustomName.res_v4_auto.txt +++ b/tests/ppx/react/expected/externalWithCustomName.res_v4_auto.txt @@ -4,4 +4,4 @@ module Foo = { external component: React.componentLike, React.element> = "component" } -let t = Js.React.jsx(Foo.component, {a: 1, b: "1"}) +let t = Jsx.jsx(Foo.component, {a: 1, b: "1"}) diff --git a/tests/ppx/react/expected/forwardRef.res_v4_auto.txt b/tests/ppx/react/expected/forwardRef.res_v4_auto.txt index ff561cbd..b1e292a6 100644 --- a/tests/ppx/react/expected/forwardRef.res_v4_auto.txt +++ b/tests/ppx/react/expected/forwardRef.res_v4_auto.txt @@ -5,15 +5,15 @@ module FancyInput = { children: 'children, ref?: ReactDOM.Ref.currentDomRef, } - let make = ({?className, children, ref, _}: props<'className, 'children>) => { + let make = ({?className, children, ?ref, _}: props<'className, 'children>) => { let ref = Js.Nullable.fromOption(ref) let _ = ref - Js.React.jsxsDom( + JsxDOM.jsxs( "div", { - children: Js.React.array([ - Js.React.jsxDom( + children: Jsx.array([ + JsxDOM.jsx( "input", { type_: "text", @@ -37,14 +37,9 @@ type props = {key?: string} let make = (_: props) => { let input = React.useRef(Js.Nullable.null) - Js.React.jsxDom( + JsxDOM.jsx( "div", - { - children: Js.React.jsx( - FancyInput.make, - {ref: input, children: {React.string("Click to focus")}}, - ), - }, + {children: Jsx.jsx(FancyInput.make, {ref: input, children: {React.string("Click to focus")}})}, ) } let make = { diff --git a/tests/ppx/react/expected/forwardRef.res_v4_cls.txt b/tests/ppx/react/expected/forwardRef.res_v4_cls.txt index 0ae05c54..3998fda1 100644 --- a/tests/ppx/react/expected/forwardRef.res_v4_cls.txt +++ b/tests/ppx/react/expected/forwardRef.res_v4_cls.txt @@ -5,7 +5,7 @@ module FancyInput = { children: 'children, ref?: ReactDOM.Ref.currentDomRef, } - let make = ({?className, children, ref, _}: props<'className, 'children>) => { + let make = ({?className, children, ?ref, _}: props<'className, 'children>) => { let ref = Js.Nullable.fromOption(ref) let _ = ref diff --git a/tests/ppx/react/expected/innerModule.res_v4_auto.txt b/tests/ppx/react/expected/innerModule.res_v4_auto.txt index 7a4ea4ff..ff6683fa 100644 --- a/tests/ppx/react/expected/innerModule.res_v4_auto.txt +++ b/tests/ppx/react/expected/innerModule.res_v4_auto.txt @@ -2,7 +2,7 @@ module Bar = { type props<'a, 'b> = {key?: string, a: 'a, b: 'b} let make = ({a, b, _}: props<'a, 'b>) => { Js.log("This function should be named `InnerModule.react$Bar`") - Js.React.jsxDom("div", {key: ?None}) + JsxDOM.jsx("div", {key: ?None}) } let make = { let \"InnerModule$Bar" = (props: props<_>) => make(props) @@ -12,7 +12,7 @@ module Bar = { let component = ({a, b, _}: props<'a, 'b>) => { Js.log("This function should be named `InnerModule.react$Bar$component`") - Js.React.jsxDom("div", {key: ?None}) + JsxDOM.jsx("div", {key: ?None}) } let component = { let \"InnerModule$Bar$component" = (props: props<_>) => make(props) diff --git a/tests/ppx/react/expected/newtype.res_v4_auto.txt b/tests/ppx/react/expected/newtype.res_v4_auto.txt index 900026ce..2db0b64f 100644 --- a/tests/ppx/react/expected/newtype.res_v4_auto.txt +++ b/tests/ppx/react/expected/newtype.res_v4_auto.txt @@ -1,6 +1,6 @@ type props<'a, 'b, 'c> = {key?: string, a: 'a, b: 'b, c: 'c} let make = (_: props<'a, 'b, 'c>, type a, ~a: a, ~b: array>, ~c: 'a, _) => - Js.React.jsxDom("div", {key: ?None}) + JsxDOM.jsx("div", {key: ?None}) let make = { let \"Newtype" = (props: props<_>) => make(props) \"Newtype" diff --git a/tests/ppx/react/expected/topLevel.res_v4_auto.txt b/tests/ppx/react/expected/topLevel.res_v4_auto.txt index 82b12972..d9abd066 100644 --- a/tests/ppx/react/expected/topLevel.res_v4_auto.txt +++ b/tests/ppx/react/expected/topLevel.res_v4_auto.txt @@ -1,7 +1,7 @@ type props<'a, 'b> = {key?: string, a: 'a, b: 'b} let make = ({a, b, _}: props<'a, 'b>) => { Js.log("This function should be named 'TopLevel.react'") - Js.React.jsxDom("div", {key: ?None}) + JsxDOM.jsx("div", {key: ?None}) } let make = { let \"TopLevel" = (props: props<_>) => make(props) diff --git a/tests/ppx/react/expected/typeConstraint.res_v4_auto.txt b/tests/ppx/react/expected/typeConstraint.res_v4_auto.txt index 17363a9b..7e67486b 100644 --- a/tests/ppx/react/expected/typeConstraint.res_v4_auto.txt +++ b/tests/ppx/react/expected/typeConstraint.res_v4_auto.txt @@ -1,7 +1,7 @@ type props<'a, 'b> = {key?: string, a: 'a, b: 'b} let make: 'a. (~a: 'a, ~b: 'a, 'a) => React.element = (_: props<'a, 'b>, type a): ( (~a: a, ~b: a, a) => React.element -) => (~a, ~b, _) => Js.React.jsxDom("div", {key: ?None}) +) => (~a, ~b, _) => JsxDOM.jsx("div", {key: ?None}) let make = { let \"TypeConstraint" = (props: props<_>) => make(props) \"TypeConstraint" From d35d8cba3f5ca72757da56b9fa0bc373de767fad Mon Sep 17 00:00:00 2001 From: Woonki Moon Date: Sun, 3 Jul 2022 17:54:08 +0900 Subject: [PATCH 15/94] use React for new jsx transform, modify cli arg - use rescript-react for the new jsx transform - change cli arg, react-runtime -> jsx-mode - add cli arg, jsx-module --- cli/reactjs_jsx_ppx_v4.ml | 21 ++++++++-------- cli/res_cli.ml | 25 +++++++++++-------- scripts/test.sh | 4 +-- .../expected/commentAtTop.res_v4_auto.txt | 2 +- .../externalWithCustomName.res_v4_auto.txt | 2 +- .../react/expected/forwardRef.res_v4_auto.txt | 15 +++++++---- .../expected/innerModule.res_v4_auto.txt | 4 +-- .../react/expected/newtype.res_v4_auto.txt | 2 +- .../react/expected/topLevel.res_v4_auto.txt | 2 +- .../expected/typeConstraint.res_v4_auto.txt | 2 +- 10 files changed, 44 insertions(+), 35 deletions(-) diff --git a/cli/reactjs_jsx_ppx_v4.ml b/cli/reactjs_jsx_ppx_v4.ml index b3fa99b4..a53fc6e4 100644 --- a/cli/reactjs_jsx_ppx_v4.ml +++ b/cli/reactjs_jsx_ppx_v4.ml @@ -351,7 +351,7 @@ let transformUppercaseCall3 jsxRuntime modulePath mapper loc attrs ( labelled "children", Exp.apply (Exp.ident - {txt = Ldot (Lident "Jsx", "array"); loc = Location.none}) + {txt = Ldot (Lident "React", "array"); loc = Location.none}) [(Nolabel, expression)] ); ] | _ -> @@ -395,14 +395,15 @@ let transformUppercaseCall3 jsxRuntime modulePath mapper loc attrs let jsxExpr, key = match (!childrenArg, keyProp) with | None, (_, keyExpr) :: _ -> - ( Exp.ident ~loc {loc; txt = Ldot (Lident "Jsx", "jsxKeyed")}, + ( Exp.ident ~loc {loc; txt = Ldot (Lident "React", "jsxKeyed")}, [(nolabel, keyExpr)] ) - | None, [] -> (Exp.ident ~loc {loc; txt = Ldot (Lident "Jsx", "jsx")}, []) + | None, [] -> + (Exp.ident ~loc {loc; txt = Ldot (Lident "React", "jsx")}, []) | Some _, (_, keyExpr) :: _ -> - ( Exp.ident ~loc {loc; txt = Ldot (Lident "Jsx", "jsxsKeyed")}, + ( Exp.ident ~loc {loc; txt = Ldot (Lident "React", "jsxsKeyed")}, [(nolabel, keyExpr)] ) | Some _, [] -> - (Exp.ident ~loc {loc; txt = Ldot (Lident "Jsx", "jsxs")}, []) + (Exp.ident ~loc {loc; txt = Ldot (Lident "React", "jsxs")}, []) in Exp.apply ~loc ~attrs jsxExpr ([(nolabel, Exp.ident ~loc {txt = ident; loc}); (nolabel, props)] @ key) @@ -461,7 +462,7 @@ let transformLowercaseCall3 jsxRuntime mapper loc attrs callExpression ( labelled "children", Exp.apply (Exp.ident - {txt = Ldot (Lident "Jsx", "array"); loc = Location.none}) + {txt = Ldot (Lident "React", "array"); loc = Location.none}) [(Nolabel, expression)] ); ] in @@ -480,15 +481,15 @@ let transformLowercaseCall3 jsxRuntime mapper loc attrs callExpression let jsxExpr, key = match (!childrenArg, keyProp) with | None, (_, keyExpr) :: _ -> - ( Exp.ident ~loc {loc; txt = Ldot (Lident "JsxDOM", "jsxKeyed")}, + ( Exp.ident ~loc {loc; txt = Ldot (Lident "ReactDOM", "jsxKeyed")}, [(nolabel, keyExpr)] ) | None, [] -> - (Exp.ident ~loc {loc; txt = Ldot (Lident "JsxDOM", "jsx")}, []) + (Exp.ident ~loc {loc; txt = Ldot (Lident "ReactDOM", "jsx")}, []) | Some _, (_, keyExpr) :: _ -> - ( Exp.ident ~loc {loc; txt = Ldot (Lident "JsxDOM", "jsxsKeyed")}, + ( Exp.ident ~loc {loc; txt = Ldot (Lident "ReactDOM", "jsxsKeyed")}, [(nolabel, keyExpr)] ) | Some _, [] -> - (Exp.ident ~loc {loc; txt = Ldot (Lident "JsxDOM", "jsxs")}, []) + (Exp.ident ~loc {loc; txt = Ldot (Lident "ReactDOM", "jsxs")}, []) in Exp.apply ~loc ~attrs jsxExpr ([(nolabel, componentNameExpr); (nolabel, props)] @ key) diff --git a/cli/res_cli.ml b/cli/res_cli.ml index 1ca17b07..10b29bc9 100644 --- a/cli/res_cli.ml +++ b/cli/res_cli.ml @@ -163,7 +163,8 @@ module ResClflags : sig val file : string ref val interface : bool ref val ppx : string ref - val jsxRuntime : string ref + val jsxModule : string ref + val jsxMode : string ref val typechecker : bool ref val parse : unit -> unit @@ -175,7 +176,8 @@ end = struct let origin = ref "" let interface = ref false let ppx = ref "" - let jsxRuntime = ref "automatic" + let jsxModule = ref "react" + let jsxMode = ref "classic" let file = ref "" let typechecker = ref false @@ -207,10 +209,12 @@ end = struct Arg.String (fun txt -> ppx := txt), "Apply a specific built-in ppx before parsing, none or jsx3, jsx4. \ Default: none" ); - ( "-jsx-runtime", - Arg.String (fun txt -> jsxRuntime := txt), - "Specify the jsx runtime for React, classic or automatic. Default: \ - automatic" ); + ( "-jsx-module", + Arg.String (fun txt -> jsxModule := txt), + "Specify the jsx module. Default: react" ); + ( "-jsx-mode", + Arg.String (fun txt -> jsxMode := txt), + "Specify the jsx mode, classic or automatic. Default: classic" ); ( "-typechecker", Arg.Unit (fun () -> typechecker := true), "Parses the ast as it would be passed to the typechecker and not the \ @@ -224,7 +228,7 @@ module CliArgProcessor = struct type backend = Parser : 'diagnostics Res_driver.parsingEngine -> backend [@@unboxed] - let processFile ~isInterface ~width ~recover ~origin ~target ~ppx ~jsxRuntime + let processFile ~isInterface ~width ~recover ~origin ~target ~ppx ~jsxMode ~typechecker filename = let len = String.length filename in let processInterface = @@ -285,8 +289,7 @@ module CliArgProcessor = struct match ppx with | "jsx3" -> Reactjs_jsx_ppx_v3.rewrite_signature parseResult.parsetree | "jsx4" -> - Reactjs_jsx_ppx_v4.rewrite_signature jsxRuntime - parseResult.parsetree + Reactjs_jsx_ppx_v4.rewrite_signature jsxMode parseResult.parsetree | _ -> parseResult.parsetree in printEngine.printInterface ~width ~filename @@ -306,7 +309,7 @@ module CliArgProcessor = struct | "jsx3" -> Reactjs_jsx_ppx_v3.rewrite_implementation parseResult.parsetree | "jsx4" -> - Reactjs_jsx_ppx_v4.rewrite_implementation jsxRuntime + Reactjs_jsx_ppx_v4.rewrite_implementation jsxMode parseResult.parsetree | _ -> parseResult.parsetree in @@ -321,5 +324,5 @@ let[@raises exit] () = CliArgProcessor.processFile ~isInterface:!ResClflags.interface ~width:!ResClflags.width ~recover:!ResClflags.recover ~target:!ResClflags.print ~origin:!ResClflags.origin ~ppx:!ResClflags.ppx - ~jsxRuntime:!ResClflags.jsxRuntime ~typechecker:!ResClflags.typechecker + ~jsxMode:!ResClflags.jsxMode ~typechecker:!ResClflags.typechecker !ResClflags.file) diff --git a/scripts/test.sh b/scripts/test.sh index dfc8e0c3..ed9656bc 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -47,13 +47,13 @@ done temp/files.txt while read file; do - rescript -ppx jsx4 -jsx-runtime classic $file &> $(exp2 $file "_v4_cls") & maybeWait + rescript -ppx jsx4 -jsx-mode classic $file &> $(exp2 $file "_v4_cls") & maybeWait done temp/files.txt while read file; do - rescript -ppx jsx4 -jsx-runtime automatic $file &> $(exp2 $file "_v4_auto") & maybeWait + rescript -ppx jsx4 -jsx-mode automatic $file &> $(exp2 $file "_v4_auto") & maybeWait done = {key?: string, msg: 'msg} // test React JSX file let make = ({msg, _}: props<'msg>) => { - JsxDOM.jsx("div", {children: {msg->React.string}}) + ReactDOM.jsx("div", {children: {msg->React.string}}) } let make = { let \"CommentAtTop" = (props: props<_>) => make(props) diff --git a/tests/ppx/react/expected/externalWithCustomName.res_v4_auto.txt b/tests/ppx/react/expected/externalWithCustomName.res_v4_auto.txt index 695ea930..49e3e620 100644 --- a/tests/ppx/react/expected/externalWithCustomName.res_v4_auto.txt +++ b/tests/ppx/react/expected/externalWithCustomName.res_v4_auto.txt @@ -4,4 +4,4 @@ module Foo = { external component: React.componentLike, React.element> = "component" } -let t = Jsx.jsx(Foo.component, {a: 1, b: "1"}) +let t = React.jsx(Foo.component, {a: 1, b: "1"}) diff --git a/tests/ppx/react/expected/forwardRef.res_v4_auto.txt b/tests/ppx/react/expected/forwardRef.res_v4_auto.txt index b1e292a6..6c257892 100644 --- a/tests/ppx/react/expected/forwardRef.res_v4_auto.txt +++ b/tests/ppx/react/expected/forwardRef.res_v4_auto.txt @@ -9,11 +9,11 @@ module FancyInput = { let ref = Js.Nullable.fromOption(ref) let _ = ref - JsxDOM.jsxs( + ReactDOM.jsxs( "div", { - children: Jsx.array([ - JsxDOM.jsx( + children: React.array([ + ReactDOM.jsx( "input", { type_: "text", @@ -37,9 +37,14 @@ type props = {key?: string} let make = (_: props) => { let input = React.useRef(Js.Nullable.null) - JsxDOM.jsx( + ReactDOM.jsx( "div", - {children: Jsx.jsx(FancyInput.make, {ref: input, children: {React.string("Click to focus")}})}, + { + children: React.jsx( + FancyInput.make, + {ref: input, children: {React.string("Click to focus")}}, + ), + }, ) } let make = { diff --git a/tests/ppx/react/expected/innerModule.res_v4_auto.txt b/tests/ppx/react/expected/innerModule.res_v4_auto.txt index ff6683fa..16a8f994 100644 --- a/tests/ppx/react/expected/innerModule.res_v4_auto.txt +++ b/tests/ppx/react/expected/innerModule.res_v4_auto.txt @@ -2,7 +2,7 @@ module Bar = { type props<'a, 'b> = {key?: string, a: 'a, b: 'b} let make = ({a, b, _}: props<'a, 'b>) => { Js.log("This function should be named `InnerModule.react$Bar`") - JsxDOM.jsx("div", {key: ?None}) + ReactDOM.jsx("div", {key: ?None}) } let make = { let \"InnerModule$Bar" = (props: props<_>) => make(props) @@ -12,7 +12,7 @@ module Bar = { let component = ({a, b, _}: props<'a, 'b>) => { Js.log("This function should be named `InnerModule.react$Bar$component`") - JsxDOM.jsx("div", {key: ?None}) + ReactDOM.jsx("div", {key: ?None}) } let component = { let \"InnerModule$Bar$component" = (props: props<_>) => make(props) diff --git a/tests/ppx/react/expected/newtype.res_v4_auto.txt b/tests/ppx/react/expected/newtype.res_v4_auto.txt index 2db0b64f..914b80af 100644 --- a/tests/ppx/react/expected/newtype.res_v4_auto.txt +++ b/tests/ppx/react/expected/newtype.res_v4_auto.txt @@ -1,6 +1,6 @@ type props<'a, 'b, 'c> = {key?: string, a: 'a, b: 'b, c: 'c} let make = (_: props<'a, 'b, 'c>, type a, ~a: a, ~b: array>, ~c: 'a, _) => - JsxDOM.jsx("div", {key: ?None}) + ReactDOM.jsx("div", {key: ?None}) let make = { let \"Newtype" = (props: props<_>) => make(props) \"Newtype" diff --git a/tests/ppx/react/expected/topLevel.res_v4_auto.txt b/tests/ppx/react/expected/topLevel.res_v4_auto.txt index d9abd066..d732f6b3 100644 --- a/tests/ppx/react/expected/topLevel.res_v4_auto.txt +++ b/tests/ppx/react/expected/topLevel.res_v4_auto.txt @@ -1,7 +1,7 @@ type props<'a, 'b> = {key?: string, a: 'a, b: 'b} let make = ({a, b, _}: props<'a, 'b>) => { Js.log("This function should be named 'TopLevel.react'") - JsxDOM.jsx("div", {key: ?None}) + ReactDOM.jsx("div", {key: ?None}) } let make = { let \"TopLevel" = (props: props<_>) => make(props) diff --git a/tests/ppx/react/expected/typeConstraint.res_v4_auto.txt b/tests/ppx/react/expected/typeConstraint.res_v4_auto.txt index 7e67486b..a83d2519 100644 --- a/tests/ppx/react/expected/typeConstraint.res_v4_auto.txt +++ b/tests/ppx/react/expected/typeConstraint.res_v4_auto.txt @@ -1,7 +1,7 @@ type props<'a, 'b> = {key?: string, a: 'a, b: 'b} let make: 'a. (~a: 'a, ~b: 'a, 'a) => React.element = (_: props<'a, 'b>, type a): ( (~a: a, ~b: a, a) => React.element -) => (~a, ~b, _) => JsxDOM.jsx("div", {key: ?None}) +) => (~a, ~b, _) => ReactDOM.jsx("div", {key: ?None}) let make = { let \"TypeConstraint" = (props: props<_>) => make(props) \"TypeConstraint" From a2f690fdc218fdba38040b83c23937c7b9ed7574 Mon Sep 17 00:00:00 2001 From: Woonki Moon Date: Sun, 3 Jul 2022 18:19:06 +0900 Subject: [PATCH 16/94] update V4 upgrade doc --- cli/JSXV4UPGRADE.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/cli/JSXV4UPGRADE.md b/cli/JSXV4UPGRADE.md index cf59ef9d..a5a7fb09 100644 --- a/cli/JSXV4UPGRADE.md +++ b/cli/JSXV4UPGRADE.md @@ -11,7 +11,8 @@ ```json "jsx": { "version": 3, - "runtime": "classic" + "module": "react", + "mode": "classic" } ``` @@ -22,7 +23,8 @@ ```json "jsx": { "version": 4, - "runtime": "classic" + "module": "react", + "mode": "classic" } ``` @@ -31,7 +33,8 @@ ```json "jsx": { "version": 4, - "runtime": "automatic" + "module": "react", + "mode": "automatic" } ``` From 355f256140e1dff101e47eb55f87e180734d0e3a Mon Sep 17 00:00:00 2001 From: Woonki Moon Date: Mon, 4 Jul 2022 09:58:42 +0900 Subject: [PATCH 17/94] fix loc for ref in forwardRef --- cli/reactjs_jsx_ppx_v4.ml | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/cli/reactjs_jsx_ppx_v4.ml b/cli/reactjs_jsx_ppx_v4.ml index a53fc6e4..1927513a 100644 --- a/cli/reactjs_jsx_ppx_v4.ml +++ b/cli/reactjs_jsx_ppx_v4.ml @@ -979,8 +979,8 @@ let transformComponentDefinition nestedModules mapper structure returnStructures makePropsRecordType "props" emptyLoc (((true, "key", [], keyType emptyLoc) :: namedTypeList) @ - if hasForwardRef then [(true, "ref", [], refType pstr_loc)] else [] - ) + if hasForwardRef then [(true, "ref", [], refType Location.none)] + else []) in let innerExpression = if hasForwardRef then @@ -1047,7 +1047,7 @@ let transformComponentDefinition nestedModules mapper structure returnStructures {ppat_desc = Ppat_construct ({txt = Lident "()"}, _) | Ppat_any}, expr ) -> (patterns, expr) - | Pexp_fun (arg_label, _default, {ppat_loc}, expr) -> + | Pexp_fun (arg_label, _default, {ppat_loc; ppat_desc}, expr) -> ( if isLabelled arg_label || isOptional arg_label then returnedExpression (( {loc = ppat_loc; txt = Lident (getLabel arg_label)}, @@ -1056,24 +1056,26 @@ let transformComponentDefinition nestedModules mapper structure returnStructures {txt = getLabel arg_label; loc = ppat_loc} ) :: patterns) expr - else returnedExpression patterns expr + else + (* Special case of nolabel arg "ref" in forwardRef fn *) + (* let make = React.forwardRef(ref => body) *) + match ppat_desc with + | Ppat_var {txt} + | Ppat_constraint ({ppat_desc = Ppat_var {txt}}, _) + when txt = "ref" -> + returnedExpression + (( {loc = ppat_loc; txt = Lident txt}, + Pat.var ~attrs:optionalAttr {txt; loc = ppat_loc} ) + :: patterns) + expr + | _ -> returnedExpression patterns expr) | _ -> (patterns, expr) in let patternsWithLid, expression = returnedExpression [] expression in - let patternsWithLid = - List.rev patternsWithLid - @ - if hasForwardRef then - [ - ( Location.mknoloc (Lident "ref"), - Pat.var ~attrs:optionalAttr (Location.mknoloc "ref") ); - ] - else [] - in let pattern = match patternsWithLid with | [] -> Pat.any () - | _ -> Pat.record patternsWithLid Open + | _ -> Pat.record (List.rev patternsWithLid) Open in (* add patttern matching for optional prop value *) let expression = From ecf656531f1821d80e23e42894c3b42f73e77384 Mon Sep 17 00:00:00 2001 From: Woonki Moon Date: Wed, 6 Jul 2022 01:21:40 +0900 Subject: [PATCH 18/94] add jsxFragment --- cli/reactjs_jsx_ppx_v4.ml | 39 +++++++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/cli/reactjs_jsx_ppx_v4.ml b/cli/reactjs_jsx_ppx_v4.ml index 1927513a..b8cacec1 100644 --- a/cli/reactjs_jsx_ppx_v4.ml +++ b/cli/reactjs_jsx_ppx_v4.ml @@ -1305,23 +1305,50 @@ let expr jsxRuntime mapper expression = | _, nonJSXAttributes -> let loc = {loc with loc_ghost = true} in let fragment = - Exp.ident ~loc {loc; txt = Ldot (Lident "ReasonReact", "fragment")} + match jsxRuntime with + | "automatic" -> + Exp.ident ~loc {loc; txt = Ldot (Lident "React", "jsxFragment")} + | "classic" | _ -> + Exp.ident ~loc {loc; txt = Ldot (Lident "ReasonReact", "fragment")} in let childrenExpr = transformChildrenIfList ~loc ~mapper listItems in let args = [ - (* "div" *) (nolabel, fragment); - (* [|moreCreateElementCallsHere|] *) - (nolabel, childrenExpr); + (match jsxRuntime with + | "automatic" -> + ( nolabel, + Exp.record + [ + ( Location.mknoloc @@ Lident "children", + match childrenExpr with + | {pexp_desc = Pexp_array children} -> ( + match children with + | [] -> recordWithOnlyKey ~loc:Location.none + | [child] -> child + | _ -> childrenExpr) + | _ -> childrenExpr ); + ] + None ) + | "classic" | _ -> (nolabel, childrenExpr)); ] in + let countOfChildren = function + | {pexp_desc = Pexp_array children} -> List.length children + | _ -> 0 + in Exp.apply ~loc (* throw away the [@JSX] attribute and keep the others, if any *) ~attrs:nonJSXAttributes (* ReactDOMRe.createElement *) - (Exp.ident ~loc - {loc; txt = Ldot (Lident "ReactDOMRe", "createElement")}) + (match jsxRuntime with + | "automatic" -> + if countOfChildren childrenExpr > 1 then + Exp.ident ~loc {loc; txt = Ldot (Lident "React", "jsxs")} + else Exp.ident ~loc {loc; txt = Ldot (Lident "React", "jsx")} + | "classic" | _ -> + Exp.ident ~loc + {loc; txt = Ldot (Lident "ReactDOMRe", "createElement")}) args) (* Delegate to the default mapper, a deep identity traversal *) | e -> default_mapper.expr mapper e From d76e8077cf9e817b461506317c4c56904015af72 Mon Sep 17 00:00:00 2001 From: Woonki Moon Date: Wed, 6 Jul 2022 07:58:33 +0900 Subject: [PATCH 19/94] labelled arg for jsx_mode --- cli/reactjs_jsx_ppx_v4.ml | 40 +++++++++++++++++++------------------- cli/reactjs_jsx_ppx_v4.mli | 5 +++-- cli/res_cli.ml | 5 +++-- 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/cli/reactjs_jsx_ppx_v4.ml b/cli/reactjs_jsx_ppx_v4.ml index b8cacec1..4f20459b 100644 --- a/cli/reactjs_jsx_ppx_v4.ml +++ b/cli/reactjs_jsx_ppx_v4.ml @@ -323,8 +323,8 @@ let makePropsRecordTypeSig propsName loc namedTypeList = ~kind:(Ptype_record labelDeclList); ] -let transformUppercaseCall3 jsxRuntime modulePath mapper loc attrs - callExpression callArguments = +let transformUppercaseCall3 ~jsxMode modulePath mapper loc attrs callExpression + callArguments = let children, argsWithLabels = extractChildren ~loc ~removeLastPositionUnit:true callArguments in @@ -345,7 +345,7 @@ let transformUppercaseCall3 jsxRuntime modulePath mapper loc attrs | ListLiteral expression -> ( (* this is a hack to support react components that introspect into their children *) childrenArg := Some expression; - match jsxRuntime with + match jsxMode with | "automatic" -> [ ( labelled "children", @@ -382,7 +382,7 @@ let transformUppercaseCall3 jsxRuntime modulePath mapper loc attrs (* handle key, ref, children *) (* React.createElement(Component.make, props, ...children) *) - match jsxRuntime with + match jsxMode with (* The new jsx transform *) | "automatic" -> let record = recordFromProps ~removeKey:true callExpression args in @@ -432,10 +432,10 @@ let transformUppercaseCall3 jsxRuntime modulePath mapper loc attrs ]) [@@raises Invalid_argument] -let transformLowercaseCall3 jsxRuntime mapper loc attrs callExpression +let transformLowercaseCall3 ~jsxMode mapper loc attrs callExpression callArguments id = let componentNameExpr = constantString ~loc id in - match jsxRuntime with + match jsxMode with (* the new jsx transform *) | "automatic" -> let children, nonChildrenProps = @@ -1221,7 +1221,7 @@ let reactComponentSignatureTransform mapper signatures = List.fold_right (transformComponentSignature mapper) signatures [] [@@raises Invalid_argument] -let transformJsxCall jsxRuntime mapper callExpression callArguments attrs = +let transformJsxCall ~jsxMode mapper callExpression callArguments attrs = match callExpression.pexp_desc with | Pexp_ident caller -> ( match caller with @@ -1231,13 +1231,13 @@ let transformJsxCall jsxRuntime mapper callExpression callArguments attrs = "JSX: `createElement` should be preceeded by a module name.") (* Foo.createElement(~prop1=foo, ~prop2=bar, ~children=[], ()) *) | {loc; txt = Ldot (modulePath, ("createElement" | "make"))} -> - transformUppercaseCall3 jsxRuntime modulePath mapper loc attrs + transformUppercaseCall3 ~jsxMode modulePath mapper loc attrs callExpression callArguments (* div(~prop1=foo, ~prop2=bar, ~children=[bla], ()) *) (* turn that into ReactDOMRe.createElement(~props=ReactDOMRe.props(~props1=foo, ~props2=bar, ()), [|bla|]) *) | {loc; txt = Lident id} -> - transformLowercaseCall3 jsxRuntime mapper loc attrs callExpression + transformLowercaseCall3 ~jsxMode mapper loc attrs callExpression callArguments id | {txt = Ldot (_, anythingNotCreateElementOrMake)} -> raise @@ -1270,7 +1270,7 @@ let structure nestedModules mapper structure = @@ reactComponentTransform nestedModules mapper structures [@@raises Invalid_argument] -let expr jsxRuntime mapper expression = +let expr ~jsxMode mapper expression = match expression with (* Does the function application have the @JSX attribute? *) | {pexp_desc = Pexp_apply (callExpression, callArguments); pexp_attributes} @@ -1284,7 +1284,7 @@ let expr jsxRuntime mapper expression = (* no JSX attribute *) | [], _ -> default_mapper.expr mapper expression | _, nonJSXAttributes -> - transformJsxCall jsxRuntime mapper callExpression callArguments + transformJsxCall ~jsxMode mapper callExpression callArguments nonJSXAttributes) (* is it a list with jsx attribute? Reason <>foo desugars to [@JSX][foo]*) | { @@ -1305,7 +1305,7 @@ let expr jsxRuntime mapper expression = | _, nonJSXAttributes -> let loc = {loc with loc_ghost = true} in let fragment = - match jsxRuntime with + match jsxMode with | "automatic" -> Exp.ident ~loc {loc; txt = Ldot (Lident "React", "jsxFragment")} | "classic" | _ -> @@ -1315,7 +1315,7 @@ let expr jsxRuntime mapper expression = let args = [ (nolabel, fragment); - (match jsxRuntime with + (match jsxMode with | "automatic" -> ( nolabel, Exp.record @@ -1341,7 +1341,7 @@ let expr jsxRuntime mapper expression = ~loc (* throw away the [@JSX] attribute and keep the others, if any *) ~attrs:nonJSXAttributes (* ReactDOMRe.createElement *) - (match jsxRuntime with + (match jsxMode with | "automatic" -> if countOfChildren childrenExpr > 1 then Exp.ident ~loc {loc; txt = Ldot (Lident "React", "jsxs")} @@ -1362,23 +1362,23 @@ let module_binding nestedModules mapper module_binding = [@@raises Failure] (* TODO: some line number might still be wrong *) -let jsxMapper jsxRuntime nestedModules = +let jsxMapper ~jsxMode nestedModules = let structure = structure nestedModules in let module_binding = module_binding nestedModules in - let expr = expr jsxRuntime in + let expr = expr ~jsxMode in {default_mapper with structure; expr; signature; module_binding} [@@raises Invalid_argument, Failure] -let rewrite_implementation jsxRuntime (code : Parsetree.structure) : +let rewrite_implementation ~jsx_mode (code : Parsetree.structure) : Parsetree.structure = let nestedModules = ref [] in - let mapper = jsxMapper jsxRuntime nestedModules in + let mapper = jsxMapper ~jsxMode:jsx_mode nestedModules in mapper.structure mapper code [@@raises Invalid_argument, Failure] -let rewrite_signature jsxRuntime (code : Parsetree.signature) : +let rewrite_signature ~jsx_mode (code : Parsetree.signature) : Parsetree.signature = let nestedModules = ref [] in - let mapper = jsxMapper jsxRuntime nestedModules in + let mapper = jsxMapper ~jsxMode:jsx_mode nestedModules in mapper.signature mapper code [@@raises Invalid_argument, Failure] diff --git a/cli/reactjs_jsx_ppx_v4.mli b/cli/reactjs_jsx_ppx_v4.mli index 0c4d1c7e..f63eb355 100644 --- a/cli/reactjs_jsx_ppx_v4.mli +++ b/cli/reactjs_jsx_ppx_v4.mli @@ -63,6 +63,7 @@ children O -> `jsxs("div", { ..., children: [ ... ]})` *) val rewrite_implementation : - string -> Parsetree.structure -> Parsetree.structure + jsx_mode:string -> Parsetree.structure -> Parsetree.structure -val rewrite_signature : string -> Parsetree.signature -> Parsetree.signature +val rewrite_signature : + jsx_mode:string -> Parsetree.signature -> Parsetree.signature diff --git a/cli/res_cli.ml b/cli/res_cli.ml index 10b29bc9..510e688e 100644 --- a/cli/res_cli.ml +++ b/cli/res_cli.ml @@ -289,7 +289,8 @@ module CliArgProcessor = struct match ppx with | "jsx3" -> Reactjs_jsx_ppx_v3.rewrite_signature parseResult.parsetree | "jsx4" -> - Reactjs_jsx_ppx_v4.rewrite_signature jsxMode parseResult.parsetree + Reactjs_jsx_ppx_v4.rewrite_signature ~jsx_mode:jsxMode + parseResult.parsetree | _ -> parseResult.parsetree in printEngine.printInterface ~width ~filename @@ -309,7 +310,7 @@ module CliArgProcessor = struct | "jsx3" -> Reactjs_jsx_ppx_v3.rewrite_implementation parseResult.parsetree | "jsx4" -> - Reactjs_jsx_ppx_v4.rewrite_implementation jsxMode + Reactjs_jsx_ppx_v4.rewrite_implementation ~jsx_mode:jsxMode parseResult.parsetree | _ -> parseResult.parsetree in From b4ae677c4530a27e127ac7cadf78a186c906c6bb Mon Sep 17 00:00:00 2001 From: Woonki Moon Date: Thu, 7 Jul 2022 01:49:29 +0900 Subject: [PATCH 20/94] add file-level jsx config, @@jsxConfig --- cli/JSXV4.md | 12 ++ cli/reactjs_jsx_ppx.ml | 141 ++++++++++++++++++ cli/reactjs_jsx_ppx.mli | 23 +++ cli/res_cli.ml | 36 ++--- scripts/test.sh | 6 +- .../react/expected/fileLevelConfig.res_v3.txt | 10 ++ .../expected/fileLevelConfig.res_v4_auto.txt | 10 ++ .../expected/fileLevelConfig.res_v4_cls.txt | 10 ++ tests/ppx/react/fileLevelConfig.res | 6 + 9 files changed, 229 insertions(+), 25 deletions(-) create mode 100644 cli/reactjs_jsx_ppx.ml create mode 100644 cli/reactjs_jsx_ppx.mli create mode 100644 tests/ppx/react/expected/fileLevelConfig.res_v3.txt create mode 100644 tests/ppx/react/expected/fileLevelConfig.res_v4_auto.txt create mode 100644 tests/ppx/react/expected/fileLevelConfig.res_v4_cls.txt create mode 100644 tests/ppx/react/fileLevelConfig.res diff --git a/cli/JSXV4.md b/cli/JSXV4.md index 015f144f..b0114704 100644 --- a/cli/JSXV4.md +++ b/cli/JSXV4.md @@ -126,3 +126,15 @@ function has the name of the enclosing module/file. ReactDOMRe.createElement(ReasonReact.fragment, [comp1, comp2, comp3]) ``` + +**File-level config** + +The top-level attribute `@@jsxConfig` set the jsx config in the file-level. The jsx config of the project is ignored. + +```rescript +@@jsxConfig({version: 4, mode: "automatic"}) +// The jsx config in bsconfig is ignored. + +@react.component +let make = () => body +``` diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml new file mode 100644 index 00000000..7bddcd53 --- /dev/null +++ b/cli/reactjs_jsx_ppx.ml @@ -0,0 +1,141 @@ +open Parsetree + +let isJsxConfigAttr ((loc, _) : attribute) = loc.txt = "jsxConfig" + +let hasJsxConfigAttrStr {pstr_desc} = + match pstr_desc with + | Pstr_attribute attribute -> isJsxConfigAttr attribute + | _ -> false + +let jsxConfigAttributeStr structure = + let firstStrItem = try Some (List.hd structure) with Not_found -> None in + match firstStrItem with + | Some strItem when hasJsxConfigAttrStr strItem -> firstStrItem + | _ -> None + +let hasJsxConfigAttrSig {psig_desc} = + match psig_desc with + | Psig_attribute attribute -> isJsxConfigAttr attribute + | _ -> false + +let jsxConfigAttributeSig signature = + (* maybe just check the first structure_item? *) + let firstSigItem = try Some (List.hd signature) with Not_found -> None in + match firstSigItem with + | Some sigItem when hasJsxConfigAttrSig sigItem -> firstSigItem + | _ -> None + +let getJsxConfig payload = + match payload with + | Some + (PStr + ({ + pstr_desc = + Pstr_eval ({pexp_desc = Pexp_record (recordFields, None)}, _); + } + :: _rest)) -> + recordFields + | _ -> raise (Invalid_argument "jsxConfig accepts a record config only") + +type configKey = Int | String + +let getJsxConfigByKey ~key ~type_ recordFields = + let values = + List.filter_map + (fun ((lid, expr) : Longident.t Location.loc * expression) -> + match (type_, lid, expr) with + | ( Int, + {txt = Lident k}, + {pexp_desc = Pexp_constant (Pconst_integer (value, None))} ) + when k = key -> + Some value + | ( String, + {txt = Lident k}, + {pexp_desc = Pexp_constant (Pconst_string (value, None))} ) + when k = key -> + Some value + | _ -> None) + recordFields + in + match values with + | [] -> None + | [v] | v :: _ -> Some v + +let transform ~v3 ~v4 ~jsx_version ~jsx_module ~jsx_mode code = + match (jsx_version, jsx_module, jsx_mode) with + | "3", _, _ -> v3 code + | "4", _, "classic" -> v4 ~jsx_mode:"classic" code + | "4", _, "automatic" -> v4 ~jsx_mode:"automatic" code + | _ -> code + +let rewrite_implementation ~jsx_version ~jsx_module ~jsx_mode + (code : Parsetree.structure) : Parsetree.structure = + match jsxConfigAttributeStr code with + (* if @@jsxConfig({ .. }) *) + | Some _ -> ( + let _attr_loc, payload = + match jsxConfigAttributeStr code with + | Some {pstr_desc = Pstr_attribute ({loc}, payload)} -> (loc, Some payload) + | _ -> (Location.none, None) + in + let version = + getJsxConfig payload |> getJsxConfigByKey ~key:"version" ~type_:Int + in + let module_ = + getJsxConfig payload |> getJsxConfigByKey ~key:"module" ~type_:String + in + let mode = + getJsxConfig payload |> getJsxConfigByKey ~key:"mode" ~type_:String + in + match (version, module_, mode) with + | Some "3", _, _ -> Reactjs_jsx_ppx_v3.rewrite_implementation code + | Some "4", _, Some "classic" -> + Reactjs_jsx_ppx_v4.rewrite_implementation ~jsx_mode:"classic" code + | Some "4", _, Some "automatic" -> + Reactjs_jsx_ppx_v4.rewrite_implementation ~jsx_mode:"automatic" code + | _ -> + raise + (Invalid_argument + "jsxConfig has options: version = [3, 4], mode = [\"classic\", \ + \"automatic\"]")) + | None -> + transform ~v3:Reactjs_jsx_ppx_v3.rewrite_implementation + ~v4:Reactjs_jsx_ppx_v4.rewrite_implementation ~jsx_version ~jsx_module + ~jsx_mode code + [@@raises Invalid_argument, Failure] + +let rewrite_signature ~jsx_version ~jsx_module ~jsx_mode + (code : Parsetree.signature) : Parsetree.signature = + match jsxConfigAttributeSig code with + (* if @@jsxConfig({ .. }) *) + | Some _ -> ( + let _attr_loc, payload = + match jsxConfigAttributeSig code with + | Some {psig_desc = Psig_attribute ({loc}, payload)} -> (loc, Some payload) + | _ -> (Location.none, None) + in + let version = + getJsxConfig payload |> getJsxConfigByKey ~key:"version" ~type_:Int + in + let module_ = + getJsxConfig payload |> getJsxConfigByKey ~key:"module" ~type_:String + in + let mode = + getJsxConfig payload |> getJsxConfigByKey ~key:"mode" ~type_:String + in + match (version, module_, mode) with + | Some "3", _, _ -> Reactjs_jsx_ppx_v3.rewrite_signature code + | Some "4", _, Some "classic" -> + Reactjs_jsx_ppx_v4.rewrite_signature ~jsx_mode code + | Some "4", _, Some "automatic" -> + Reactjs_jsx_ppx_v4.rewrite_signature ~jsx_mode code + | _ -> + raise + (Invalid_argument + "jsxConfig has options: version = [3, 4], mode = [\"classic\", \ + \"automatic\"]")) + | None -> + transform ~v3:Reactjs_jsx_ppx_v3.rewrite_signature + ~v4:Reactjs_jsx_ppx_v4.rewrite_signature ~jsx_version ~jsx_module + ~jsx_mode code + [@@raises Invalid_argument, Failure] diff --git a/cli/reactjs_jsx_ppx.mli b/cli/reactjs_jsx_ppx.mli new file mode 100644 index 00000000..d4db0354 --- /dev/null +++ b/cli/reactjs_jsx_ppx.mli @@ -0,0 +1,23 @@ +(* + This is the module that handles turning Reason JSX' agnostic function call into + a ReasonReact-specific function call. Aka, this is a macro, using OCaml's ppx + facilities; https://whitequark.org/blog/2014/04/16/a-guide-to-extension- + points-in-ocaml/ + You wouldn't use this file directly; it's used by ReScript's + bsconfig.json. Specifically, there's a field called `react-jsx` inside the + field `reason`, which enables this ppx through some internal call in bsb +*) + +val rewrite_implementation : + jsx_version:string -> + jsx_module:string -> + jsx_mode:string -> + Parsetree.structure -> + Parsetree.structure + +val rewrite_signature : + jsx_version:string -> + jsx_module:string -> + jsx_mode:string -> + Parsetree.signature -> + Parsetree.signature diff --git a/cli/res_cli.ml b/cli/res_cli.ml index 510e688e..4d19dfe0 100644 --- a/cli/res_cli.ml +++ b/cli/res_cli.ml @@ -162,7 +162,7 @@ module ResClflags : sig val origin : string ref val file : string ref val interface : bool ref - val ppx : string ref + val jsxVersion : string ref val jsxModule : string ref val jsxMode : string ref val typechecker : bool ref @@ -175,7 +175,7 @@ end = struct let print = ref "res" let origin = ref "" let interface = ref false - let ppx = ref "" + let jsxVersion = ref "" let jsxModule = ref "react" let jsxMode = ref "classic" let file = ref "" @@ -205,10 +205,10 @@ end = struct ( "-interface", Arg.Unit (fun () -> interface := true), "Parse as interface" ); - ( "-ppx", - Arg.String (fun txt -> ppx := txt), - "Apply a specific built-in ppx before parsing, none or jsx3, jsx4. \ - Default: none" ); + ( "-jsx-version", + Arg.String (fun txt -> jsxVersion := txt), + "Apply a specific built-in ppx before parsing, none or 3, 4. Default: \ + none" ); ( "-jsx-module", Arg.String (fun txt -> jsxModule := txt), "Specify the jsx module. Default: react" ); @@ -228,8 +228,8 @@ module CliArgProcessor = struct type backend = Parser : 'diagnostics Res_driver.parsingEngine -> backend [@@unboxed] - let processFile ~isInterface ~width ~recover ~origin ~target ~ppx ~jsxMode - ~typechecker filename = + let processFile ~isInterface ~width ~recover ~origin ~target ~jsxVersion + ~jsxModule ~jsxMode ~typechecker filename = let len = String.length filename in let processInterface = isInterface @@ -286,12 +286,8 @@ module CliArgProcessor = struct else exit 1) else let parsetree = - match ppx with - | "jsx3" -> Reactjs_jsx_ppx_v3.rewrite_signature parseResult.parsetree - | "jsx4" -> - Reactjs_jsx_ppx_v4.rewrite_signature ~jsx_mode:jsxMode - parseResult.parsetree - | _ -> parseResult.parsetree + Reactjs_jsx_ppx.rewrite_signature ~jsx_version:jsxVersion + ~jsx_module:jsxModule ~jsx_mode:jsxMode parseResult.parsetree in printEngine.printInterface ~width ~filename ~comments:parseResult.comments parsetree @@ -306,13 +302,8 @@ module CliArgProcessor = struct else exit 1) else let parsetree = - match ppx with - | "jsx3" -> - Reactjs_jsx_ppx_v3.rewrite_implementation parseResult.parsetree - | "jsx4" -> - Reactjs_jsx_ppx_v4.rewrite_implementation ~jsx_mode:jsxMode - parseResult.parsetree - | _ -> parseResult.parsetree + Reactjs_jsx_ppx.rewrite_implementation ~jsx_version:jsxVersion + ~jsx_module:jsxModule ~jsx_mode:jsxMode parseResult.parsetree in printEngine.printImplementation ~width ~filename ~comments:parseResult.comments parsetree @@ -324,6 +315,7 @@ let[@raises exit] () = ResClflags.parse (); CliArgProcessor.processFile ~isInterface:!ResClflags.interface ~width:!ResClflags.width ~recover:!ResClflags.recover - ~target:!ResClflags.print ~origin:!ResClflags.origin ~ppx:!ResClflags.ppx + ~target:!ResClflags.print ~origin:!ResClflags.origin + ~jsxVersion:!ResClflags.jsxVersion ~jsxModule:!ResClflags.jsxModule ~jsxMode:!ResClflags.jsxMode ~typechecker:!ResClflags.typechecker !ResClflags.file) diff --git a/scripts/test.sh b/scripts/test.sh index ed9656bc..7be84526 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -41,19 +41,19 @@ done temp/files.txt while read file; do - rescript -ppx jsx3 $file &> $(exp2 $file "_v3") & maybeWait + rescript -jsx-version 3 $file &> $(exp2 $file "_v3") & maybeWait done temp/files.txt while read file; do - rescript -ppx jsx4 -jsx-mode classic $file &> $(exp2 $file "_v4_cls") & maybeWait + rescript -jsx-version 4 -jsx-mode classic $file &> $(exp2 $file "_v4_cls") & maybeWait done temp/files.txt while read file; do - rescript -ppx jsx4 -jsx-mode automatic $file &> $(exp2 $file "_v4_auto") & maybeWait + rescript -jsx-version 4 -jsx-mode automatic $file &> $(exp2 $file "_v4_auto") & maybeWait done = {key?: string, msg: 'msg} + +let make = ({msg, _}: props<'msg>) => { + ReactDOM.jsx("div", {children: {msg->React.string}}) +} +let make = { + let \"FileLevelConfig" = (props: props<_>) => make(props) + \"FileLevelConfig" +} diff --git a/tests/ppx/react/expected/fileLevelConfig.res_v4_auto.txt b/tests/ppx/react/expected/fileLevelConfig.res_v4_auto.txt new file mode 100644 index 00000000..a67a65e5 --- /dev/null +++ b/tests/ppx/react/expected/fileLevelConfig.res_v4_auto.txt @@ -0,0 +1,10 @@ +@@jsxConfig({version: 4, mode: "automatic"}) +type props<'msg> = {key?: string, msg: 'msg} + +let make = ({msg, _}: props<'msg>) => { + ReactDOM.jsx("div", {children: {msg->React.string}}) +} +let make = { + let \"FileLevelConfig" = (props: props<_>) => make(props) + \"FileLevelConfig" +} diff --git a/tests/ppx/react/expected/fileLevelConfig.res_v4_cls.txt b/tests/ppx/react/expected/fileLevelConfig.res_v4_cls.txt new file mode 100644 index 00000000..a67a65e5 --- /dev/null +++ b/tests/ppx/react/expected/fileLevelConfig.res_v4_cls.txt @@ -0,0 +1,10 @@ +@@jsxConfig({version: 4, mode: "automatic"}) +type props<'msg> = {key?: string, msg: 'msg} + +let make = ({msg, _}: props<'msg>) => { + ReactDOM.jsx("div", {children: {msg->React.string}}) +} +let make = { + let \"FileLevelConfig" = (props: props<_>) => make(props) + \"FileLevelConfig" +} diff --git a/tests/ppx/react/fileLevelConfig.res b/tests/ppx/react/fileLevelConfig.res new file mode 100644 index 00000000..3354e688 --- /dev/null +++ b/tests/ppx/react/fileLevelConfig.res @@ -0,0 +1,6 @@ +@@jsxConfig({version: 4, mode: "automatic"}) + +@react.component +let make = (~msg) => { +
{msg->React.string}
+} From 3367a9cc176459bdc0b5967dc28133dbf69b632f Mon Sep 17 00:00:00 2001 From: Woonki Moon Date: Thu, 7 Jul 2022 16:38:23 +0900 Subject: [PATCH 21/94] change jsx_version type to int --- cli/reactjs_jsx_ppx.ml | 28 +++++++++++++++++----------- cli/reactjs_jsx_ppx.mli | 4 ++-- cli/res_cli.ml | 6 +++--- 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index 7bddcd53..3e8e305f 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -63,9 +63,9 @@ let getJsxConfigByKey ~key ~type_ recordFields = let transform ~v3 ~v4 ~jsx_version ~jsx_module ~jsx_mode code = match (jsx_version, jsx_module, jsx_mode) with - | "3", _, _ -> v3 code - | "4", _, "classic" -> v4 ~jsx_mode:"classic" code - | "4", _, "automatic" -> v4 ~jsx_mode:"automatic" code + | 3, _, _ -> v3 code + | 4, _, "classic" -> v4 ~jsx_mode:"classic" code + | 4, _, "automatic" -> v4 ~jsx_mode:"automatic" code | _ -> code let rewrite_implementation ~jsx_version ~jsx_module ~jsx_mode @@ -79,7 +79,10 @@ let rewrite_implementation ~jsx_version ~jsx_module ~jsx_mode | _ -> (Location.none, None) in let version = - getJsxConfig payload |> getJsxConfigByKey ~key:"version" ~type_:Int + getJsxConfig payload + |> getJsxConfigByKey ~key:"version" ~type_:Int + |> Option.map int_of_string_opt + |> Option.join in let module_ = getJsxConfig payload |> getJsxConfigByKey ~key:"module" ~type_:String @@ -88,10 +91,10 @@ let rewrite_implementation ~jsx_version ~jsx_module ~jsx_mode getJsxConfig payload |> getJsxConfigByKey ~key:"mode" ~type_:String in match (version, module_, mode) with - | Some "3", _, _ -> Reactjs_jsx_ppx_v3.rewrite_implementation code - | Some "4", _, Some "classic" -> + | Some 3, _, _ -> Reactjs_jsx_ppx_v3.rewrite_implementation code + | Some 4, _, Some "classic" -> Reactjs_jsx_ppx_v4.rewrite_implementation ~jsx_mode:"classic" code - | Some "4", _, Some "automatic" -> + | Some 4, _, Some "automatic" -> Reactjs_jsx_ppx_v4.rewrite_implementation ~jsx_mode:"automatic" code | _ -> raise @@ -115,7 +118,10 @@ let rewrite_signature ~jsx_version ~jsx_module ~jsx_mode | _ -> (Location.none, None) in let version = - getJsxConfig payload |> getJsxConfigByKey ~key:"version" ~type_:Int + getJsxConfig payload + |> getJsxConfigByKey ~key:"version" ~type_:Int + |> Option.map int_of_string_opt + |> Option.join in let module_ = getJsxConfig payload |> getJsxConfigByKey ~key:"module" ~type_:String @@ -124,10 +130,10 @@ let rewrite_signature ~jsx_version ~jsx_module ~jsx_mode getJsxConfig payload |> getJsxConfigByKey ~key:"mode" ~type_:String in match (version, module_, mode) with - | Some "3", _, _ -> Reactjs_jsx_ppx_v3.rewrite_signature code - | Some "4", _, Some "classic" -> + | Some 3, _, _ -> Reactjs_jsx_ppx_v3.rewrite_signature code + | Some 4, _, Some "classic" -> Reactjs_jsx_ppx_v4.rewrite_signature ~jsx_mode code - | Some "4", _, Some "automatic" -> + | Some 4, _, Some "automatic" -> Reactjs_jsx_ppx_v4.rewrite_signature ~jsx_mode code | _ -> raise diff --git a/cli/reactjs_jsx_ppx.mli b/cli/reactjs_jsx_ppx.mli index d4db0354..e98536f7 100644 --- a/cli/reactjs_jsx_ppx.mli +++ b/cli/reactjs_jsx_ppx.mli @@ -9,14 +9,14 @@ *) val rewrite_implementation : - jsx_version:string -> + jsx_version:int -> jsx_module:string -> jsx_mode:string -> Parsetree.structure -> Parsetree.structure val rewrite_signature : - jsx_version:string -> + jsx_version:int -> jsx_module:string -> jsx_mode:string -> Parsetree.signature -> diff --git a/cli/res_cli.ml b/cli/res_cli.ml index 4d19dfe0..c19ba607 100644 --- a/cli/res_cli.ml +++ b/cli/res_cli.ml @@ -162,7 +162,7 @@ module ResClflags : sig val origin : string ref val file : string ref val interface : bool ref - val jsxVersion : string ref + val jsxVersion : int ref val jsxModule : string ref val jsxMode : string ref val typechecker : bool ref @@ -175,7 +175,7 @@ end = struct let print = ref "res" let origin = ref "" let interface = ref false - let jsxVersion = ref "" + let jsxVersion = ref (-1) let jsxModule = ref "react" let jsxMode = ref "classic" let file = ref "" @@ -206,7 +206,7 @@ end = struct Arg.Unit (fun () -> interface := true), "Parse as interface" ); ( "-jsx-version", - Arg.String (fun txt -> jsxVersion := txt), + Arg.Int (fun i -> jsxVersion := i), "Apply a specific built-in ppx before parsing, none or 3, 4. Default: \ none" ); ( "-jsx-module", From 1cc4efcb3fa0c7874133d269a7c853e6d0050d5a Mon Sep 17 00:00:00 2001 From: Woonki Moon Date: Thu, 7 Jul 2022 16:57:27 +0900 Subject: [PATCH 22/94] clean up of updating jsx config --- cli/reactjs_jsx_ppx.ml | 135 ++++++++++++++++------------------------- 1 file changed, 51 insertions(+), 84 deletions(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index 3e8e305f..4bc5f56d 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -8,9 +8,9 @@ let hasJsxConfigAttrStr {pstr_desc} = | _ -> false let jsxConfigAttributeStr structure = - let firstStrItem = try Some (List.hd structure) with Not_found -> None in - match firstStrItem with - | Some strItem when hasJsxConfigAttrStr strItem -> firstStrItem + match structure with + | [] -> None + | ([strItem] | strItem :: _) when hasJsxConfigAttrStr strItem -> Some strItem | _ -> None let hasJsxConfigAttrSig {psig_desc} = @@ -19,10 +19,9 @@ let hasJsxConfigAttrSig {psig_desc} = | _ -> false let jsxConfigAttributeSig signature = - (* maybe just check the first structure_item? *) - let firstSigItem = try Some (List.hd signature) with Not_found -> None in - match firstSigItem with - | Some sigItem when hasJsxConfigAttrSig sigItem -> firstSigItem + match signature with + | [] -> None + | ([sigItem] | sigItem :: _) when hasJsxConfigAttrSig sigItem -> Some sigItem | _ -> None let getJsxConfig payload = @@ -35,7 +34,7 @@ let getJsxConfig payload = } :: _rest)) -> recordFields - | _ -> raise (Invalid_argument "jsxConfig accepts a record config only") + | _ -> [] type configKey = Int | String @@ -61,87 +60,55 @@ let getJsxConfigByKey ~key ~type_ recordFields = | [] -> None | [v] | v :: _ -> Some v -let transform ~v3 ~v4 ~jsx_version ~jsx_module ~jsx_mode code = - match (jsx_version, jsx_module, jsx_mode) with - | 3, _, _ -> v3 code - | 4, _, "classic" -> v4 ~jsx_mode:"classic" code - | 4, _, "automatic" -> v4 ~jsx_mode:"automatic" code - | _ -> code +let getOrDefaultInt ~key ~default payload = + getJsxConfig payload + |> getJsxConfigByKey ~key ~type_:Int + |> Option.map int_of_string_opt + |> Option.join |> Option.value ~default + +let getOrDefaultString ~key ~default payload = + getJsxConfig payload + |> getJsxConfigByKey ~key ~type_:String + |> Option.value ~default + +let updateConfig ~jsx_version ~jsx_module ~jsx_mode payload = + let version = getOrDefaultInt ~key:"version" ~default:jsx_version payload in + let module_ = getOrDefaultString ~key:"module" ~default:jsx_module payload in + let mode = getOrDefaultString ~key:"mode" ~default:jsx_mode payload in + (version, module_, mode) let rewrite_implementation ~jsx_version ~jsx_module ~jsx_mode (code : Parsetree.structure) : Parsetree.structure = - match jsxConfigAttributeStr code with - (* if @@jsxConfig({ .. }) *) - | Some _ -> ( - let _attr_loc, payload = - match jsxConfigAttributeStr code with - | Some {pstr_desc = Pstr_attribute ({loc}, payload)} -> (loc, Some payload) - | _ -> (Location.none, None) - in - let version = - getJsxConfig payload - |> getJsxConfigByKey ~key:"version" ~type_:Int - |> Option.map int_of_string_opt - |> Option.join - in - let module_ = - getJsxConfig payload |> getJsxConfigByKey ~key:"module" ~type_:String - in - let mode = - getJsxConfig payload |> getJsxConfigByKey ~key:"mode" ~type_:String - in - match (version, module_, mode) with - | Some 3, _, _ -> Reactjs_jsx_ppx_v3.rewrite_implementation code - | Some 4, _, Some "classic" -> - Reactjs_jsx_ppx_v4.rewrite_implementation ~jsx_mode:"classic" code - | Some 4, _, Some "automatic" -> - Reactjs_jsx_ppx_v4.rewrite_implementation ~jsx_mode:"automatic" code - | _ -> - raise - (Invalid_argument - "jsxConfig has options: version = [3, 4], mode = [\"classic\", \ - \"automatic\"]")) - | None -> - transform ~v3:Reactjs_jsx_ppx_v3.rewrite_implementation - ~v4:Reactjs_jsx_ppx_v4.rewrite_implementation ~jsx_version ~jsx_module - ~jsx_mode code + let _attr_loc, payload = + match jsxConfigAttributeStr code with + | Some {pstr_desc = Pstr_attribute ({loc}, payload)} -> (loc, Some payload) + | _ -> (Location.none, None) + in + let version, module_, mode = + updateConfig ~jsx_version ~jsx_module ~jsx_mode payload + in + match (version, module_, mode) with + | 3, _, _ -> Reactjs_jsx_ppx_v3.rewrite_implementation code + | 4, _, "classic" -> + Reactjs_jsx_ppx_v4.rewrite_implementation ~jsx_mode:"classic" code + | 4, _, "automatic" -> + Reactjs_jsx_ppx_v4.rewrite_implementation ~jsx_mode:"automatic" code + | _ -> code [@@raises Invalid_argument, Failure] let rewrite_signature ~jsx_version ~jsx_module ~jsx_mode (code : Parsetree.signature) : Parsetree.signature = - match jsxConfigAttributeSig code with - (* if @@jsxConfig({ .. }) *) - | Some _ -> ( - let _attr_loc, payload = - match jsxConfigAttributeSig code with - | Some {psig_desc = Psig_attribute ({loc}, payload)} -> (loc, Some payload) - | _ -> (Location.none, None) - in - let version = - getJsxConfig payload - |> getJsxConfigByKey ~key:"version" ~type_:Int - |> Option.map int_of_string_opt - |> Option.join - in - let module_ = - getJsxConfig payload |> getJsxConfigByKey ~key:"module" ~type_:String - in - let mode = - getJsxConfig payload |> getJsxConfigByKey ~key:"mode" ~type_:String - in - match (version, module_, mode) with - | Some 3, _, _ -> Reactjs_jsx_ppx_v3.rewrite_signature code - | Some 4, _, Some "classic" -> - Reactjs_jsx_ppx_v4.rewrite_signature ~jsx_mode code - | Some 4, _, Some "automatic" -> - Reactjs_jsx_ppx_v4.rewrite_signature ~jsx_mode code - | _ -> - raise - (Invalid_argument - "jsxConfig has options: version = [3, 4], mode = [\"classic\", \ - \"automatic\"]")) - | None -> - transform ~v3:Reactjs_jsx_ppx_v3.rewrite_signature - ~v4:Reactjs_jsx_ppx_v4.rewrite_signature ~jsx_version ~jsx_module - ~jsx_mode code + let _attr_loc, payload = + match jsxConfigAttributeSig code with + | Some {psig_desc = Psig_attribute ({loc}, payload)} -> (loc, Some payload) + | _ -> (Location.none, None) + in + let version, module_, mode = + updateConfig ~jsx_version ~jsx_module ~jsx_mode payload + in + match (version, module_, mode) with + | 3, _, _ -> Reactjs_jsx_ppx_v3.rewrite_signature code + | 4, _, "classic" -> Reactjs_jsx_ppx_v4.rewrite_signature ~jsx_mode code + | 4, _, "automatic" -> Reactjs_jsx_ppx_v4.rewrite_signature ~jsx_mode code + | _ -> code [@@raises Invalid_argument, Failure] From 4f021c23a8e0e7472e76e4abc19f346b9f016e78 Mon Sep 17 00:00:00 2001 From: Woonki Moon Date: Thu, 7 Jul 2022 17:17:26 +0900 Subject: [PATCH 23/94] update V4 spec doc --- cli/JSXV4.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/JSXV4.md b/cli/JSXV4.md index b0114704..be14eb51 100644 --- a/cli/JSXV4.md +++ b/cli/JSXV4.md @@ -133,7 +133,7 @@ The top-level attribute `@@jsxConfig` set the jsx config in the file-level. The ```rescript @@jsxConfig({version: 4, mode: "automatic"}) -// The jsx config in bsconfig is ignored. +// The jsx config is updated in the file-level. @react.component let make = () => body From 6561566a92cadea27cb9e5fe0f7459b6a98176fc Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Thu, 7 Jul 2022 11:42:32 +0200 Subject: [PATCH 24/94] Update JSXV4.md --- cli/JSXV4.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cli/JSXV4.md b/cli/JSXV4.md index be14eb51..c3466ee8 100644 --- a/cli/JSXV4.md +++ b/cli/JSXV4.md @@ -70,11 +70,11 @@ React.createElement(Comp.make, {x, y:7, @optional z}) React.createElement(Comp.make, {x, key: "7"}) ``` -**The new JSX transform** +**New "jsx" transform** -The JSX PPX V4 supports [the new JSX transform](https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html) of React.js. +The V4 ppx supports [the new "jsx" transform](https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html) of React.js. -It affects only the application. +The "jsx" transform affects component application but not the definition. ```rescript @@ -129,11 +129,11 @@ ReactDOMRe.createElement(ReasonReact.fragment, [comp1, comp2, comp3]) **File-level config** -The top-level attribute `@@jsxConfig` set the jsx config in the file-level. The jsx config of the project is ignored. +The top-level attribute `@@jsxConfig` is used to update the jsx config for the rest of the file. Only the values mentioned are updated, the others are left unchanged. ```rescript @@jsxConfig({version: 4, mode: "automatic"}) -// The jsx config is updated in the file-level. +// The jsx config is updated for the rest of the file. @react.component let make = () => body From 1f3fb4683fe3e8f4403b59ecf52f2ef9f570e1b6 Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Thu, 7 Jul 2022 13:21:54 +0200 Subject: [PATCH 25/94] Update JSXV4.md --- cli/JSXV4.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/JSXV4.md b/cli/JSXV4.md index c3466ee8..3f7430b5 100644 --- a/cli/JSXV4.md +++ b/cli/JSXV4.md @@ -129,7 +129,7 @@ ReactDOMRe.createElement(ReasonReact.fragment, [comp1, comp2, comp3]) **File-level config** -The top-level attribute `@@jsxConfig` is used to update the jsx config for the rest of the file. Only the values mentioned are updated, the others are left unchanged. +The top-level attribute `@@jsxConfig` is used to update the jsx config for the rest of the file (or until the next config update). Only the values mentioned are updated, the others are left unchanged. ```rescript @@jsxConfig({version: 4, mode: "automatic"}) From ef3613a9423c7e4756301a576fee37475d670ae8 Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Thu, 7 Jul 2022 15:16:18 +0200 Subject: [PATCH 26/94] Half way through making config more flexible. --- cli/reactjs_jsx_ppx.ml | 111 ++---------------- cli/reactjs_jsx_ppx.mli | 12 +- cli/reactjs_jsx_ppx_v4.ml | 101 +++++++++++++--- cli/reactjs_jsx_ppx_v4.mli | 4 +- cli/res_cli.ml | 8 +- .../react/expected/fileLevelConfig.res_v3.txt | 11 +- .../expected/fileLevelConfig.res_v4_cls.txt | 2 +- 7 files changed, 111 insertions(+), 138 deletions(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index 4bc5f56d..f1ba3bde 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -1,114 +1,23 @@ open Parsetree -let isJsxConfigAttr ((loc, _) : attribute) = loc.txt = "jsxConfig" - -let hasJsxConfigAttrStr {pstr_desc} = - match pstr_desc with - | Pstr_attribute attribute -> isJsxConfigAttr attribute - | _ -> false - -let jsxConfigAttributeStr structure = - match structure with - | [] -> None - | ([strItem] | strItem :: _) when hasJsxConfigAttrStr strItem -> Some strItem - | _ -> None - -let hasJsxConfigAttrSig {psig_desc} = - match psig_desc with - | Psig_attribute attribute -> isJsxConfigAttr attribute - | _ -> false - -let jsxConfigAttributeSig signature = - match signature with - | [] -> None - | ([sigItem] | sigItem :: _) when hasJsxConfigAttrSig sigItem -> Some sigItem - | _ -> None - -let getJsxConfig payload = - match payload with - | Some - (PStr - ({ - pstr_desc = - Pstr_eval ({pexp_desc = Pexp_record (recordFields, None)}, _); - } - :: _rest)) -> - recordFields - | _ -> [] - -type configKey = Int | String - -let getJsxConfigByKey ~key ~type_ recordFields = - let values = - List.filter_map - (fun ((lid, expr) : Longident.t Location.loc * expression) -> - match (type_, lid, expr) with - | ( Int, - {txt = Lident k}, - {pexp_desc = Pexp_constant (Pconst_integer (value, None))} ) - when k = key -> - Some value - | ( String, - {txt = Lident k}, - {pexp_desc = Pexp_constant (Pconst_string (value, None))} ) - when k = key -> - Some value - | _ -> None) - recordFields - in - match values with - | [] -> None - | [v] | v :: _ -> Some v - -let getOrDefaultInt ~key ~default payload = - getJsxConfig payload - |> getJsxConfigByKey ~key ~type_:Int - |> Option.map int_of_string_opt - |> Option.join |> Option.value ~default - -let getOrDefaultString ~key ~default payload = - getJsxConfig payload - |> getJsxConfigByKey ~key ~type_:String - |> Option.value ~default - -let updateConfig ~jsx_version ~jsx_module ~jsx_mode payload = - let version = getOrDefaultInt ~key:"version" ~default:jsx_version payload in - let module_ = getOrDefaultString ~key:"module" ~default:jsx_module payload in - let mode = getOrDefaultString ~key:"mode" ~default:jsx_mode payload in - (version, module_, mode) - -let rewrite_implementation ~jsx_version ~jsx_module ~jsx_mode +let rewrite_implementation ~jsxVersion ~jsxModule ~jsxMode (code : Parsetree.structure) : Parsetree.structure = - let _attr_loc, payload = - match jsxConfigAttributeStr code with - | Some {pstr_desc = Pstr_attribute ({loc}, payload)} -> (loc, Some payload) - | _ -> (Location.none, None) - in - let version, module_, mode = - updateConfig ~jsx_version ~jsx_module ~jsx_mode payload - in - match (version, module_, mode) with + match (jsxVersion, jsxModule, jsxMode) with | 3, _, _ -> Reactjs_jsx_ppx_v3.rewrite_implementation code | 4, _, "classic" -> - Reactjs_jsx_ppx_v4.rewrite_implementation ~jsx_mode:"classic" code + Reactjs_jsx_ppx_v4.rewrite_implementation ~jsxMode code | 4, _, "automatic" -> - Reactjs_jsx_ppx_v4.rewrite_implementation ~jsx_mode:"automatic" code + Reactjs_jsx_ppx_v4.rewrite_implementation ~jsxMode code | _ -> code [@@raises Invalid_argument, Failure] -let rewrite_signature ~jsx_version ~jsx_module ~jsx_mode +let rewrite_signature ~jsxVersion ~jsxModule ~jsxMode (code : Parsetree.signature) : Parsetree.signature = - let _attr_loc, payload = - match jsxConfigAttributeSig code with - | Some {psig_desc = Psig_attribute ({loc}, payload)} -> (loc, Some payload) - | _ -> (Location.none, None) - in - let version, module_, mode = - updateConfig ~jsx_version ~jsx_module ~jsx_mode payload - in - match (version, module_, mode) with + match (jsxVersion, jsxModule, jsxMode) with | 3, _, _ -> Reactjs_jsx_ppx_v3.rewrite_signature code - | 4, _, "classic" -> Reactjs_jsx_ppx_v4.rewrite_signature ~jsx_mode code - | 4, _, "automatic" -> Reactjs_jsx_ppx_v4.rewrite_signature ~jsx_mode code + | 4, _, "classic" -> + Reactjs_jsx_ppx_v4.rewrite_signature ~jsxMode code + | 4, _, "automatic" -> + Reactjs_jsx_ppx_v4.rewrite_signature ~jsxMode code | _ -> code [@@raises Invalid_argument, Failure] diff --git a/cli/reactjs_jsx_ppx.mli b/cli/reactjs_jsx_ppx.mli index e98536f7..388202ed 100644 --- a/cli/reactjs_jsx_ppx.mli +++ b/cli/reactjs_jsx_ppx.mli @@ -9,15 +9,15 @@ *) val rewrite_implementation : - jsx_version:int -> - jsx_module:string -> - jsx_mode:string -> + jsxVersion:int -> + jsxModule:string -> + jsxMode:string -> Parsetree.structure -> Parsetree.structure val rewrite_signature : - jsx_version:int -> - jsx_module:string -> - jsx_mode:string -> + jsxVersion:int -> + jsxModule:string -> + jsxMode:string -> Parsetree.signature -> Parsetree.signature diff --git a/cli/reactjs_jsx_ppx_v4.ml b/cli/reactjs_jsx_ppx_v4.ml index 4f20459b..c93cbf78 100644 --- a/cli/reactjs_jsx_ppx_v4.ml +++ b/cli/reactjs_jsx_ppx_v4.ml @@ -4,6 +4,67 @@ open Asttypes open Parsetree open Longident +let getJsxConfig payload = + match payload with + | PStr + ({ + pstr_desc = + Pstr_eval ({pexp_desc = Pexp_record (recordFields, None)}, _); + } + :: _rest) -> + recordFields + | _ -> [] + +type configKey = Int | String + +let getJsxConfigByKey ~key ~type_ recordFields = + let values = + List.filter_map + (fun ((lid, expr) : Longident.t Location.loc * expression) -> + match (type_, lid, expr) with + | ( Int, + {txt = Lident k}, + {pexp_desc = Pexp_constant (Pconst_integer (value, None))} ) + when k = key -> + Some value + | ( String, + {txt = Lident k}, + {pexp_desc = Pexp_constant (Pconst_string (value, None))} ) + when k = key -> + Some value + | _ -> None) + recordFields + in + match values with + | [] -> None + | [v] | v :: _ -> Some v + +let getOrDefaultInt ~key ~default fields = + fields + |> getJsxConfigByKey ~key ~type_:Int + |> Option.map int_of_string_opt + |> Option.join |> Option.value ~default + +let getOrDefaultString ~key ~default fields = + fields |> getJsxConfigByKey ~key ~type_:String |> Option.value ~default + +type jsxConfig = {version: int; module_: string; mode: string} + +let updateConfig {version; module_; mode} payload = + let fields = getJsxConfig payload in + let version = getOrDefaultInt ~key:"version" ~default:version fields in + let module_ = getOrDefaultString ~key:"module" ~default:module_ fields in + let mode = getOrDefaultString ~key:"mode" ~default:mode fields in + {version; module_; mode} + +let isJsxConfigAttr ((loc, _) : attribute) = loc.txt = "jsxConfig" + +let processJsxConfgUpdate attribute config = + if isJsxConfigAttr attribute then + let _, payload = attribute in + updateConfig config payload + else config + let rec find_opt p = function | [] -> None | x :: l -> if p x then Some x else find_opt p l @@ -323,7 +384,7 @@ let makePropsRecordTypeSig propsName loc namedTypeList = ~kind:(Ptype_record labelDeclList); ] -let transformUppercaseCall3 ~jsxMode modulePath mapper loc attrs callExpression +let transformUppercaseCall3 ~config modulePath mapper loc attrs callExpression callArguments = let children, argsWithLabels = extractChildren ~loc ~removeLastPositionUnit:true callArguments @@ -345,7 +406,7 @@ let transformUppercaseCall3 ~jsxMode modulePath mapper loc attrs callExpression | ListLiteral expression -> ( (* this is a hack to support react components that introspect into their children *) childrenArg := Some expression; - match jsxMode with + match config.mode with | "automatic" -> [ ( labelled "children", @@ -382,7 +443,7 @@ let transformUppercaseCall3 ~jsxMode modulePath mapper loc attrs callExpression (* handle key, ref, children *) (* React.createElement(Component.make, props, ...children) *) - match jsxMode with + match config.mode with (* The new jsx transform *) | "automatic" -> let record = recordFromProps ~removeKey:true callExpression args in @@ -432,10 +493,10 @@ let transformUppercaseCall3 ~jsxMode modulePath mapper loc attrs callExpression ]) [@@raises Invalid_argument] -let transformLowercaseCall3 ~jsxMode mapper loc attrs callExpression +let transformLowercaseCall3 ~config mapper loc attrs callExpression callArguments id = let componentNameExpr = constantString ~loc id in - match jsxMode with + match config.mode with (* the new jsx transform *) | "automatic" -> let children, nonChildrenProps = @@ -1221,7 +1282,7 @@ let reactComponentSignatureTransform mapper signatures = List.fold_right (transformComponentSignature mapper) signatures [] [@@raises Invalid_argument] -let transformJsxCall ~jsxMode mapper callExpression callArguments attrs = +let transformJsxCall ~config mapper callExpression callArguments attrs = match callExpression.pexp_desc with | Pexp_ident caller -> ( match caller with @@ -1231,13 +1292,13 @@ let transformJsxCall ~jsxMode mapper callExpression callArguments attrs = "JSX: `createElement` should be preceeded by a module name.") (* Foo.createElement(~prop1=foo, ~prop2=bar, ~children=[], ()) *) | {loc; txt = Ldot (modulePath, ("createElement" | "make"))} -> - transformUppercaseCall3 ~jsxMode modulePath mapper loc attrs + transformUppercaseCall3 ~config modulePath mapper loc attrs callExpression callArguments (* div(~prop1=foo, ~prop2=bar, ~children=[bla], ()) *) (* turn that into ReactDOMRe.createElement(~props=ReactDOMRe.props(~props1=foo, ~props2=bar, ()), [|bla|]) *) | {loc; txt = Lident id} -> - transformLowercaseCall3 ~jsxMode mapper loc attrs callExpression + transformLowercaseCall3 ~config mapper loc attrs callExpression callArguments id | {txt = Ldot (_, anythingNotCreateElementOrMake)} -> raise @@ -1270,7 +1331,7 @@ let structure nestedModules mapper structure = @@ reactComponentTransform nestedModules mapper structures [@@raises Invalid_argument] -let expr ~jsxMode mapper expression = +let expr ~config mapper expression = match expression with (* Does the function application have the @JSX attribute? *) | {pexp_desc = Pexp_apply (callExpression, callArguments); pexp_attributes} @@ -1284,7 +1345,7 @@ let expr ~jsxMode mapper expression = (* no JSX attribute *) | [], _ -> default_mapper.expr mapper expression | _, nonJSXAttributes -> - transformJsxCall ~jsxMode mapper callExpression callArguments + transformJsxCall ~config mapper callExpression callArguments nonJSXAttributes) (* is it a list with jsx attribute? Reason <>foo desugars to [@JSX][foo]*) | { @@ -1305,7 +1366,7 @@ let expr ~jsxMode mapper expression = | _, nonJSXAttributes -> let loc = {loc with loc_ghost = true} in let fragment = - match jsxMode with + match config.mode with | "automatic" -> Exp.ident ~loc {loc; txt = Ldot (Lident "React", "jsxFragment")} | "classic" | _ -> @@ -1315,7 +1376,7 @@ let expr ~jsxMode mapper expression = let args = [ (nolabel, fragment); - (match jsxMode with + (match config.mode with | "automatic" -> ( nolabel, Exp.record @@ -1341,7 +1402,7 @@ let expr ~jsxMode mapper expression = ~loc (* throw away the [@JSX] attribute and keep the others, if any *) ~attrs:nonJSXAttributes (* ReactDOMRe.createElement *) - (match jsxMode with + (match config.mode with | "automatic" -> if countOfChildren childrenExpr > 1 then Exp.ident ~loc {loc; txt = Ldot (Lident "React", "jsxs")} @@ -1362,23 +1423,25 @@ let module_binding nestedModules mapper module_binding = [@@raises Failure] (* TODO: some line number might still be wrong *) -let jsxMapper ~jsxMode nestedModules = +let jsxMapper ~config nestedModules = let structure = structure nestedModules in let module_binding = module_binding nestedModules in - let expr = expr ~jsxMode in + let expr = expr ~config in {default_mapper with structure; expr; signature; module_binding} [@@raises Invalid_argument, Failure] -let rewrite_implementation ~jsx_mode (code : Parsetree.structure) : +let rewrite_implementation ~jsxMode (code : Parsetree.structure) : Parsetree.structure = let nestedModules = ref [] in - let mapper = jsxMapper ~jsxMode:jsx_mode nestedModules in + let config = {mode = jsxMode; module_ = ""; version = 4} in + let mapper = jsxMapper ~config nestedModules in mapper.structure mapper code [@@raises Invalid_argument, Failure] -let rewrite_signature ~jsx_mode (code : Parsetree.signature) : +let rewrite_signature ~jsxMode (code : Parsetree.signature) : Parsetree.signature = let nestedModules = ref [] in - let mapper = jsxMapper ~jsxMode:jsx_mode nestedModules in + let config = {mode = jsxMode; module_ = ""; version = 4} in + let mapper = jsxMapper ~config nestedModules in mapper.signature mapper code [@@raises Invalid_argument, Failure] diff --git a/cli/reactjs_jsx_ppx_v4.mli b/cli/reactjs_jsx_ppx_v4.mli index f63eb355..79b97297 100644 --- a/cli/reactjs_jsx_ppx_v4.mli +++ b/cli/reactjs_jsx_ppx_v4.mli @@ -63,7 +63,7 @@ children O -> `jsxs("div", { ..., children: [ ... ]})` *) val rewrite_implementation : - jsx_mode:string -> Parsetree.structure -> Parsetree.structure + jsxMode:string -> Parsetree.structure -> Parsetree.structure val rewrite_signature : - jsx_mode:string -> Parsetree.signature -> Parsetree.signature + jsxMode:string -> Parsetree.signature -> Parsetree.signature diff --git a/cli/res_cli.ml b/cli/res_cli.ml index c19ba607..ba04d685 100644 --- a/cli/res_cli.ml +++ b/cli/res_cli.ml @@ -286,8 +286,8 @@ module CliArgProcessor = struct else exit 1) else let parsetree = - Reactjs_jsx_ppx.rewrite_signature ~jsx_version:jsxVersion - ~jsx_module:jsxModule ~jsx_mode:jsxMode parseResult.parsetree + Reactjs_jsx_ppx.rewrite_signature ~jsxVersion ~jsxModule ~jsxMode + parseResult.parsetree in printEngine.printInterface ~width ~filename ~comments:parseResult.comments parsetree @@ -302,8 +302,8 @@ module CliArgProcessor = struct else exit 1) else let parsetree = - Reactjs_jsx_ppx.rewrite_implementation ~jsx_version:jsxVersion - ~jsx_module:jsxModule ~jsx_mode:jsxMode parseResult.parsetree + Reactjs_jsx_ppx.rewrite_implementation ~jsxVersion ~jsxModule ~jsxMode + parseResult.parsetree in printEngine.printImplementation ~width ~filename ~comments:parseResult.comments parsetree diff --git a/tests/ppx/react/expected/fileLevelConfig.res_v3.txt b/tests/ppx/react/expected/fileLevelConfig.res_v3.txt index a67a65e5..d38ec6da 100644 --- a/tests/ppx/react/expected/fileLevelConfig.res_v3.txt +++ b/tests/ppx/react/expected/fileLevelConfig.res_v3.txt @@ -1,10 +1,11 @@ @@jsxConfig({version: 4, mode: "automatic"}) -type props<'msg> = {key?: string, msg: 'msg} +@obj external makeProps: (~msg: 'msg, ~key: string=?, unit) => {"msg": 'msg} = "" -let make = ({msg, _}: props<'msg>) => { - ReactDOM.jsx("div", {children: {msg->React.string}}) -} +let make = + (@warning("-16") ~msg) => { + ReactDOMRe.createDOMElementVariadic("div", [{msg->React.string}]) + } let make = { - let \"FileLevelConfig" = (props: props<_>) => make(props) + let \"FileLevelConfig" = (\"Props": {"msg": 'msg}) => make(~msg=\"Props"["msg"]) \"FileLevelConfig" } diff --git a/tests/ppx/react/expected/fileLevelConfig.res_v4_cls.txt b/tests/ppx/react/expected/fileLevelConfig.res_v4_cls.txt index a67a65e5..1fc53953 100644 --- a/tests/ppx/react/expected/fileLevelConfig.res_v4_cls.txt +++ b/tests/ppx/react/expected/fileLevelConfig.res_v4_cls.txt @@ -2,7 +2,7 @@ type props<'msg> = {key?: string, msg: 'msg} let make = ({msg, _}: props<'msg>) => { - ReactDOM.jsx("div", {children: {msg->React.string}}) + ReactDOMRe.createDOMElementVariadic("div", [{msg->React.string}]) } let make = { let \"FileLevelConfig" = (props: props<_>) => make(props) From d20c9745ee8487b95562dcb2ca90dda5321a49e1 Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Thu, 7 Jul 2022 16:09:00 +0200 Subject: [PATCH 27/94] Make config mutable. --- cli/reactjs_jsx_ppx.ml | 12 ++++-------- cli/reactjs_jsx_ppx_v4.ml | 10 +++++++--- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index f1ba3bde..dcb5541b 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -4,10 +4,8 @@ let rewrite_implementation ~jsxVersion ~jsxModule ~jsxMode (code : Parsetree.structure) : Parsetree.structure = match (jsxVersion, jsxModule, jsxMode) with | 3, _, _ -> Reactjs_jsx_ppx_v3.rewrite_implementation code - | 4, _, "classic" -> - Reactjs_jsx_ppx_v4.rewrite_implementation ~jsxMode code - | 4, _, "automatic" -> - Reactjs_jsx_ppx_v4.rewrite_implementation ~jsxMode code + | 4, _, "classic" -> Reactjs_jsx_ppx_v4.rewrite_implementation ~jsxMode code + | 4, _, "automatic" -> Reactjs_jsx_ppx_v4.rewrite_implementation ~jsxMode code | _ -> code [@@raises Invalid_argument, Failure] @@ -15,9 +13,7 @@ let rewrite_signature ~jsxVersion ~jsxModule ~jsxMode (code : Parsetree.signature) : Parsetree.signature = match (jsxVersion, jsxModule, jsxMode) with | 3, _, _ -> Reactjs_jsx_ppx_v3.rewrite_signature code - | 4, _, "classic" -> - Reactjs_jsx_ppx_v4.rewrite_signature ~jsxMode code - | 4, _, "automatic" -> - Reactjs_jsx_ppx_v4.rewrite_signature ~jsxMode code + | 4, _, "classic" -> Reactjs_jsx_ppx_v4.rewrite_signature ~jsxMode code + | 4, _, "automatic" -> Reactjs_jsx_ppx_v4.rewrite_signature ~jsxMode code | _ -> code [@@raises Invalid_argument, Failure] diff --git a/cli/reactjs_jsx_ppx_v4.ml b/cli/reactjs_jsx_ppx_v4.ml index c93cbf78..af0a1bfa 100644 --- a/cli/reactjs_jsx_ppx_v4.ml +++ b/cli/reactjs_jsx_ppx_v4.ml @@ -48,7 +48,11 @@ let getOrDefaultInt ~key ~default fields = let getOrDefaultString ~key ~default fields = fields |> getJsxConfigByKey ~key ~type_:String |> Option.value ~default -type jsxConfig = {version: int; module_: string; mode: string} +type jsxConfig = { + mutable version: int; + mutable module_: string; + mutable mode: string; +} let updateConfig {version; module_; mode} payload = let fields = getJsxConfig payload in @@ -1292,8 +1296,8 @@ let transformJsxCall ~config mapper callExpression callArguments attrs = "JSX: `createElement` should be preceeded by a module name.") (* Foo.createElement(~prop1=foo, ~prop2=bar, ~children=[], ()) *) | {loc; txt = Ldot (modulePath, ("createElement" | "make"))} -> - transformUppercaseCall3 ~config modulePath mapper loc attrs - callExpression callArguments + transformUppercaseCall3 ~config modulePath mapper loc attrs callExpression + callArguments (* div(~prop1=foo, ~prop2=bar, ~children=[bla], ()) *) (* turn that into ReactDOMRe.createElement(~props=ReactDOMRe.props(~props1=foo, ~props2=bar, ()), [|bla|]) *) From af563dfd75f4bd5bd5959f2830b05647266a9a24 Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Thu, 7 Jul 2022 16:15:13 +0200 Subject: [PATCH 28/94] Make update imperative. --- cli/reactjs_jsx_ppx_v4.ml | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/cli/reactjs_jsx_ppx_v4.ml b/cli/reactjs_jsx_ppx_v4.ml index af0a1bfa..cb2ab4cb 100644 --- a/cli/reactjs_jsx_ppx_v4.ml +++ b/cli/reactjs_jsx_ppx_v4.ml @@ -39,14 +39,12 @@ let getJsxConfigByKey ~key ~type_ recordFields = | [] -> None | [v] | v :: _ -> Some v -let getOrDefaultInt ~key ~default fields = - fields - |> getJsxConfigByKey ~key ~type_:Int - |> Option.map int_of_string_opt - |> Option.join |> Option.value ~default +let getInt ~key fields = + match fields |> getJsxConfigByKey ~key ~type_:Int with + | None -> None + | Some s -> int_of_string_opt s -let getOrDefaultString ~key ~default fields = - fields |> getJsxConfigByKey ~key ~type_:String |> Option.value ~default +let getString ~key fields = fields |> getJsxConfigByKey ~key ~type_:String type jsxConfig = { mutable version: int; @@ -54,20 +52,22 @@ type jsxConfig = { mutable mode: string; } -let updateConfig {version; module_; mode} payload = +let updateConfig config payload = let fields = getJsxConfig payload in - let version = getOrDefaultInt ~key:"version" ~default:version fields in - let module_ = getOrDefaultString ~key:"module" ~default:module_ fields in - let mode = getOrDefaultString ~key:"mode" ~default:mode fields in - {version; module_; mode} + (match getInt ~key:"version" fields with + | None -> () + | Some i -> config.version <- i); + (match getString ~key:"module" fields with + | None -> () + | Some s -> config.module_ <- s); + match getString ~key:"mode" fields with + | None -> () + | Some s -> config.mode <- s let isJsxConfigAttr ((loc, _) : attribute) = loc.txt = "jsxConfig" -let processJsxConfgUpdate attribute config = - if isJsxConfigAttr attribute then - let _, payload = attribute in - updateConfig config payload - else config +let processConfigAttribute attribute config = + if isJsxConfigAttr attribute then updateConfig config (snd attribute) let rec find_opt p = function | [] -> None From e61a03c2b2c65acbafaa92e4db250adfa1c11e0b Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Thu, 7 Jul 2022 16:20:14 +0200 Subject: [PATCH 29/94] Add str item and sig item. --- cli/reactjs_jsx_ppx_v4.ml | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/cli/reactjs_jsx_ppx_v4.ml b/cli/reactjs_jsx_ppx_v4.ml index cb2ab4cb..73f12c4a 100644 --- a/cli/reactjs_jsx_ppx_v4.ml +++ b/cli/reactjs_jsx_ppx_v4.ml @@ -1328,13 +1328,17 @@ let signature mapper signature = @@ reactComponentSignatureTransform mapper signature [@@raises Invalid_argument] -let structure nestedModules mapper structure = - match structure with - | structures -> +let signature_item mapper item = default_mapper.signature_item mapper item + +let structure nestedModules mapper items = + match items with + | items -> default_mapper.structure mapper - @@ reactComponentTransform nestedModules mapper structures + @@ reactComponentTransform nestedModules mapper items [@@raises Invalid_argument] +let structure_item mapper item = default_mapper.structure_item mapper item + let expr ~config mapper expression = match expression with (* Does the function application have the @JSX attribute? *) @@ -1431,7 +1435,15 @@ let jsxMapper ~config nestedModules = let structure = structure nestedModules in let module_binding = module_binding nestedModules in let expr = expr ~config in - {default_mapper with structure; expr; signature; module_binding} + { + default_mapper with + expr; + module_binding; + signature; + signature_item; + structure; + structure_item; + } [@@raises Invalid_argument, Failure] let rewrite_implementation ~jsxMode (code : Parsetree.structure) : From eefd1b32a35caf7cc4dd9c01733a5aba0d6c898b Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Thu, 7 Jul 2022 16:23:41 +0200 Subject: [PATCH 30/94] Hook up config update to V4. --- cli/reactjs_jsx_ppx_v4.ml | 14 ++++++++++++-- .../react/expected/fileLevelConfig.res_v4_cls.txt | 2 +- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/cli/reactjs_jsx_ppx_v4.ml b/cli/reactjs_jsx_ppx_v4.ml index 73f12c4a..25041d3e 100644 --- a/cli/reactjs_jsx_ppx_v4.ml +++ b/cli/reactjs_jsx_ppx_v4.ml @@ -1328,7 +1328,11 @@ let signature mapper signature = @@ reactComponentSignatureTransform mapper signature [@@raises Invalid_argument] -let signature_item mapper item = default_mapper.signature_item mapper item +let signature_item ~config mapper item = + (match item.psig_desc with + | Psig_attribute attr -> processConfigAttribute attr config + | _ -> ()); + default_mapper.signature_item mapper item let structure nestedModules mapper items = match items with @@ -1337,7 +1341,11 @@ let structure nestedModules mapper items = @@ reactComponentTransform nestedModules mapper items [@@raises Invalid_argument] -let structure_item mapper item = default_mapper.structure_item mapper item +let structure_item ~config mapper item = + (match item.pstr_desc with + | Pstr_attribute attr -> processConfigAttribute attr config + | _ -> ()); + default_mapper.structure_item mapper item let expr ~config mapper expression = match expression with @@ -1433,6 +1441,8 @@ let module_binding nestedModules mapper module_binding = (* TODO: some line number might still be wrong *) let jsxMapper ~config nestedModules = let structure = structure nestedModules in + let structure_item = structure_item ~config in + let signature_item = signature_item ~config in let module_binding = module_binding nestedModules in let expr = expr ~config in { diff --git a/tests/ppx/react/expected/fileLevelConfig.res_v4_cls.txt b/tests/ppx/react/expected/fileLevelConfig.res_v4_cls.txt index 1fc53953..a67a65e5 100644 --- a/tests/ppx/react/expected/fileLevelConfig.res_v4_cls.txt +++ b/tests/ppx/react/expected/fileLevelConfig.res_v4_cls.txt @@ -2,7 +2,7 @@ type props<'msg> = {key?: string, msg: 'msg} let make = ({msg, _}: props<'msg>) => { - ReactDOMRe.createDOMElementVariadic("div", [{msg->React.string}]) + ReactDOM.jsx("div", {children: {msg->React.string}}) } let make = { let \"FileLevelConfig" = (props: props<_>) => make(props) From 1eebb57435c5408739c7bdf3b6f8f2c9ecb304da Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Thu, 7 Jul 2022 16:28:42 +0200 Subject: [PATCH 31/94] Rename V4-specific visitor with V4. --- cli/reactjs_jsx_ppx_v4.ml | 41 +++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/cli/reactjs_jsx_ppx_v4.ml b/cli/reactjs_jsx_ppx_v4.ml index 25041d3e..570062a7 100644 --- a/cli/reactjs_jsx_ppx_v4.ml +++ b/cli/reactjs_jsx_ppx_v4.ml @@ -1323,31 +1323,19 @@ let transformJsxCall ~config mapper callExpression callArguments attrs = name.") [@@raises Invalid_argument] -let signature mapper signature = +let signatureV4 mapper signature = default_mapper.signature mapper @@ reactComponentSignatureTransform mapper signature [@@raises Invalid_argument] -let signature_item ~config mapper item = - (match item.psig_desc with - | Psig_attribute attr -> processConfigAttribute attr config - | _ -> ()); - default_mapper.signature_item mapper item - -let structure nestedModules mapper items = +let structureV4 nestedModules mapper items = match items with | items -> default_mapper.structure mapper @@ reactComponentTransform nestedModules mapper items [@@raises Invalid_argument] -let structure_item ~config mapper item = - (match item.pstr_desc with - | Pstr_attribute attr -> processConfigAttribute attr config - | _ -> ()); - default_mapper.structure_item mapper item - -let expr ~config mapper expression = +let exprV4 ~config mapper expression = match expression with (* Does the function application have the @JSX attribute? *) | {pexp_desc = Pexp_apply (callExpression, callArguments); pexp_attributes} @@ -1431,7 +1419,7 @@ let expr ~config mapper expression = | e -> default_mapper.expr mapper e [@@raises Invalid_argument] -let module_binding nestedModules mapper module_binding = +let module_bindingV4 nestedModules mapper module_binding = let _ = nestedModules := module_binding.pmb_name.txt :: !nestedModules in let mapped = default_mapper.module_binding mapper module_binding in let _ = nestedModules := List.tl !nestedModules in @@ -1440,11 +1428,22 @@ let module_binding nestedModules mapper module_binding = (* TODO: some line number might still be wrong *) let jsxMapper ~config nestedModules = - let structure = structure nestedModules in - let structure_item = structure_item ~config in - let signature_item = signature_item ~config in - let module_binding = module_binding nestedModules in - let expr = expr ~config in + let structure_item mapper item = + (match item.pstr_desc with + | Pstr_attribute attr -> processConfigAttribute attr config + | _ -> ()); + default_mapper.structure_item mapper item + in + let signature_item mapper item = + (match item.psig_desc with + | Psig_attribute attr -> processConfigAttribute attr config + | _ -> ()); + default_mapper.signature_item mapper item + in + + let structure = structureV4 nestedModules in + let module_binding = module_bindingV4 nestedModules in + let expr = exprV4 ~config in { default_mapper with expr; From 78a30dd3116d32a86a58cac0aca4f1d01e56a8c7 Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Thu, 7 Jul 2022 16:32:22 +0200 Subject: [PATCH 32/94] Move V4 inside. --- cli/reactjs_jsx_ppx.ml | 1494 +++++++++++++++++++++++++++++++++++- cli/reactjs_jsx_ppx_v4.ml | 1472 ----------------------------------- cli/reactjs_jsx_ppx_v4.mli | 69 -- 3 files changed, 1490 insertions(+), 1545 deletions(-) delete mode 100644 cli/reactjs_jsx_ppx_v4.ml delete mode 100644 cli/reactjs_jsx_ppx_v4.mli diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index dcb5541b..783da5c2 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -1,11 +1,1497 @@ open Parsetree +module V4 = struct + open Ast_helper + open Ast_mapper + open Asttypes + open Parsetree + open Longident + + let getJsxConfig payload = + match payload with + | PStr + ({ + pstr_desc = + Pstr_eval ({pexp_desc = Pexp_record (recordFields, None)}, _); + } + :: _rest) -> + recordFields + | _ -> [] + + type configKey = Int | String + + let getJsxConfigByKey ~key ~type_ recordFields = + let values = + List.filter_map + (fun ((lid, expr) : Longident.t Location.loc * expression) -> + match (type_, lid, expr) with + | ( Int, + {txt = Lident k}, + {pexp_desc = Pexp_constant (Pconst_integer (value, None))} ) + when k = key -> + Some value + | ( String, + {txt = Lident k}, + {pexp_desc = Pexp_constant (Pconst_string (value, None))} ) + when k = key -> + Some value + | _ -> None) + recordFields + in + match values with + | [] -> None + | [v] | v :: _ -> Some v + + let getInt ~key fields = + match fields |> getJsxConfigByKey ~key ~type_:Int with + | None -> None + | Some s -> int_of_string_opt s + + let getString ~key fields = fields |> getJsxConfigByKey ~key ~type_:String + + type jsxConfig = { + mutable version: int; + mutable module_: string; + mutable mode: string; + } + + let updateConfig config payload = + let fields = getJsxConfig payload in + (match getInt ~key:"version" fields with + | None -> () + | Some i -> config.version <- i); + (match getString ~key:"module" fields with + | None -> () + | Some s -> config.module_ <- s); + match getString ~key:"mode" fields with + | None -> () + | Some s -> config.mode <- s + + let isJsxConfigAttr ((loc, _) : attribute) = loc.txt = "jsxConfig" + + let processConfigAttribute attribute config = + if isJsxConfigAttr attribute then updateConfig config (snd attribute) + + let rec find_opt p = function + | [] -> None + | x :: l -> if p x then Some x else find_opt p l + + let nolabel = Nolabel + + let labelled str = Labelled str + + let isOptional str = + match str with + | Optional _ -> true + | _ -> false + + let isLabelled str = + match str with + | Labelled _ -> true + | _ -> false + + let isForwardRef = function + | {pexp_desc = Pexp_ident {txt = Ldot (Lident "React", "forwardRef")}} -> + true + | _ -> false + + let getLabel str = + match str with + | Optional str | Labelled str -> str + | Nolabel -> "" + + let optionIdent = Lident "option" + + let optionalAttr = [({txt = "ns.optional"; loc = Location.none}, PStr [])] + + let constantString ~loc str = + Ast_helper.Exp.constant ~loc (Pconst_string (str, None)) + + (* {} empty object in Js *) + let recordWithOnlyKey ~loc = + Exp.record ~loc + (* {key: @optional None} *) + [ + ( {loc; txt = Lident "key"}, + Exp.construct ~attrs:optionalAttr {loc; txt = Lident "None"} None ); + ] + None + + let safeTypeFromValue valueStr = + let valueStr = getLabel valueStr in + match String.sub valueStr 0 1 with + | "_" -> "T" ^ valueStr + | _ -> valueStr + [@@raises Invalid_argument] + + let keyType loc = Typ.constr ~loc {loc; txt = Lident "string"} [] + + let refType loc = + Typ.constr ~loc + {loc; txt = Ldot (Ldot (Lident "ReactDOM", "Ref"), "currentDomRef")} + [] + + type 'a children = ListLiteral of 'a | Exact of 'a + + (* if children is a list, convert it to an array while mapping each element. If not, just map over it, as usual *) + let transformChildrenIfListUpper ~loc ~mapper theList = + let rec transformChildren_ theList accum = + (* not in the sense of converting a list to an array; convert the AST + reprensentation of a list to the AST reprensentation of an array *) + match theList with + | {pexp_desc = Pexp_construct ({txt = Lident "[]"}, None)} -> ( + match accum with + | [singleElement] -> Exact singleElement + | accum -> ListLiteral (Exp.array ~loc (List.rev accum))) + | { + pexp_desc = + Pexp_construct + ({txt = Lident "::"}, Some {pexp_desc = Pexp_tuple [v; acc]}); + } -> + transformChildren_ acc (mapper.expr mapper v :: accum) + | notAList -> Exact (mapper.expr mapper notAList) + in + transformChildren_ theList [] + + let transformChildrenIfList ~loc ~mapper theList = + let rec transformChildren_ theList accum = + (* not in the sense of converting a list to an array; convert the AST + reprensentation of a list to the AST reprensentation of an array *) + match theList with + | {pexp_desc = Pexp_construct ({txt = Lident "[]"}, None)} -> + Exp.array ~loc (List.rev accum) + | { + pexp_desc = + Pexp_construct + ({txt = Lident "::"}, Some {pexp_desc = Pexp_tuple [v; acc]}); + } -> + transformChildren_ acc (mapper.expr mapper v :: accum) + | notAList -> mapper.expr mapper notAList + in + transformChildren_ theList [] + + let extractChildren ?(removeLastPositionUnit = false) ~loc propsAndChildren = + let rec allButLast_ lst acc = + match lst with + | [] -> [] + | [(Nolabel, {pexp_desc = Pexp_construct ({txt = Lident "()"}, None)})] -> + acc + | (Nolabel, _) :: _rest -> + raise + (Invalid_argument + "JSX: found non-labelled argument before the last position") + | arg :: rest -> allButLast_ rest (arg :: acc) + [@@raises Invalid_argument] + in + let allButLast lst = + allButLast_ lst [] |> List.rev + [@@raises Invalid_argument] + in + match + List.partition + (fun (label, _) -> label = labelled "children") + propsAndChildren + with + | [], props -> + (* no children provided? Place a placeholder list *) + ( Exp.construct ~loc {loc; txt = Lident "[]"} None, + if removeLastPositionUnit then allButLast props else props ) + | [(_, childrenExpr)], props -> + (childrenExpr, if removeLastPositionUnit then allButLast props else props) + | _ -> + raise + (Invalid_argument "JSX: somehow there's more than one `children` label") + [@@raises Invalid_argument] + + let unerasableIgnore loc = + ( {loc; txt = "warning"}, + PStr [Str.eval (Exp.constant (Pconst_string ("-16", None)))] ) + + let merlinFocus = ({loc = Location.none; txt = "merlin.focus"}, PStr []) + + (* Helper method to look up the [@react.component] attribute *) + let hasAttr (loc, _) = loc.txt = "react.component" + + (* Helper method to filter out any attribute that isn't [@react.component] *) + let otherAttrsPure (loc, _) = loc.txt <> "react.component" + + (* Iterate over the attributes and try to find the [@react.component] attribute *) + let hasAttrOnBinding {pvb_attributes} = + find_opt hasAttr pvb_attributes <> None + + (* Finds the name of the variable the binding is assigned to, otherwise raises Invalid_argument *) + let rec getFnName binding = + match binding with + | {ppat_desc = Ppat_var {txt}} -> txt + | {ppat_desc = Ppat_constraint (pat, _)} -> getFnName pat + | _ -> + raise (Invalid_argument "react.component calls cannot be destructured.") + [@@raises Invalid_argument] + + let makeNewBinding binding expression newName = + match binding with + | {pvb_pat = {ppat_desc = Ppat_var ppat_var} as pvb_pat} -> + { + binding with + pvb_pat = + {pvb_pat with ppat_desc = Ppat_var {ppat_var with txt = newName}}; + pvb_expr = expression; + pvb_attributes = [merlinFocus]; + } + | _ -> + raise (Invalid_argument "react.component calls cannot be destructured.") + [@@raises Invalid_argument] + + (* Lookup the filename from the location information on the AST node and turn it into a valid module identifier *) + let filenameFromLoc (pstr_loc : Location.t) = + let fileName = + match pstr_loc.loc_start.pos_fname with + | "" -> !Location.input_name + | fileName -> fileName + in + let fileName = + try Filename.chop_extension (Filename.basename fileName) + with Invalid_argument _ -> fileName + in + let fileName = String.capitalize_ascii fileName in + fileName + + (* Build a string representation of a module name with segments separated by $ *) + let makeModuleName fileName nestedModules fnName = + let fullModuleName = + match (fileName, nestedModules, fnName) with + (* TODO: is this even reachable? It seems like the fileName always exists *) + | "", nestedModules, "make" -> nestedModules + | "", nestedModules, fnName -> List.rev (fnName :: nestedModules) + | fileName, nestedModules, "make" -> fileName :: List.rev nestedModules + | fileName, nestedModules, fnName -> + fileName :: List.rev (fnName :: nestedModules) + in + let fullModuleName = String.concat "$" fullModuleName in + fullModuleName + + (* + AST node builders + These functions help us build AST nodes that are needed when transforming a [@react.component] into a + constructor and a props external +*) + + (* make record from props and spread props if exists *) + let recordFromProps ?(removeKey = false) {pexp_loc} callArguments = + let rec removeLastPositionUnitAux props acc = + match props with + | [] -> acc + | [(Nolabel, {pexp_desc = Pexp_construct ({txt = Lident "()"}, None)})] -> + acc + | (Nolabel, _) :: _rest -> + raise + (Invalid_argument + "JSX: found non-labelled argument before the last position") + | prop :: rest -> removeLastPositionUnitAux rest (prop :: acc) + in + let props, propsToSpread = + removeLastPositionUnitAux callArguments [] + |> List.rev + |> List.partition (fun (label, _) -> label <> labelled "spreadProps") + in + let props = + if removeKey then + props |> List.filter (fun (arg_label, _) -> "key" <> getLabel arg_label) + else props + in + let fields = + props + |> List.map (fun (arg_label, ({pexp_loc} as expr)) -> + (* In case filed label is "key" only then change expression to option *) + if isOptional arg_label then + ( {txt = Lident (getLabel arg_label); loc = pexp_loc}, + {expr with pexp_attributes = optionalAttr} ) + else ({txt = Lident (getLabel arg_label); loc = pexp_loc}, expr)) + in + let spreadFields = + propsToSpread |> List.map (fun (_, expression) -> expression) + in + match spreadFields with + | [] -> + {pexp_desc = Pexp_record (fields, None); pexp_loc; pexp_attributes = []} + | [spreadProps] -> + { + pexp_desc = Pexp_record (fields, Some spreadProps); + pexp_loc; + pexp_attributes = []; + } + | spreadProps :: _ -> + { + pexp_desc = Pexp_record (fields, Some spreadProps); + pexp_loc; + pexp_attributes = []; + } + + (* make type params for type props<'id, 'name, ...> *) + let makePropsTypeParamsTvar namedTypeList = + namedTypeList + |> List.filter_map (fun (_, label, _, _) -> + if label = "key" || label = "ref" then None + else Some (Typ.var label, Invariant)) + + (* make type params for make fn arguments *) + (* let make = ({id, name, children}: props<'id, 'name, 'children>) *) + let makePropsTypeParams namedTypeList = + namedTypeList + |> List.filter_map (fun (_isOptional, label, _, _interiorType) -> + if label = "key" || label = "ref" then None else Some (Typ.var label)) + + (* make type params for make sig arguments *) + (* let make: React.componentLike>, React.element> *) + let makePropsTypeParamsSig namedTypeList = + namedTypeList + |> List.filter_map (fun (_isOptional, label, _, interiorType) -> + if label = "key" || label = "ref" then None else Some interiorType) + + (* type props<'id, 'name, ...> = { @optional key: string, @optional id: 'id, ... } *) + let makePropsRecordType propsName loc namedTypeList = + let labelDeclList = + namedTypeList + |> List.map (fun (isOptional, label, _, _interiorType) -> + if label = "key" then + Type.field ~loc ~attrs:optionalAttr {txt = label; loc} + (keyType Location.none) + else if label = "ref" then + Type.field ~loc + ~attrs:(if isOptional then optionalAttr else []) + {txt = label; loc} (refType Location.none) + else if isOptional then + Type.field ~loc ~attrs:optionalAttr {txt = label; loc} + (Typ.var label) + else Type.field ~loc {txt = label; loc} (Typ.var label)) + in + (* 'id, 'className, ... *) + let params = makePropsTypeParamsTvar namedTypeList in + Str.type_ Nonrecursive + [ + Type.mk ~loc ~params {txt = propsName; loc} + ~kind:(Ptype_record labelDeclList); + ] + + (* type props<'id, 'name, ...> = { @optional key: string, @optional id: 'id, ... } *) + let makePropsRecordTypeSig propsName loc namedTypeList = + let labelDeclList = + namedTypeList + |> List.map (fun (isOptional, label, _, _interiorType) -> + if label = "key" then + Type.field ~loc ~attrs:optionalAttr {txt = label; loc} + (keyType Location.none) + else if isOptional then + Type.field ~loc ~attrs:optionalAttr {txt = label; loc} + (Typ.var label) + else Type.field ~loc {txt = label; loc} (Typ.var label)) + in + let params = makePropsTypeParamsTvar namedTypeList in + Sig.type_ Nonrecursive + [ + Type.mk ~loc ~params {txt = propsName; loc} + ~kind:(Ptype_record labelDeclList); + ] + + let transformUppercaseCall3 ~config modulePath mapper loc attrs callExpression + callArguments = + let children, argsWithLabels = + extractChildren ~loc ~removeLastPositionUnit:true callArguments + in + let argsForMake = argsWithLabels in + let childrenExpr = transformChildrenIfListUpper ~loc ~mapper children in + let recursivelyTransformedArgsForMake = + argsForMake + |> List.map (fun (label, expression) -> + (label, mapper.expr mapper expression)) + in + let childrenArg = ref None in + let args = + recursivelyTransformedArgsForMake + @ + match childrenExpr with + | Exact children -> [(labelled "children", children)] + | ListLiteral {pexp_desc = Pexp_array list} when list = [] -> [] + | ListLiteral expression -> ( + (* this is a hack to support react components that introspect into their children *) + childrenArg := Some expression; + match config.mode with + | "automatic" -> + [ + ( labelled "children", + Exp.apply + (Exp.ident + {txt = Ldot (Lident "React", "array"); loc = Location.none}) + [(Nolabel, expression)] ); + ] + | _ -> + [ + ( labelled "children", + Exp.ident ~loc {loc; txt = Ldot (Lident "React", "null")} ); + ]) + in + + let isCap str = + let first = String.sub str 0 1 [@@raises Invalid_argument] in + let capped = String.uppercase_ascii first in + first = capped + [@@raises Invalid_argument] + in + let ident = + match modulePath with + | Lident _ -> Ldot (modulePath, "make") + | Ldot (_modulePath, value) as fullPath when isCap value -> + Ldot (fullPath, "make") + | modulePath -> modulePath + in + let isEmptyRecord {pexp_desc} = + match pexp_desc with + | Pexp_record (labelDecls, _) when List.length labelDecls = 0 -> true + | _ -> false + in + + (* handle key, ref, children *) + (* React.createElement(Component.make, props, ...children) *) + match config.mode with + (* The new jsx transform *) + | "automatic" -> + let record = recordFromProps ~removeKey:true callExpression args in + let props = + if isEmptyRecord record then recordWithOnlyKey ~loc else record + in + let keyProp = + args |> List.filter (fun (arg_label, _) -> "key" = getLabel arg_label) + in + let jsxExpr, key = + match (!childrenArg, keyProp) with + | None, (_, keyExpr) :: _ -> + ( Exp.ident ~loc {loc; txt = Ldot (Lident "React", "jsxKeyed")}, + [(nolabel, keyExpr)] ) + | None, [] -> + (Exp.ident ~loc {loc; txt = Ldot (Lident "React", "jsx")}, []) + | Some _, (_, keyExpr) :: _ -> + ( Exp.ident ~loc {loc; txt = Ldot (Lident "React", "jsxsKeyed")}, + [(nolabel, keyExpr)] ) + | Some _, [] -> + (Exp.ident ~loc {loc; txt = Ldot (Lident "React", "jsxs")}, []) + in + Exp.apply ~loc ~attrs jsxExpr + ([(nolabel, Exp.ident ~loc {txt = ident; loc}); (nolabel, props)] @ key) + | _ -> ( + let record = recordFromProps callExpression args in + (* check if record which goes to Foo.make({ ... } as record) empty or not + if empty then change it to {key: @optional None} only for upper case jsx + This would be redundant regarding PR progress https://github.com/rescript-lang/syntax/pull/299 + *) + let props = + if isEmptyRecord record then recordWithOnlyKey ~loc else record + in + match !childrenArg with + | None -> + Exp.apply ~loc ~attrs + (Exp.ident ~loc {loc; txt = Ldot (Lident "React", "createElement")}) + [(nolabel, Exp.ident ~loc {txt = ident; loc}); (nolabel, props)] + | Some children -> + Exp.apply ~loc ~attrs + (Exp.ident ~loc + {loc; txt = Ldot (Lident "React", "createElementVariadic")}) + [ + (nolabel, Exp.ident ~loc {txt = ident; loc}); + (nolabel, props); + (nolabel, children); + ]) + [@@raises Invalid_argument] + + let transformLowercaseCall3 ~config mapper loc attrs callExpression + callArguments id = + let componentNameExpr = constantString ~loc id in + match config.mode with + (* the new jsx transform *) + | "automatic" -> + let children, nonChildrenProps = + extractChildren ~removeLastPositionUnit:true ~loc callArguments + in + let argsForMake = nonChildrenProps in + let childrenExpr = transformChildrenIfListUpper ~loc ~mapper children in + let recursivelyTransformedArgsForMake = + argsForMake + |> List.map (fun (label, expression) -> + (label, mapper.expr mapper expression)) + in + let childrenArg = ref None in + let args = + recursivelyTransformedArgsForMake + @ + match childrenExpr with + | Exact children -> [(labelled "children", children)] + | ListLiteral {pexp_desc = Pexp_array list} when list = [] -> [] + | ListLiteral expression -> + (* this is a hack to support react components that introspect into their children *) + childrenArg := Some expression; + [ + ( labelled "children", + Exp.apply + (Exp.ident + {txt = Ldot (Lident "React", "array"); loc = Location.none}) + [(Nolabel, expression)] ); + ] + in + let isEmptyRecord {pexp_desc} = + match pexp_desc with + | Pexp_record (labelDecls, _) when List.length labelDecls = 0 -> true + | _ -> false + in + let record = recordFromProps ~removeKey:true callExpression args in + let props = + if isEmptyRecord record then recordWithOnlyKey ~loc else record + in + let keyProp = + args |> List.filter (fun (arg_label, _) -> "key" = getLabel arg_label) + in + let jsxExpr, key = + match (!childrenArg, keyProp) with + | None, (_, keyExpr) :: _ -> + ( Exp.ident ~loc {loc; txt = Ldot (Lident "ReactDOM", "jsxKeyed")}, + [(nolabel, keyExpr)] ) + | None, [] -> + (Exp.ident ~loc {loc; txt = Ldot (Lident "ReactDOM", "jsx")}, []) + | Some _, (_, keyExpr) :: _ -> + ( Exp.ident ~loc {loc; txt = Ldot (Lident "ReactDOM", "jsxsKeyed")}, + [(nolabel, keyExpr)] ) + | Some _, [] -> + (Exp.ident ~loc {loc; txt = Ldot (Lident "ReactDOM", "jsxs")}, []) + in + Exp.apply ~loc ~attrs jsxExpr + ([(nolabel, componentNameExpr); (nolabel, props)] @ key) + | _ -> + let children, nonChildrenProps = extractChildren ~loc callArguments in + let childrenExpr = transformChildrenIfList ~loc ~mapper children in + let createElementCall = + match children with + (* [@JSX] div(~children=[a]), coming from
a
*) + | { + pexp_desc = + ( Pexp_construct + ({txt = Lident "::"}, Some {pexp_desc = Pexp_tuple _}) + | Pexp_construct ({txt = Lident "[]"}, None) ); + } -> + "createDOMElementVariadic" + (* [@JSX] div(~children= value), coming from
...(value)
*) + | _ -> + raise + (Invalid_argument + "A spread as a DOM element's children don't make sense written \ + together. You can simply remove the spread.") + in + let args = + match nonChildrenProps with + | [_justTheUnitArgumentAtEnd] -> + [ + (* "div" *) + (nolabel, componentNameExpr); + (* [|moreCreateElementCallsHere|] *) + (nolabel, childrenExpr); + ] + | nonEmptyProps -> + let propsCall = + Exp.apply ~loc + (Exp.ident ~loc + {loc; txt = Ldot (Lident "ReactDOMRe", "domProps")}) + (nonEmptyProps + |> List.map (fun (label, expression) -> + (label, mapper.expr mapper expression))) + in + [ + (* "div" *) + (nolabel, componentNameExpr); + (* ReactDOMRe.domProps(~className=blabla, ~foo=bar, ()) *) + (labelled "props", propsCall); + (* [|moreCreateElementCallsHere|] *) + (nolabel, childrenExpr); + ] + in + Exp.apply + ~loc (* throw away the [@JSX] attribute and keep the others, if any *) + ~attrs + (* ReactDOMRe.createElement *) + (Exp.ident ~loc + {loc; txt = Ldot (Lident "ReactDOMRe", createElementCall)}) + args + [@@raises Invalid_argument] + + let rec recursivelyTransformNamedArgsForMake mapper expr args newtypes = + let expr = mapper.expr mapper expr in + match expr.pexp_desc with + (* TODO: make this show up with a loc. *) + | Pexp_fun (Labelled "key", _, _, _) | Pexp_fun (Optional "key", _, _, _) -> + raise + (Invalid_argument + "Key cannot be accessed inside of a component. Don't worry - you \ + can always key a component from its parent!") + | Pexp_fun (Labelled "ref", _, _, _) | Pexp_fun (Optional "ref", _, _, _) -> + raise + (Invalid_argument + "Ref cannot be passed as a normal prop. Please use `forwardRef` API \ + instead.") + | Pexp_fun (arg, default, pattern, expression) + when isOptional arg || isLabelled arg -> + let () = + match (isOptional arg, pattern, default) with + | true, {ppat_desc = Ppat_constraint (_, {ptyp_desc})}, None -> ( + match ptyp_desc with + | Ptyp_constr ({txt = Lident "option"}, [_]) -> () + | _ -> + let currentType = + match ptyp_desc with + | Ptyp_constr ({txt}, []) -> + String.concat "." (Longident.flatten txt) + | Ptyp_constr ({txt}, _innerTypeArgs) -> + String.concat "." (Longident.flatten txt) ^ "(...)" + | _ -> "..." + in + Location.prerr_warning pattern.ppat_loc + (Preprocessor + (Printf.sprintf + "React: optional argument annotations must have explicit \ + `option`. Did you mean `option(%s)=?`?" + currentType))) + | _ -> () + in + let alias = + match pattern with + | {ppat_desc = Ppat_alias (_, {txt}) | Ppat_var {txt}} -> txt + | {ppat_desc = Ppat_any} -> "_" + | _ -> getLabel arg + in + let type_ = + match pattern with + | {ppat_desc = Ppat_constraint (_, type_)} -> Some type_ + | _ -> None + in + + recursivelyTransformNamedArgsForMake mapper expression + ((arg, default, pattern, alias, pattern.ppat_loc, type_) :: args) + newtypes + | Pexp_fun + ( Nolabel, + _, + {ppat_desc = Ppat_construct ({txt = Lident "()"}, _) | Ppat_any}, + _expression ) -> + (args, newtypes, None) + | Pexp_fun + ( Nolabel, + _, + { + ppat_desc = + Ppat_var {txt} | Ppat_constraint ({ppat_desc = Ppat_var {txt}}, _); + }, + _expression ) -> + (args, newtypes, Some txt) + | Pexp_fun (Nolabel, _, pattern, _expression) -> + Location.raise_errorf ~loc:pattern.ppat_loc + "React: react.component refs only support plain arguments and type \ + annotations." + | Pexp_newtype (label, expression) -> + recursivelyTransformNamedArgsForMake mapper expression args + (label :: newtypes) + | Pexp_constraint (expression, _typ) -> + recursivelyTransformNamedArgsForMake mapper expression args newtypes + | _ -> (args, newtypes, None) + [@@raises Invalid_argument] + + let argToType types (name, default, _noLabelName, _alias, loc, type_) = + match (type_, name, default) with + | Some {ptyp_desc = Ptyp_constr ({txt = Lident "option"}, [type_])}, name, _ + when isOptional name -> + ( true, + getLabel name, + [], + { + type_ with + ptyp_desc = + Ptyp_constr ({loc = type_.ptyp_loc; txt = optionIdent}, [type_]); + } ) + :: types + | Some type_, name, Some _default -> + ( false, + getLabel name, + [], + { + ptyp_desc = Ptyp_constr ({loc; txt = optionIdent}, [type_]); + ptyp_loc = loc; + ptyp_attributes = []; + } ) + :: types + | Some type_, name, _ -> (false, getLabel name, [], type_) :: types + | None, name, _ when isOptional name -> + ( true, + getLabel name, + [], + { + ptyp_desc = + Ptyp_constr + ( {loc; txt = optionIdent}, + [ + { + ptyp_desc = Ptyp_var (safeTypeFromValue name); + ptyp_loc = loc; + ptyp_attributes = []; + }; + ] ); + ptyp_loc = loc; + ptyp_attributes = []; + } ) + :: types + | None, name, _ when isLabelled name -> + ( false, + getLabel name, + [], + { + ptyp_desc = Ptyp_var (safeTypeFromValue name); + ptyp_loc = loc; + ptyp_attributes = []; + } ) + :: types + | _ -> types + [@@raises Invalid_argument] + + let argWithDefaultValue (name, default, _, _, _, _) = + match default with + | Some default when isOptional name -> Some (getLabel name, default) + | _ -> None + [@@raises Invalid_argument] + + let argToConcreteType types (name, _loc, type_) = + match name with + | name when isLabelled name -> (false, getLabel name, [], type_) :: types + | name when isOptional name -> (true, getLabel name, [], type_) :: types + | _ -> types + + let transformComponentDefinition nestedModules mapper structure + returnStructures = + match structure with + (* external *) + | { + pstr_loc; + pstr_desc = + Pstr_primitive ({pval_attributes; pval_type} as value_description); + } as pstr -> ( + match List.filter hasAttr pval_attributes with + | [] -> structure :: returnStructures + | [_] -> + let rec getPropTypes types ({ptyp_loc; ptyp_desc} as fullType) = + match ptyp_desc with + | Ptyp_arrow (name, type_, ({ptyp_desc = Ptyp_arrow _} as rest)) + when isLabelled name || isOptional name -> + getPropTypes ((name, ptyp_loc, type_) :: types) rest + | Ptyp_arrow (Nolabel, _type, rest) -> getPropTypes types rest + | Ptyp_arrow (name, type_, returnValue) + when isLabelled name || isOptional name -> + (returnValue, (name, returnValue.ptyp_loc, type_) :: types) + | _ -> (fullType, types) + in + let innerType, propTypes = getPropTypes [] pval_type in + let namedTypeList = List.fold_left argToConcreteType [] propTypes in + let retPropsType = + Typ.constr ~loc:pstr_loc + (Location.mkloc (Lident "props") pstr_loc) + (makePropsTypeParams namedTypeList) + in + (* type props<'id, 'name> = { @optional key: string, @optional id: 'id, ... } *) + let propsRecordType = + makePropsRecordType "props" Location.none + ((true, "key", [], keyType pstr_loc) :: namedTypeList) + in + (* can't be an arrow because it will defensively uncurry *) + let newExternalType = + Ptyp_constr + ( {loc = pstr_loc; txt = Ldot (Lident "React", "componentLike")}, + [retPropsType; innerType] ) + in + let newStructure = + { + pstr with + pstr_desc = + Pstr_primitive + { + value_description with + pval_type = {pval_type with ptyp_desc = newExternalType}; + pval_attributes = List.filter otherAttrsPure pval_attributes; + }; + } + in + propsRecordType :: newStructure :: returnStructures + | _ -> + raise + (Invalid_argument + "Only one react.component call can exist on a component at one \ + time")) + (* let component = ... *) + | {pstr_loc; pstr_desc = Pstr_value (recFlag, valueBindings)} -> + let fileName = filenameFromLoc pstr_loc in + let emptyLoc = Location.in_file fileName in + let mapBinding binding = + if hasAttrOnBinding binding then + let bindingLoc = binding.pvb_loc in + let bindingPatLoc = binding.pvb_pat.ppat_loc in + let binding = + { + binding with + pvb_pat = {binding.pvb_pat with ppat_loc = emptyLoc}; + pvb_loc = emptyLoc; + } + in + let fnName = getFnName binding.pvb_pat in + let internalFnName = fnName ^ "$Internal" in + let fullModuleName = makeModuleName fileName !nestedModules fnName in + let modifiedBindingOld binding = + let expression = binding.pvb_expr in + (* TODO: there is a long-tail of unsupported features inside of blocks - Pexp_letmodule , Pexp_letexception , Pexp_ifthenelse *) + let rec spelunkForFunExpression expression = + match expression with + (* let make = (~prop) => ... *) + | {pexp_desc = Pexp_fun _} | {pexp_desc = Pexp_newtype _} -> + expression + (* let make = {let foo = bar in (~prop) => ...} *) + | {pexp_desc = Pexp_let (_recursive, _vbs, returnExpression)} -> + (* here's where we spelunk! *) + spelunkForFunExpression returnExpression + (* let make = React.forwardRef((~prop) => ...) *) + | { + pexp_desc = + Pexp_apply + (_wrapperExpression, [(Nolabel, innerFunctionExpression)]); + } -> + spelunkForFunExpression innerFunctionExpression + | { + pexp_desc = + Pexp_sequence (_wrapperExpression, innerFunctionExpression); + } -> + spelunkForFunExpression innerFunctionExpression + | {pexp_desc = Pexp_constraint (innerFunctionExpression, _typ)} -> + spelunkForFunExpression innerFunctionExpression + | _ -> + raise + (Invalid_argument + "react.component calls can only be on function \ + definitions or component wrappers (forwardRef, memo).") + [@@raises Invalid_argument] + in + spelunkForFunExpression expression + in + let modifiedBinding binding = + let hasApplication = ref false in + let wrapExpressionWithBinding expressionFn expression = + Vb.mk ~loc:bindingLoc + ~attrs:(List.filter otherAttrsPure binding.pvb_attributes) + (Pat.var ~loc:bindingPatLoc {loc = bindingPatLoc; txt = fnName}) + (expressionFn expression) + in + let expression = binding.pvb_expr in + let unerasableIgnoreExp exp = + { + exp with + pexp_attributes = + unerasableIgnore emptyLoc :: exp.pexp_attributes; + } + in + (* TODO: there is a long-tail of unsupported features inside of blocks - Pexp_letmodule , Pexp_letexception , Pexp_ifthenelse *) + let rec spelunkForFunExpression expression = + match expression with + (* let make = (~prop) => ... with no final unit *) + | { + pexp_desc = + Pexp_fun + ( ((Labelled _ | Optional _) as label), + default, + pattern, + ({pexp_desc = Pexp_fun _} as internalExpression) ); + } -> + let wrap, hasUnit, hasForwardRef, exp = + spelunkForFunExpression internalExpression + in + ( wrap, + hasUnit, + hasForwardRef, + unerasableIgnoreExp + { + expression with + pexp_desc = Pexp_fun (label, default, pattern, exp); + } ) + (* let make = (()) => ... *) + (* let make = (_) => ... *) + | { + pexp_desc = + Pexp_fun + ( Nolabel, + _default, + { + ppat_desc = + Ppat_construct ({txt = Lident "()"}, _) | Ppat_any; + }, + _internalExpression ); + } -> + ((fun a -> a), true, false, expression) + (* let make = (~prop) => ... *) + | { + pexp_desc = + Pexp_fun + ( (Labelled _ | Optional _), + _default, + _pattern, + _internalExpression ); + } -> + ((fun a -> a), false, false, unerasableIgnoreExp expression) + (* let make = (prop) => ... *) + | { + pexp_desc = + Pexp_fun (_nolabel, _default, pattern, _internalExpression); + } -> + if !hasApplication then + ((fun a -> a), false, false, unerasableIgnoreExp expression) + else + Location.raise_errorf ~loc:pattern.ppat_loc + "React: props need to be labelled arguments.\n\ + \ If you are working with refs be sure to wrap with \ + React.forwardRef.\n\ + \ If your component doesn't have any props use () or _ \ + instead of a name." + (* let make = {let foo = bar in (~prop) => ...} *) + | {pexp_desc = Pexp_let (recursive, vbs, internalExpression)} -> + (* here's where we spelunk! *) + let wrap, hasUnit, hasForwardRef, exp = + spelunkForFunExpression internalExpression + in + ( wrap, + hasUnit, + hasForwardRef, + {expression with pexp_desc = Pexp_let (recursive, vbs, exp)} + ) + (* let make = React.forwardRef((~prop) => ...) *) + | { + pexp_desc = + Pexp_apply (wrapperExpression, [(Nolabel, internalExpression)]); + } -> + let () = hasApplication := true in + let _, hasUnit, _, exp = + spelunkForFunExpression internalExpression + in + let hasForwardRef = isForwardRef wrapperExpression in + ( (fun exp -> Exp.apply wrapperExpression [(nolabel, exp)]), + hasUnit, + hasForwardRef, + exp ) + | { + pexp_desc = Pexp_sequence (wrapperExpression, internalExpression); + } -> + let wrap, hasUnit, hasForwardRef, exp = + spelunkForFunExpression internalExpression + in + ( wrap, + hasUnit, + hasForwardRef, + { + expression with + pexp_desc = Pexp_sequence (wrapperExpression, exp); + } ) + | e -> ((fun a -> a), false, false, e) + in + let wrapExpression, hasUnit, hasForwardRef, expression = + spelunkForFunExpression expression + in + ( wrapExpressionWithBinding wrapExpression, + hasUnit, + hasForwardRef, + expression ) + in + let bindingWrapper, _hasUnit, hasForwardRef, expression = + modifiedBinding binding + in + (* do stuff here! *) + let namedArgList, _newtypes, _forwardRef = + recursivelyTransformNamedArgsForMake mapper + (modifiedBindingOld binding) + [] [] + in + let namedTypeList = List.fold_left argToType [] namedArgList in + (* let _ = ref *) + let vbIgnoreUnusedRef = + Vb.mk (Pat.any ()) (Exp.ident (Location.mknoloc (Lident "ref"))) + in + (* let ref = ref->Js.Nullable.fromOption *) + let vbRefFromOption = + Vb.mk + (Pat.var @@ Location.mknoloc "ref") + (Exp.apply + (Exp.ident + (Location.mknoloc + (Ldot (Ldot (Lident "Js", "Nullable"), "fromOption")))) + [(Nolabel, Exp.ident (Location.mknoloc @@ Lident "ref"))]) + in + let namedArgWithDefaultValueList = + List.filter_map argWithDefaultValue namedArgList + in + let vbMatch (label, default) = + Vb.mk + (Pat.var (Location.mknoloc label)) + (Exp.match_ + (Exp.ident {txt = Lident label; loc = Location.none}) + [ + Exp.case + (Pat.construct + (Location.mknoloc @@ Lident "Some") + (Some (Pat.var (Location.mknoloc label)))) + (Exp.ident (Location.mknoloc @@ Lident label)); + Exp.case + (Pat.construct (Location.mknoloc @@ Lident "None") None) + default; + ]) + in + let vbMatchList = List.map vbMatch namedArgWithDefaultValueList in + (* type props = { ... } *) + let propsRecordType = + makePropsRecordType "props" emptyLoc + (((true, "key", [], keyType emptyLoc) :: namedTypeList) + @ + if hasForwardRef then [(true, "ref", [], refType Location.none)] + else []) + in + let innerExpression = + if hasForwardRef then + Exp.apply + (Exp.ident @@ Location.mknoloc @@ Lident "make") + [ + ( Nolabel, + Exp.record + [ + ( Location.mknoloc @@ Lident "ref", + Exp.apply ~attrs:optionalAttr + (Exp.ident + (Location.mknoloc + (Ldot + (Ldot (Lident "Js", "Nullable"), "toOption")))) + [ + ( Nolabel, + Exp.ident (Location.mknoloc @@ Lident "ref") ); + ] ); + ] + (Some (Exp.ident (Location.mknoloc @@ Lident "props"))) ); + ] + else + Exp.apply + (Exp.ident (Location.mknoloc @@ Lident "make")) + [(Nolabel, Exp.ident (Location.mknoloc @@ Lident "props"))] + in + let fullExpression = + (* React component name should start with uppercase letter *) + (* let make = { let \"App" = props => make(props); \"App" } *) + (* let make = React.forwardRef({ + let \"App" = (props, ref) => make({...props, ref: @optional (Js.Nullabel.toOption(ref))}) + })*) + Exp.fun_ nolabel None + (match namedTypeList with + | [] -> Pat.var @@ Location.mknoloc "props" + | _ -> + Pat.constraint_ + (Pat.var @@ Location.mknoloc "props") + (Typ.constr (Location.mknoloc @@ Lident "props") [Typ.any ()])) + (if hasForwardRef then + Exp.fun_ nolabel None + (Pat.var @@ Location.mknoloc "ref") + innerExpression + else innerExpression) + in + let fullExpression = + match fullModuleName with + | "" -> fullExpression + | txt -> + Exp.let_ Nonrecursive + [ + Vb.mk ~loc:emptyLoc + (Pat.var ~loc:emptyLoc {loc = emptyLoc; txt}) + fullExpression; + ] + (Exp.ident ~loc:emptyLoc {loc = emptyLoc; txt = Lident txt}) + in + let rec returnedExpression patterns ({pexp_desc} as expr) = + match pexp_desc with + | Pexp_fun + ( _arg_label, + _default, + { + ppat_desc = + Ppat_construct ({txt = Lident "()"}, _) | Ppat_any; + }, + expr ) -> + (patterns, expr) + | Pexp_fun (arg_label, _default, {ppat_loc; ppat_desc}, expr) -> ( + if isLabelled arg_label || isOptional arg_label then + returnedExpression + (( {loc = ppat_loc; txt = Lident (getLabel arg_label)}, + Pat.var + ~attrs: + (if isOptional arg_label then optionalAttr else []) + {txt = getLabel arg_label; loc = ppat_loc} ) + :: patterns) + expr + else + (* Special case of nolabel arg "ref" in forwardRef fn *) + (* let make = React.forwardRef(ref => body) *) + match ppat_desc with + | Ppat_var {txt} + | Ppat_constraint ({ppat_desc = Ppat_var {txt}}, _) + when txt = "ref" -> + returnedExpression + (( {loc = ppat_loc; txt = Lident txt}, + Pat.var ~attrs:optionalAttr {txt; loc = ppat_loc} ) + :: patterns) + expr + | _ -> returnedExpression patterns expr) + | _ -> (patterns, expr) + in + let patternsWithLid, expression = returnedExpression [] expression in + let pattern = + match patternsWithLid with + | [] -> Pat.any () + | _ -> Pat.record (List.rev patternsWithLid) Open + in + (* add patttern matching for optional prop value *) + let expression = + if List.length vbMatchList = 0 then expression + else Exp.let_ Nonrecursive vbMatchList expression + in + (* add let _ = ref to ignore unused warning *) + let expression = + match hasForwardRef with + | true -> + let expression = + Exp.let_ Nonrecursive [vbIgnoreUnusedRef] expression + in + Exp.let_ Nonrecursive [vbRefFromOption] expression + | false -> expression + in + let expression = + Exp.fun_ Nolabel None + (Pat.constraint_ pattern + (Typ.constr ~loc:emptyLoc + {txt = Lident "props"; loc = emptyLoc} + (makePropsTypeParams namedTypeList))) + expression + in + (* let make = ({id, name, ...}: props<'id, 'name, ...>) => { ... } *) + let bindings, newBinding = + match recFlag with + | Recursive -> + ( [ + bindingWrapper + (Exp.let_ ~loc:emptyLoc Recursive + [ + makeNewBinding binding expression internalFnName; + Vb.mk + (Pat.var {loc = emptyLoc; txt = fnName}) + fullExpression; + ] + (Exp.ident {loc = emptyLoc; txt = Lident fnName})); + ], + None ) + | Nonrecursive -> + ( [{binding with pvb_expr = expression; pvb_attributes = []}], + Some (bindingWrapper fullExpression) ) + in + (Some propsRecordType, bindings, newBinding) + else (None, [binding], None) + [@@raises Invalid_argument] + in + (* END of mapBinding fn *) + let structuresAndBinding = List.map mapBinding valueBindings in + let otherStructures (type_, binding, newBinding) + (types, bindings, newBindings) = + let types = + match type_ with + | Some type_ -> type_ :: types + | None -> types + in + let newBindings = + match newBinding with + | Some newBinding -> newBinding :: newBindings + | None -> newBindings + in + (types, binding @ bindings, newBindings) + in + let types, bindings, newBindings = + List.fold_right otherStructures structuresAndBinding ([], [], []) + in + types + @ [{pstr_loc; pstr_desc = Pstr_value (recFlag, bindings)}] + @ (match newBindings with + | [] -> [] + | newBindings -> + [{pstr_loc = emptyLoc; pstr_desc = Pstr_value (recFlag, newBindings)}]) + @ returnStructures + | structure -> structure :: returnStructures + [@@raises Invalid_argument] + + let reactComponentTransform nestedModules mapper structures = + List.fold_right + (transformComponentDefinition nestedModules mapper) + structures [] + [@@raises Invalid_argument] + + let transformComponentSignature _mapper signature returnSignatures = + match signature with + | { + psig_loc; + psig_desc = Psig_value ({pval_attributes; pval_type} as psig_desc); + } as psig -> ( + match List.filter hasAttr pval_attributes with + | [] -> signature :: returnSignatures + | [_] -> + let rec getPropTypes types ({ptyp_loc; ptyp_desc} as fullType) = + match ptyp_desc with + | Ptyp_arrow (name, type_, ({ptyp_desc = Ptyp_arrow _} as rest)) + when isOptional name || isLabelled name -> + getPropTypes ((name, ptyp_loc, type_) :: types) rest + | Ptyp_arrow (Nolabel, _type, rest) -> getPropTypes types rest + | Ptyp_arrow (name, type_, returnValue) + when isOptional name || isLabelled name -> + (returnValue, (name, returnValue.ptyp_loc, type_) :: types) + | _ -> (fullType, types) + in + let innerType, propTypes = getPropTypes [] pval_type in + let namedTypeList = List.fold_left argToConcreteType [] propTypes in + let retPropsType = + Typ.constr + (Location.mkloc (Lident "props") psig_loc) + (makePropsTypeParamsSig namedTypeList) + in + let propsRecordType = + makePropsRecordTypeSig "props" Location.none + ((true, "key", [], keyType Location.none) :: namedTypeList) + in + (* can't be an arrow because it will defensively uncurry *) + let newExternalType = + Ptyp_constr + ( {loc = psig_loc; txt = Ldot (Lident "React", "componentLike")}, + [retPropsType; innerType] ) + in + let newStructure = + { + psig with + psig_desc = + Psig_value + { + psig_desc with + pval_type = {pval_type with ptyp_desc = newExternalType}; + pval_attributes = List.filter otherAttrsPure pval_attributes; + }; + } + in + propsRecordType :: newStructure :: returnSignatures + | _ -> + raise + (Invalid_argument + "Only one react.component call can exist on a component at one \ + time")) + | signature -> signature :: returnSignatures + [@@raises Invalid_argument] + + let reactComponentSignatureTransform mapper signatures = + List.fold_right (transformComponentSignature mapper) signatures [] + [@@raises Invalid_argument] + + let transformJsxCall ~config mapper callExpression callArguments attrs = + match callExpression.pexp_desc with + | Pexp_ident caller -> ( + match caller with + | {txt = Lident "createElement"} -> + raise + (Invalid_argument + "JSX: `createElement` should be preceeded by a module name.") + (* Foo.createElement(~prop1=foo, ~prop2=bar, ~children=[], ()) *) + | {loc; txt = Ldot (modulePath, ("createElement" | "make"))} -> + transformUppercaseCall3 ~config modulePath mapper loc attrs + callExpression callArguments + (* div(~prop1=foo, ~prop2=bar, ~children=[bla], ()) *) + (* turn that into + ReactDOMRe.createElement(~props=ReactDOMRe.props(~props1=foo, ~props2=bar, ()), [|bla|]) *) + | {loc; txt = Lident id} -> + transformLowercaseCall3 ~config mapper loc attrs callExpression + callArguments id + | {txt = Ldot (_, anythingNotCreateElementOrMake)} -> + raise + (Invalid_argument + ("JSX: the JSX attribute should be attached to a \ + `YourModuleName.createElement` or `YourModuleName.make` call. \ + We saw `" ^ anythingNotCreateElementOrMake ^ "` instead")) + | {txt = Lapply _} -> + (* don't think there's ever a case where this is reached *) + raise + (Invalid_argument + "JSX: encountered a weird case while processing the code. Please \ + report this!")) + | _ -> + raise + (Invalid_argument + "JSX: `createElement` should be preceeded by a simple, direct \ + module name.") + [@@raises Invalid_argument] + + let signatureV4 mapper signature = + default_mapper.signature mapper + @@ reactComponentSignatureTransform mapper signature + [@@raises Invalid_argument] + + let structureV4 nestedModules mapper items = + match items with + | items -> + default_mapper.structure mapper + @@ reactComponentTransform nestedModules mapper items + [@@raises Invalid_argument] + + let exprV4 ~config mapper expression = + match expression with + (* Does the function application have the @JSX attribute? *) + | {pexp_desc = Pexp_apply (callExpression, callArguments); pexp_attributes} + -> ( + let jsxAttribute, nonJSXAttributes = + List.partition + (fun (attribute, _) -> attribute.txt = "JSX") + pexp_attributes + in + match (jsxAttribute, nonJSXAttributes) with + (* no JSX attribute *) + | [], _ -> default_mapper.expr mapper expression + | _, nonJSXAttributes -> + transformJsxCall ~config mapper callExpression callArguments + nonJSXAttributes) + (* is it a list with jsx attribute? Reason <>foo desugars to [@JSX][foo]*) + | { + pexp_desc = + ( Pexp_construct + ({txt = Lident "::"; loc}, Some {pexp_desc = Pexp_tuple _}) + | Pexp_construct ({txt = Lident "[]"; loc}, None) ); + pexp_attributes; + } as listItems -> ( + let jsxAttribute, nonJSXAttributes = + List.partition + (fun (attribute, _) -> attribute.txt = "JSX") + pexp_attributes + in + match (jsxAttribute, nonJSXAttributes) with + (* no JSX attribute *) + | [], _ -> default_mapper.expr mapper expression + | _, nonJSXAttributes -> + let loc = {loc with loc_ghost = true} in + let fragment = + match config.mode with + | "automatic" -> + Exp.ident ~loc {loc; txt = Ldot (Lident "React", "jsxFragment")} + | "classic" | _ -> + Exp.ident ~loc {loc; txt = Ldot (Lident "ReasonReact", "fragment")} + in + let childrenExpr = transformChildrenIfList ~loc ~mapper listItems in + let args = + [ + (nolabel, fragment); + (match config.mode with + | "automatic" -> + ( nolabel, + Exp.record + [ + ( Location.mknoloc @@ Lident "children", + match childrenExpr with + | {pexp_desc = Pexp_array children} -> ( + match children with + | [] -> recordWithOnlyKey ~loc:Location.none + | [child] -> child + | _ -> childrenExpr) + | _ -> childrenExpr ); + ] + None ) + | "classic" | _ -> (nolabel, childrenExpr)); + ] + in + let countOfChildren = function + | {pexp_desc = Pexp_array children} -> List.length children + | _ -> 0 + in + Exp.apply + ~loc (* throw away the [@JSX] attribute and keep the others, if any *) + ~attrs:nonJSXAttributes + (* ReactDOMRe.createElement *) + (match config.mode with + | "automatic" -> + if countOfChildren childrenExpr > 1 then + Exp.ident ~loc {loc; txt = Ldot (Lident "React", "jsxs")} + else Exp.ident ~loc {loc; txt = Ldot (Lident "React", "jsx")} + | "classic" | _ -> + Exp.ident ~loc + {loc; txt = Ldot (Lident "ReactDOMRe", "createElement")}) + args) + (* Delegate to the default mapper, a deep identity traversal *) + | e -> default_mapper.expr mapper e + [@@raises Invalid_argument] + + let module_bindingV4 nestedModules mapper module_binding = + let _ = nestedModules := module_binding.pmb_name.txt :: !nestedModules in + let mapped = default_mapper.module_binding mapper module_binding in + let _ = nestedModules := List.tl !nestedModules in + mapped + [@@raises Failure] + + (* TODO: some line number might still be wrong *) + let jsxMapper ~config nestedModules = + let structure_item mapper item = + (match item.pstr_desc with + | Pstr_attribute attr -> processConfigAttribute attr config + | _ -> ()); + default_mapper.structure_item mapper item + in + let signature_item mapper item = + (match item.psig_desc with + | Psig_attribute attr -> processConfigAttribute attr config + | _ -> ()); + default_mapper.signature_item mapper item + in + + let structure = structureV4 nestedModules in + let signature = signatureV4 in + let module_binding = module_bindingV4 nestedModules in + let expr = exprV4 ~config in + { + default_mapper with + expr; + module_binding; + signature; + signature_item; + structure; + structure_item; + } + [@@raises Invalid_argument, Failure] + + let rewrite_implementation ~jsxMode (code : Parsetree.structure) : + Parsetree.structure = + let nestedModules = ref [] in + let config = {mode = jsxMode; module_ = ""; version = 4} in + let mapper = jsxMapper ~config nestedModules in + mapper.structure mapper code + [@@raises Invalid_argument, Failure] + + let rewrite_signature ~jsxMode (code : Parsetree.signature) : + Parsetree.signature = + let nestedModules = ref [] in + let config = {mode = jsxMode; module_ = ""; version = 4} in + let mapper = jsxMapper ~config nestedModules in + mapper.signature mapper code + [@@raises Invalid_argument, Failure] +end + let rewrite_implementation ~jsxVersion ~jsxModule ~jsxMode (code : Parsetree.structure) : Parsetree.structure = match (jsxVersion, jsxModule, jsxMode) with | 3, _, _ -> Reactjs_jsx_ppx_v3.rewrite_implementation code - | 4, _, "classic" -> Reactjs_jsx_ppx_v4.rewrite_implementation ~jsxMode code - | 4, _, "automatic" -> Reactjs_jsx_ppx_v4.rewrite_implementation ~jsxMode code + | 4, _, "classic" -> V4.rewrite_implementation ~jsxMode code + | 4, _, "automatic" -> V4.rewrite_implementation ~jsxMode code | _ -> code [@@raises Invalid_argument, Failure] @@ -13,7 +1499,7 @@ let rewrite_signature ~jsxVersion ~jsxModule ~jsxMode (code : Parsetree.signature) : Parsetree.signature = match (jsxVersion, jsxModule, jsxMode) with | 3, _, _ -> Reactjs_jsx_ppx_v3.rewrite_signature code - | 4, _, "classic" -> Reactjs_jsx_ppx_v4.rewrite_signature ~jsxMode code - | 4, _, "automatic" -> Reactjs_jsx_ppx_v4.rewrite_signature ~jsxMode code + | 4, _, "classic" -> V4.rewrite_signature ~jsxMode code + | 4, _, "automatic" -> V4.rewrite_signature ~jsxMode code | _ -> code [@@raises Invalid_argument, Failure] diff --git a/cli/reactjs_jsx_ppx_v4.ml b/cli/reactjs_jsx_ppx_v4.ml deleted file mode 100644 index 570062a7..00000000 --- a/cli/reactjs_jsx_ppx_v4.ml +++ /dev/null @@ -1,1472 +0,0 @@ -open Ast_helper -open Ast_mapper -open Asttypes -open Parsetree -open Longident - -let getJsxConfig payload = - match payload with - | PStr - ({ - pstr_desc = - Pstr_eval ({pexp_desc = Pexp_record (recordFields, None)}, _); - } - :: _rest) -> - recordFields - | _ -> [] - -type configKey = Int | String - -let getJsxConfigByKey ~key ~type_ recordFields = - let values = - List.filter_map - (fun ((lid, expr) : Longident.t Location.loc * expression) -> - match (type_, lid, expr) with - | ( Int, - {txt = Lident k}, - {pexp_desc = Pexp_constant (Pconst_integer (value, None))} ) - when k = key -> - Some value - | ( String, - {txt = Lident k}, - {pexp_desc = Pexp_constant (Pconst_string (value, None))} ) - when k = key -> - Some value - | _ -> None) - recordFields - in - match values with - | [] -> None - | [v] | v :: _ -> Some v - -let getInt ~key fields = - match fields |> getJsxConfigByKey ~key ~type_:Int with - | None -> None - | Some s -> int_of_string_opt s - -let getString ~key fields = fields |> getJsxConfigByKey ~key ~type_:String - -type jsxConfig = { - mutable version: int; - mutable module_: string; - mutable mode: string; -} - -let updateConfig config payload = - let fields = getJsxConfig payload in - (match getInt ~key:"version" fields with - | None -> () - | Some i -> config.version <- i); - (match getString ~key:"module" fields with - | None -> () - | Some s -> config.module_ <- s); - match getString ~key:"mode" fields with - | None -> () - | Some s -> config.mode <- s - -let isJsxConfigAttr ((loc, _) : attribute) = loc.txt = "jsxConfig" - -let processConfigAttribute attribute config = - if isJsxConfigAttr attribute then updateConfig config (snd attribute) - -let rec find_opt p = function - | [] -> None - | x :: l -> if p x then Some x else find_opt p l - -let nolabel = Nolabel - -let labelled str = Labelled str - -let isOptional str = - match str with - | Optional _ -> true - | _ -> false - -let isLabelled str = - match str with - | Labelled _ -> true - | _ -> false - -let isForwardRef = function - | {pexp_desc = Pexp_ident {txt = Ldot (Lident "React", "forwardRef")}} -> true - | _ -> false - -let getLabel str = - match str with - | Optional str | Labelled str -> str - | Nolabel -> "" - -let optionIdent = Lident "option" - -let optionalAttr = [({txt = "ns.optional"; loc = Location.none}, PStr [])] - -let constantString ~loc str = - Ast_helper.Exp.constant ~loc (Pconst_string (str, None)) - -(* {} empty object in Js *) -let recordWithOnlyKey ~loc = - Exp.record ~loc - (* {key: @optional None} *) - [ - ( {loc; txt = Lident "key"}, - Exp.construct ~attrs:optionalAttr {loc; txt = Lident "None"} None ); - ] - None - -let safeTypeFromValue valueStr = - let valueStr = getLabel valueStr in - match String.sub valueStr 0 1 with - | "_" -> "T" ^ valueStr - | _ -> valueStr - [@@raises Invalid_argument] - -let keyType loc = Typ.constr ~loc {loc; txt = Lident "string"} [] - -let refType loc = - Typ.constr ~loc - {loc; txt = Ldot (Ldot (Lident "ReactDOM", "Ref"), "currentDomRef")} - [] - -type 'a children = ListLiteral of 'a | Exact of 'a - -(* if children is a list, convert it to an array while mapping each element. If not, just map over it, as usual *) -let transformChildrenIfListUpper ~loc ~mapper theList = - let rec transformChildren_ theList accum = - (* not in the sense of converting a list to an array; convert the AST - reprensentation of a list to the AST reprensentation of an array *) - match theList with - | {pexp_desc = Pexp_construct ({txt = Lident "[]"}, None)} -> ( - match accum with - | [singleElement] -> Exact singleElement - | accum -> ListLiteral (Exp.array ~loc (List.rev accum))) - | { - pexp_desc = - Pexp_construct - ({txt = Lident "::"}, Some {pexp_desc = Pexp_tuple [v; acc]}); - } -> - transformChildren_ acc (mapper.expr mapper v :: accum) - | notAList -> Exact (mapper.expr mapper notAList) - in - transformChildren_ theList [] - -let transformChildrenIfList ~loc ~mapper theList = - let rec transformChildren_ theList accum = - (* not in the sense of converting a list to an array; convert the AST - reprensentation of a list to the AST reprensentation of an array *) - match theList with - | {pexp_desc = Pexp_construct ({txt = Lident "[]"}, None)} -> - Exp.array ~loc (List.rev accum) - | { - pexp_desc = - Pexp_construct - ({txt = Lident "::"}, Some {pexp_desc = Pexp_tuple [v; acc]}); - } -> - transformChildren_ acc (mapper.expr mapper v :: accum) - | notAList -> mapper.expr mapper notAList - in - transformChildren_ theList [] - -let extractChildren ?(removeLastPositionUnit = false) ~loc propsAndChildren = - let rec allButLast_ lst acc = - match lst with - | [] -> [] - | [(Nolabel, {pexp_desc = Pexp_construct ({txt = Lident "()"}, None)})] -> - acc - | (Nolabel, _) :: _rest -> - raise - (Invalid_argument - "JSX: found non-labelled argument before the last position") - | arg :: rest -> allButLast_ rest (arg :: acc) - [@@raises Invalid_argument] - in - let allButLast lst = - allButLast_ lst [] |> List.rev - [@@raises Invalid_argument] - in - match - List.partition - (fun (label, _) -> label = labelled "children") - propsAndChildren - with - | [], props -> - (* no children provided? Place a placeholder list *) - ( Exp.construct ~loc {loc; txt = Lident "[]"} None, - if removeLastPositionUnit then allButLast props else props ) - | [(_, childrenExpr)], props -> - (childrenExpr, if removeLastPositionUnit then allButLast props else props) - | _ -> - raise - (Invalid_argument "JSX: somehow there's more than one `children` label") - [@@raises Invalid_argument] - -let unerasableIgnore loc = - ( {loc; txt = "warning"}, - PStr [Str.eval (Exp.constant (Pconst_string ("-16", None)))] ) - -let merlinFocus = ({loc = Location.none; txt = "merlin.focus"}, PStr []) - -(* Helper method to look up the [@react.component] attribute *) -let hasAttr (loc, _) = loc.txt = "react.component" - -(* Helper method to filter out any attribute that isn't [@react.component] *) -let otherAttrsPure (loc, _) = loc.txt <> "react.component" - -(* Iterate over the attributes and try to find the [@react.component] attribute *) -let hasAttrOnBinding {pvb_attributes} = find_opt hasAttr pvb_attributes <> None - -(* Finds the name of the variable the binding is assigned to, otherwise raises Invalid_argument *) -let rec getFnName binding = - match binding with - | {ppat_desc = Ppat_var {txt}} -> txt - | {ppat_desc = Ppat_constraint (pat, _)} -> getFnName pat - | _ -> - raise (Invalid_argument "react.component calls cannot be destructured.") - [@@raises Invalid_argument] - -let makeNewBinding binding expression newName = - match binding with - | {pvb_pat = {ppat_desc = Ppat_var ppat_var} as pvb_pat} -> - { - binding with - pvb_pat = - {pvb_pat with ppat_desc = Ppat_var {ppat_var with txt = newName}}; - pvb_expr = expression; - pvb_attributes = [merlinFocus]; - } - | _ -> - raise (Invalid_argument "react.component calls cannot be destructured.") - [@@raises Invalid_argument] - -(* Lookup the filename from the location information on the AST node and turn it into a valid module identifier *) -let filenameFromLoc (pstr_loc : Location.t) = - let fileName = - match pstr_loc.loc_start.pos_fname with - | "" -> !Location.input_name - | fileName -> fileName - in - let fileName = - try Filename.chop_extension (Filename.basename fileName) - with Invalid_argument _ -> fileName - in - let fileName = String.capitalize_ascii fileName in - fileName - -(* Build a string representation of a module name with segments separated by $ *) -let makeModuleName fileName nestedModules fnName = - let fullModuleName = - match (fileName, nestedModules, fnName) with - (* TODO: is this even reachable? It seems like the fileName always exists *) - | "", nestedModules, "make" -> nestedModules - | "", nestedModules, fnName -> List.rev (fnName :: nestedModules) - | fileName, nestedModules, "make" -> fileName :: List.rev nestedModules - | fileName, nestedModules, fnName -> - fileName :: List.rev (fnName :: nestedModules) - in - let fullModuleName = String.concat "$" fullModuleName in - fullModuleName - -(* - AST node builders - These functions help us build AST nodes that are needed when transforming a [@react.component] into a - constructor and a props external -*) - -(* make record from props and spread props if exists *) -let recordFromProps ?(removeKey = false) {pexp_loc} callArguments = - let rec removeLastPositionUnitAux props acc = - match props with - | [] -> acc - | [(Nolabel, {pexp_desc = Pexp_construct ({txt = Lident "()"}, None)})] -> - acc - | (Nolabel, _) :: _rest -> - raise - (Invalid_argument - "JSX: found non-labelled argument before the last position") - | prop :: rest -> removeLastPositionUnitAux rest (prop :: acc) - in - let props, propsToSpread = - removeLastPositionUnitAux callArguments [] - |> List.rev - |> List.partition (fun (label, _) -> label <> labelled "spreadProps") - in - let props = - if removeKey then - props |> List.filter (fun (arg_label, _) -> "key" <> getLabel arg_label) - else props - in - let fields = - props - |> List.map (fun (arg_label, ({pexp_loc} as expr)) -> - (* In case filed label is "key" only then change expression to option *) - if isOptional arg_label then - ( {txt = Lident (getLabel arg_label); loc = pexp_loc}, - {expr with pexp_attributes = optionalAttr} ) - else ({txt = Lident (getLabel arg_label); loc = pexp_loc}, expr)) - in - let spreadFields = - propsToSpread |> List.map (fun (_, expression) -> expression) - in - match spreadFields with - | [] -> - {pexp_desc = Pexp_record (fields, None); pexp_loc; pexp_attributes = []} - | [spreadProps] -> - { - pexp_desc = Pexp_record (fields, Some spreadProps); - pexp_loc; - pexp_attributes = []; - } - | spreadProps :: _ -> - { - pexp_desc = Pexp_record (fields, Some spreadProps); - pexp_loc; - pexp_attributes = []; - } - -(* make type params for type props<'id, 'name, ...> *) -let makePropsTypeParamsTvar namedTypeList = - namedTypeList - |> List.filter_map (fun (_, label, _, _) -> - if label = "key" || label = "ref" then None - else Some (Typ.var label, Invariant)) - -(* make type params for make fn arguments *) -(* let make = ({id, name, children}: props<'id, 'name, 'children>) *) -let makePropsTypeParams namedTypeList = - namedTypeList - |> List.filter_map (fun (_isOptional, label, _, _interiorType) -> - if label = "key" || label = "ref" then None else Some (Typ.var label)) - -(* make type params for make sig arguments *) -(* let make: React.componentLike>, React.element> *) -let makePropsTypeParamsSig namedTypeList = - namedTypeList - |> List.filter_map (fun (_isOptional, label, _, interiorType) -> - if label = "key" || label = "ref" then None else Some interiorType) - -(* type props<'id, 'name, ...> = { @optional key: string, @optional id: 'id, ... } *) -let makePropsRecordType propsName loc namedTypeList = - let labelDeclList = - namedTypeList - |> List.map (fun (isOptional, label, _, _interiorType) -> - if label = "key" then - Type.field ~loc ~attrs:optionalAttr {txt = label; loc} - (keyType Location.none) - else if label = "ref" then - Type.field ~loc - ~attrs:(if isOptional then optionalAttr else []) - {txt = label; loc} (refType Location.none) - else if isOptional then - Type.field ~loc ~attrs:optionalAttr {txt = label; loc} - (Typ.var label) - else Type.field ~loc {txt = label; loc} (Typ.var label)) - in - (* 'id, 'className, ... *) - let params = makePropsTypeParamsTvar namedTypeList in - Str.type_ Nonrecursive - [ - Type.mk ~loc ~params {txt = propsName; loc} - ~kind:(Ptype_record labelDeclList); - ] - -(* type props<'id, 'name, ...> = { @optional key: string, @optional id: 'id, ... } *) -let makePropsRecordTypeSig propsName loc namedTypeList = - let labelDeclList = - namedTypeList - |> List.map (fun (isOptional, label, _, _interiorType) -> - if label = "key" then - Type.field ~loc ~attrs:optionalAttr {txt = label; loc} - (keyType Location.none) - else if isOptional then - Type.field ~loc ~attrs:optionalAttr {txt = label; loc} - (Typ.var label) - else Type.field ~loc {txt = label; loc} (Typ.var label)) - in - let params = makePropsTypeParamsTvar namedTypeList in - Sig.type_ Nonrecursive - [ - Type.mk ~loc ~params {txt = propsName; loc} - ~kind:(Ptype_record labelDeclList); - ] - -let transformUppercaseCall3 ~config modulePath mapper loc attrs callExpression - callArguments = - let children, argsWithLabels = - extractChildren ~loc ~removeLastPositionUnit:true callArguments - in - let argsForMake = argsWithLabels in - let childrenExpr = transformChildrenIfListUpper ~loc ~mapper children in - let recursivelyTransformedArgsForMake = - argsForMake - |> List.map (fun (label, expression) -> - (label, mapper.expr mapper expression)) - in - let childrenArg = ref None in - let args = - recursivelyTransformedArgsForMake - @ - match childrenExpr with - | Exact children -> [(labelled "children", children)] - | ListLiteral {pexp_desc = Pexp_array list} when list = [] -> [] - | ListLiteral expression -> ( - (* this is a hack to support react components that introspect into their children *) - childrenArg := Some expression; - match config.mode with - | "automatic" -> - [ - ( labelled "children", - Exp.apply - (Exp.ident - {txt = Ldot (Lident "React", "array"); loc = Location.none}) - [(Nolabel, expression)] ); - ] - | _ -> - [ - ( labelled "children", - Exp.ident ~loc {loc; txt = Ldot (Lident "React", "null")} ); - ]) - in - - let isCap str = - let first = String.sub str 0 1 [@@raises Invalid_argument] in - let capped = String.uppercase_ascii first in - first = capped - [@@raises Invalid_argument] - in - let ident = - match modulePath with - | Lident _ -> Ldot (modulePath, "make") - | Ldot (_modulePath, value) as fullPath when isCap value -> - Ldot (fullPath, "make") - | modulePath -> modulePath - in - let isEmptyRecord {pexp_desc} = - match pexp_desc with - | Pexp_record (labelDecls, _) when List.length labelDecls = 0 -> true - | _ -> false - in - - (* handle key, ref, children *) - (* React.createElement(Component.make, props, ...children) *) - match config.mode with - (* The new jsx transform *) - | "automatic" -> - let record = recordFromProps ~removeKey:true callExpression args in - let props = - if isEmptyRecord record then recordWithOnlyKey ~loc else record - in - let keyProp = - args |> List.filter (fun (arg_label, _) -> "key" = getLabel arg_label) - in - let jsxExpr, key = - match (!childrenArg, keyProp) with - | None, (_, keyExpr) :: _ -> - ( Exp.ident ~loc {loc; txt = Ldot (Lident "React", "jsxKeyed")}, - [(nolabel, keyExpr)] ) - | None, [] -> - (Exp.ident ~loc {loc; txt = Ldot (Lident "React", "jsx")}, []) - | Some _, (_, keyExpr) :: _ -> - ( Exp.ident ~loc {loc; txt = Ldot (Lident "React", "jsxsKeyed")}, - [(nolabel, keyExpr)] ) - | Some _, [] -> - (Exp.ident ~loc {loc; txt = Ldot (Lident "React", "jsxs")}, []) - in - Exp.apply ~loc ~attrs jsxExpr - ([(nolabel, Exp.ident ~loc {txt = ident; loc}); (nolabel, props)] @ key) - | _ -> ( - let record = recordFromProps callExpression args in - (* check if record which goes to Foo.make({ ... } as record) empty or not - if empty then change it to {key: @optional None} only for upper case jsx - This would be redundant regarding PR progress https://github.com/rescript-lang/syntax/pull/299 - *) - let props = - if isEmptyRecord record then recordWithOnlyKey ~loc else record - in - match !childrenArg with - | None -> - Exp.apply ~loc ~attrs - (Exp.ident ~loc {loc; txt = Ldot (Lident "React", "createElement")}) - [(nolabel, Exp.ident ~loc {txt = ident; loc}); (nolabel, props)] - | Some children -> - Exp.apply ~loc ~attrs - (Exp.ident ~loc - {loc; txt = Ldot (Lident "React", "createElementVariadic")}) - [ - (nolabel, Exp.ident ~loc {txt = ident; loc}); - (nolabel, props); - (nolabel, children); - ]) - [@@raises Invalid_argument] - -let transformLowercaseCall3 ~config mapper loc attrs callExpression - callArguments id = - let componentNameExpr = constantString ~loc id in - match config.mode with - (* the new jsx transform *) - | "automatic" -> - let children, nonChildrenProps = - extractChildren ~removeLastPositionUnit:true ~loc callArguments - in - let argsForMake = nonChildrenProps in - let childrenExpr = transformChildrenIfListUpper ~loc ~mapper children in - let recursivelyTransformedArgsForMake = - argsForMake - |> List.map (fun (label, expression) -> - (label, mapper.expr mapper expression)) - in - let childrenArg = ref None in - let args = - recursivelyTransformedArgsForMake - @ - match childrenExpr with - | Exact children -> [(labelled "children", children)] - | ListLiteral {pexp_desc = Pexp_array list} when list = [] -> [] - | ListLiteral expression -> - (* this is a hack to support react components that introspect into their children *) - childrenArg := Some expression; - [ - ( labelled "children", - Exp.apply - (Exp.ident - {txt = Ldot (Lident "React", "array"); loc = Location.none}) - [(Nolabel, expression)] ); - ] - in - let isEmptyRecord {pexp_desc} = - match pexp_desc with - | Pexp_record (labelDecls, _) when List.length labelDecls = 0 -> true - | _ -> false - in - let record = recordFromProps ~removeKey:true callExpression args in - let props = - if isEmptyRecord record then recordWithOnlyKey ~loc else record - in - let keyProp = - args |> List.filter (fun (arg_label, _) -> "key" = getLabel arg_label) - in - let jsxExpr, key = - match (!childrenArg, keyProp) with - | None, (_, keyExpr) :: _ -> - ( Exp.ident ~loc {loc; txt = Ldot (Lident "ReactDOM", "jsxKeyed")}, - [(nolabel, keyExpr)] ) - | None, [] -> - (Exp.ident ~loc {loc; txt = Ldot (Lident "ReactDOM", "jsx")}, []) - | Some _, (_, keyExpr) :: _ -> - ( Exp.ident ~loc {loc; txt = Ldot (Lident "ReactDOM", "jsxsKeyed")}, - [(nolabel, keyExpr)] ) - | Some _, [] -> - (Exp.ident ~loc {loc; txt = Ldot (Lident "ReactDOM", "jsxs")}, []) - in - Exp.apply ~loc ~attrs jsxExpr - ([(nolabel, componentNameExpr); (nolabel, props)] @ key) - | _ -> - let children, nonChildrenProps = extractChildren ~loc callArguments in - let childrenExpr = transformChildrenIfList ~loc ~mapper children in - let createElementCall = - match children with - (* [@JSX] div(~children=[a]), coming from
a
*) - | { - pexp_desc = - ( Pexp_construct ({txt = Lident "::"}, Some {pexp_desc = Pexp_tuple _}) - | Pexp_construct ({txt = Lident "[]"}, None) ); - } -> - "createDOMElementVariadic" - (* [@JSX] div(~children= value), coming from
...(value)
*) - | _ -> - raise - (Invalid_argument - "A spread as a DOM element's children don't make sense written \ - together. You can simply remove the spread.") - in - let args = - match nonChildrenProps with - | [_justTheUnitArgumentAtEnd] -> - [ - (* "div" *) - (nolabel, componentNameExpr); - (* [|moreCreateElementCallsHere|] *) - (nolabel, childrenExpr); - ] - | nonEmptyProps -> - let propsCall = - Exp.apply ~loc - (Exp.ident ~loc {loc; txt = Ldot (Lident "ReactDOMRe", "domProps")}) - (nonEmptyProps - |> List.map (fun (label, expression) -> - (label, mapper.expr mapper expression))) - in - [ - (* "div" *) - (nolabel, componentNameExpr); - (* ReactDOMRe.domProps(~className=blabla, ~foo=bar, ()) *) - (labelled "props", propsCall); - (* [|moreCreateElementCallsHere|] *) - (nolabel, childrenExpr); - ] - in - Exp.apply - ~loc (* throw away the [@JSX] attribute and keep the others, if any *) - ~attrs - (* ReactDOMRe.createElement *) - (Exp.ident ~loc - {loc; txt = Ldot (Lident "ReactDOMRe", createElementCall)}) - args - [@@raises Invalid_argument] - -let rec recursivelyTransformNamedArgsForMake mapper expr args newtypes = - let expr = mapper.expr mapper expr in - match expr.pexp_desc with - (* TODO: make this show up with a loc. *) - | Pexp_fun (Labelled "key", _, _, _) | Pexp_fun (Optional "key", _, _, _) -> - raise - (Invalid_argument - "Key cannot be accessed inside of a component. Don't worry - you can \ - always key a component from its parent!") - | Pexp_fun (Labelled "ref", _, _, _) | Pexp_fun (Optional "ref", _, _, _) -> - raise - (Invalid_argument - "Ref cannot be passed as a normal prop. Please use `forwardRef` API \ - instead.") - | Pexp_fun (arg, default, pattern, expression) - when isOptional arg || isLabelled arg -> - let () = - match (isOptional arg, pattern, default) with - | true, {ppat_desc = Ppat_constraint (_, {ptyp_desc})}, None -> ( - match ptyp_desc with - | Ptyp_constr ({txt = Lident "option"}, [_]) -> () - | _ -> - let currentType = - match ptyp_desc with - | Ptyp_constr ({txt}, []) -> - String.concat "." (Longident.flatten txt) - | Ptyp_constr ({txt}, _innerTypeArgs) -> - String.concat "." (Longident.flatten txt) ^ "(...)" - | _ -> "..." - in - Location.prerr_warning pattern.ppat_loc - (Preprocessor - (Printf.sprintf - "React: optional argument annotations must have explicit \ - `option`. Did you mean `option(%s)=?`?" - currentType))) - | _ -> () - in - let alias = - match pattern with - | {ppat_desc = Ppat_alias (_, {txt}) | Ppat_var {txt}} -> txt - | {ppat_desc = Ppat_any} -> "_" - | _ -> getLabel arg - in - let type_ = - match pattern with - | {ppat_desc = Ppat_constraint (_, type_)} -> Some type_ - | _ -> None - in - - recursivelyTransformNamedArgsForMake mapper expression - ((arg, default, pattern, alias, pattern.ppat_loc, type_) :: args) - newtypes - | Pexp_fun - ( Nolabel, - _, - {ppat_desc = Ppat_construct ({txt = Lident "()"}, _) | Ppat_any}, - _expression ) -> - (args, newtypes, None) - | Pexp_fun - ( Nolabel, - _, - { - ppat_desc = - Ppat_var {txt} | Ppat_constraint ({ppat_desc = Ppat_var {txt}}, _); - }, - _expression ) -> - (args, newtypes, Some txt) - | Pexp_fun (Nolabel, _, pattern, _expression) -> - Location.raise_errorf ~loc:pattern.ppat_loc - "React: react.component refs only support plain arguments and type \ - annotations." - | Pexp_newtype (label, expression) -> - recursivelyTransformNamedArgsForMake mapper expression args - (label :: newtypes) - | Pexp_constraint (expression, _typ) -> - recursivelyTransformNamedArgsForMake mapper expression args newtypes - | _ -> (args, newtypes, None) - [@@raises Invalid_argument] - -let argToType types (name, default, _noLabelName, _alias, loc, type_) = - match (type_, name, default) with - | Some {ptyp_desc = Ptyp_constr ({txt = Lident "option"}, [type_])}, name, _ - when isOptional name -> - ( true, - getLabel name, - [], - { - type_ with - ptyp_desc = - Ptyp_constr ({loc = type_.ptyp_loc; txt = optionIdent}, [type_]); - } ) - :: types - | Some type_, name, Some _default -> - ( false, - getLabel name, - [], - { - ptyp_desc = Ptyp_constr ({loc; txt = optionIdent}, [type_]); - ptyp_loc = loc; - ptyp_attributes = []; - } ) - :: types - | Some type_, name, _ -> (false, getLabel name, [], type_) :: types - | None, name, _ when isOptional name -> - ( true, - getLabel name, - [], - { - ptyp_desc = - Ptyp_constr - ( {loc; txt = optionIdent}, - [ - { - ptyp_desc = Ptyp_var (safeTypeFromValue name); - ptyp_loc = loc; - ptyp_attributes = []; - }; - ] ); - ptyp_loc = loc; - ptyp_attributes = []; - } ) - :: types - | None, name, _ when isLabelled name -> - ( false, - getLabel name, - [], - { - ptyp_desc = Ptyp_var (safeTypeFromValue name); - ptyp_loc = loc; - ptyp_attributes = []; - } ) - :: types - | _ -> types - [@@raises Invalid_argument] - -let argWithDefaultValue (name, default, _, _, _, _) = - match default with - | Some default when isOptional name -> Some (getLabel name, default) - | _ -> None - [@@raises Invalid_argument] - -let argToConcreteType types (name, _loc, type_) = - match name with - | name when isLabelled name -> (false, getLabel name, [], type_) :: types - | name when isOptional name -> (true, getLabel name, [], type_) :: types - | _ -> types - -let transformComponentDefinition nestedModules mapper structure returnStructures - = - match structure with - (* external *) - | { - pstr_loc; - pstr_desc = - Pstr_primitive ({pval_attributes; pval_type} as value_description); - } as pstr -> ( - match List.filter hasAttr pval_attributes with - | [] -> structure :: returnStructures - | [_] -> - let rec getPropTypes types ({ptyp_loc; ptyp_desc} as fullType) = - match ptyp_desc with - | Ptyp_arrow (name, type_, ({ptyp_desc = Ptyp_arrow _} as rest)) - when isLabelled name || isOptional name -> - getPropTypes ((name, ptyp_loc, type_) :: types) rest - | Ptyp_arrow (Nolabel, _type, rest) -> getPropTypes types rest - | Ptyp_arrow (name, type_, returnValue) - when isLabelled name || isOptional name -> - (returnValue, (name, returnValue.ptyp_loc, type_) :: types) - | _ -> (fullType, types) - in - let innerType, propTypes = getPropTypes [] pval_type in - let namedTypeList = List.fold_left argToConcreteType [] propTypes in - let retPropsType = - Typ.constr ~loc:pstr_loc - (Location.mkloc (Lident "props") pstr_loc) - (makePropsTypeParams namedTypeList) - in - (* type props<'id, 'name> = { @optional key: string, @optional id: 'id, ... } *) - let propsRecordType = - makePropsRecordType "props" Location.none - ((true, "key", [], keyType pstr_loc) :: namedTypeList) - in - (* can't be an arrow because it will defensively uncurry *) - let newExternalType = - Ptyp_constr - ( {loc = pstr_loc; txt = Ldot (Lident "React", "componentLike")}, - [retPropsType; innerType] ) - in - let newStructure = - { - pstr with - pstr_desc = - Pstr_primitive - { - value_description with - pval_type = {pval_type with ptyp_desc = newExternalType}; - pval_attributes = List.filter otherAttrsPure pval_attributes; - }; - } - in - propsRecordType :: newStructure :: returnStructures - | _ -> - raise - (Invalid_argument - "Only one react.component call can exist on a component at one time") - ) - (* let component = ... *) - | {pstr_loc; pstr_desc = Pstr_value (recFlag, valueBindings)} -> - let fileName = filenameFromLoc pstr_loc in - let emptyLoc = Location.in_file fileName in - let mapBinding binding = - if hasAttrOnBinding binding then - let bindingLoc = binding.pvb_loc in - let bindingPatLoc = binding.pvb_pat.ppat_loc in - let binding = - { - binding with - pvb_pat = {binding.pvb_pat with ppat_loc = emptyLoc}; - pvb_loc = emptyLoc; - } - in - let fnName = getFnName binding.pvb_pat in - let internalFnName = fnName ^ "$Internal" in - let fullModuleName = makeModuleName fileName !nestedModules fnName in - let modifiedBindingOld binding = - let expression = binding.pvb_expr in - (* TODO: there is a long-tail of unsupported features inside of blocks - Pexp_letmodule , Pexp_letexception , Pexp_ifthenelse *) - let rec spelunkForFunExpression expression = - match expression with - (* let make = (~prop) => ... *) - | {pexp_desc = Pexp_fun _} | {pexp_desc = Pexp_newtype _} -> - expression - (* let make = {let foo = bar in (~prop) => ...} *) - | {pexp_desc = Pexp_let (_recursive, _vbs, returnExpression)} -> - (* here's where we spelunk! *) - spelunkForFunExpression returnExpression - (* let make = React.forwardRef((~prop) => ...) *) - | { - pexp_desc = - Pexp_apply - (_wrapperExpression, [(Nolabel, innerFunctionExpression)]); - } -> - spelunkForFunExpression innerFunctionExpression - | { - pexp_desc = - Pexp_sequence (_wrapperExpression, innerFunctionExpression); - } -> - spelunkForFunExpression innerFunctionExpression - | {pexp_desc = Pexp_constraint (innerFunctionExpression, _typ)} -> - spelunkForFunExpression innerFunctionExpression - | _ -> - raise - (Invalid_argument - "react.component calls can only be on function definitions \ - or component wrappers (forwardRef, memo).") - [@@raises Invalid_argument] - in - spelunkForFunExpression expression - in - let modifiedBinding binding = - let hasApplication = ref false in - let wrapExpressionWithBinding expressionFn expression = - Vb.mk ~loc:bindingLoc - ~attrs:(List.filter otherAttrsPure binding.pvb_attributes) - (Pat.var ~loc:bindingPatLoc {loc = bindingPatLoc; txt = fnName}) - (expressionFn expression) - in - let expression = binding.pvb_expr in - let unerasableIgnoreExp exp = - { - exp with - pexp_attributes = unerasableIgnore emptyLoc :: exp.pexp_attributes; - } - in - (* TODO: there is a long-tail of unsupported features inside of blocks - Pexp_letmodule , Pexp_letexception , Pexp_ifthenelse *) - let rec spelunkForFunExpression expression = - match expression with - (* let make = (~prop) => ... with no final unit *) - | { - pexp_desc = - Pexp_fun - ( ((Labelled _ | Optional _) as label), - default, - pattern, - ({pexp_desc = Pexp_fun _} as internalExpression) ); - } -> - let wrap, hasUnit, hasForwardRef, exp = - spelunkForFunExpression internalExpression - in - ( wrap, - hasUnit, - hasForwardRef, - unerasableIgnoreExp - { - expression with - pexp_desc = Pexp_fun (label, default, pattern, exp); - } ) - (* let make = (()) => ... *) - (* let make = (_) => ... *) - | { - pexp_desc = - Pexp_fun - ( Nolabel, - _default, - { - ppat_desc = - Ppat_construct ({txt = Lident "()"}, _) | Ppat_any; - }, - _internalExpression ); - } -> - ((fun a -> a), true, false, expression) - (* let make = (~prop) => ... *) - | { - pexp_desc = - Pexp_fun - ( (Labelled _ | Optional _), - _default, - _pattern, - _internalExpression ); - } -> - ((fun a -> a), false, false, unerasableIgnoreExp expression) - (* let make = (prop) => ... *) - | { - pexp_desc = - Pexp_fun (_nolabel, _default, pattern, _internalExpression); - } -> - if !hasApplication then - ((fun a -> a), false, false, unerasableIgnoreExp expression) - else - Location.raise_errorf ~loc:pattern.ppat_loc - "React: props need to be labelled arguments.\n\ - \ If you are working with refs be sure to wrap with \ - React.forwardRef.\n\ - \ If your component doesn't have any props use () or _ \ - instead of a name." - (* let make = {let foo = bar in (~prop) => ...} *) - | {pexp_desc = Pexp_let (recursive, vbs, internalExpression)} -> - (* here's where we spelunk! *) - let wrap, hasUnit, hasForwardRef, exp = - spelunkForFunExpression internalExpression - in - ( wrap, - hasUnit, - hasForwardRef, - {expression with pexp_desc = Pexp_let (recursive, vbs, exp)} ) - (* let make = React.forwardRef((~prop) => ...) *) - | { - pexp_desc = - Pexp_apply (wrapperExpression, [(Nolabel, internalExpression)]); - } -> - let () = hasApplication := true in - let _, hasUnit, _, exp = - spelunkForFunExpression internalExpression - in - let hasForwardRef = isForwardRef wrapperExpression in - ( (fun exp -> Exp.apply wrapperExpression [(nolabel, exp)]), - hasUnit, - hasForwardRef, - exp ) - | { - pexp_desc = Pexp_sequence (wrapperExpression, internalExpression); - } -> - let wrap, hasUnit, hasForwardRef, exp = - spelunkForFunExpression internalExpression - in - ( wrap, - hasUnit, - hasForwardRef, - { - expression with - pexp_desc = Pexp_sequence (wrapperExpression, exp); - } ) - | e -> ((fun a -> a), false, false, e) - in - let wrapExpression, hasUnit, hasForwardRef, expression = - spelunkForFunExpression expression - in - ( wrapExpressionWithBinding wrapExpression, - hasUnit, - hasForwardRef, - expression ) - in - let bindingWrapper, _hasUnit, hasForwardRef, expression = - modifiedBinding binding - in - (* do stuff here! *) - let namedArgList, _newtypes, _forwardRef = - recursivelyTransformNamedArgsForMake mapper - (modifiedBindingOld binding) - [] [] - in - let namedTypeList = List.fold_left argToType [] namedArgList in - (* let _ = ref *) - let vbIgnoreUnusedRef = - Vb.mk (Pat.any ()) (Exp.ident (Location.mknoloc (Lident "ref"))) - in - (* let ref = ref->Js.Nullable.fromOption *) - let vbRefFromOption = - Vb.mk - (Pat.var @@ Location.mknoloc "ref") - (Exp.apply - (Exp.ident - (Location.mknoloc - (Ldot (Ldot (Lident "Js", "Nullable"), "fromOption")))) - [(Nolabel, Exp.ident (Location.mknoloc @@ Lident "ref"))]) - in - let namedArgWithDefaultValueList = - List.filter_map argWithDefaultValue namedArgList - in - let vbMatch (label, default) = - Vb.mk - (Pat.var (Location.mknoloc label)) - (Exp.match_ - (Exp.ident {txt = Lident label; loc = Location.none}) - [ - Exp.case - (Pat.construct - (Location.mknoloc @@ Lident "Some") - (Some (Pat.var (Location.mknoloc label)))) - (Exp.ident (Location.mknoloc @@ Lident label)); - Exp.case - (Pat.construct (Location.mknoloc @@ Lident "None") None) - default; - ]) - in - let vbMatchList = List.map vbMatch namedArgWithDefaultValueList in - (* type props = { ... } *) - let propsRecordType = - makePropsRecordType "props" emptyLoc - (((true, "key", [], keyType emptyLoc) :: namedTypeList) - @ - if hasForwardRef then [(true, "ref", [], refType Location.none)] - else []) - in - let innerExpression = - if hasForwardRef then - Exp.apply - (Exp.ident @@ Location.mknoloc @@ Lident "make") - [ - ( Nolabel, - Exp.record - [ - ( Location.mknoloc @@ Lident "ref", - Exp.apply ~attrs:optionalAttr - (Exp.ident - (Location.mknoloc - (Ldot - (Ldot (Lident "Js", "Nullable"), "toOption")))) - [ - ( Nolabel, - Exp.ident (Location.mknoloc @@ Lident "ref") ); - ] ); - ] - (Some (Exp.ident (Location.mknoloc @@ Lident "props"))) ); - ] - else - Exp.apply - (Exp.ident (Location.mknoloc @@ Lident "make")) - [(Nolabel, Exp.ident (Location.mknoloc @@ Lident "props"))] - in - let fullExpression = - (* React component name should start with uppercase letter *) - (* let make = { let \"App" = props => make(props); \"App" } *) - (* let make = React.forwardRef({ - let \"App" = (props, ref) => make({...props, ref: @optional (Js.Nullabel.toOption(ref))}) - })*) - Exp.fun_ nolabel None - (match namedTypeList with - | [] -> Pat.var @@ Location.mknoloc "props" - | _ -> - Pat.constraint_ - (Pat.var @@ Location.mknoloc "props") - (Typ.constr (Location.mknoloc @@ Lident "props") [Typ.any ()])) - (if hasForwardRef then - Exp.fun_ nolabel None - (Pat.var @@ Location.mknoloc "ref") - innerExpression - else innerExpression) - in - let fullExpression = - match fullModuleName with - | "" -> fullExpression - | txt -> - Exp.let_ Nonrecursive - [ - Vb.mk ~loc:emptyLoc - (Pat.var ~loc:emptyLoc {loc = emptyLoc; txt}) - fullExpression; - ] - (Exp.ident ~loc:emptyLoc {loc = emptyLoc; txt = Lident txt}) - in - let rec returnedExpression patterns ({pexp_desc} as expr) = - match pexp_desc with - | Pexp_fun - ( _arg_label, - _default, - {ppat_desc = Ppat_construct ({txt = Lident "()"}, _) | Ppat_any}, - expr ) -> - (patterns, expr) - | Pexp_fun (arg_label, _default, {ppat_loc; ppat_desc}, expr) -> ( - if isLabelled arg_label || isOptional arg_label then - returnedExpression - (( {loc = ppat_loc; txt = Lident (getLabel arg_label)}, - Pat.var - ~attrs:(if isOptional arg_label then optionalAttr else []) - {txt = getLabel arg_label; loc = ppat_loc} ) - :: patterns) - expr - else - (* Special case of nolabel arg "ref" in forwardRef fn *) - (* let make = React.forwardRef(ref => body) *) - match ppat_desc with - | Ppat_var {txt} - | Ppat_constraint ({ppat_desc = Ppat_var {txt}}, _) - when txt = "ref" -> - returnedExpression - (( {loc = ppat_loc; txt = Lident txt}, - Pat.var ~attrs:optionalAttr {txt; loc = ppat_loc} ) - :: patterns) - expr - | _ -> returnedExpression patterns expr) - | _ -> (patterns, expr) - in - let patternsWithLid, expression = returnedExpression [] expression in - let pattern = - match patternsWithLid with - | [] -> Pat.any () - | _ -> Pat.record (List.rev patternsWithLid) Open - in - (* add patttern matching for optional prop value *) - let expression = - if List.length vbMatchList = 0 then expression - else Exp.let_ Nonrecursive vbMatchList expression - in - (* add let _ = ref to ignore unused warning *) - let expression = - match hasForwardRef with - | true -> - let expression = - Exp.let_ Nonrecursive [vbIgnoreUnusedRef] expression - in - Exp.let_ Nonrecursive [vbRefFromOption] expression - | false -> expression - in - let expression = - Exp.fun_ Nolabel None - (Pat.constraint_ pattern - (Typ.constr ~loc:emptyLoc - {txt = Lident "props"; loc = emptyLoc} - (makePropsTypeParams namedTypeList))) - expression - in - (* let make = ({id, name, ...}: props<'id, 'name, ...>) => { ... } *) - let bindings, newBinding = - match recFlag with - | Recursive -> - ( [ - bindingWrapper - (Exp.let_ ~loc:emptyLoc Recursive - [ - makeNewBinding binding expression internalFnName; - Vb.mk - (Pat.var {loc = emptyLoc; txt = fnName}) - fullExpression; - ] - (Exp.ident {loc = emptyLoc; txt = Lident fnName})); - ], - None ) - | Nonrecursive -> - ( [{binding with pvb_expr = expression; pvb_attributes = []}], - Some (bindingWrapper fullExpression) ) - in - (Some propsRecordType, bindings, newBinding) - else (None, [binding], None) - [@@raises Invalid_argument] - in - (* END of mapBinding fn *) - let structuresAndBinding = List.map mapBinding valueBindings in - let otherStructures (type_, binding, newBinding) - (types, bindings, newBindings) = - let types = - match type_ with - | Some type_ -> type_ :: types - | None -> types - in - let newBindings = - match newBinding with - | Some newBinding -> newBinding :: newBindings - | None -> newBindings - in - (types, binding @ bindings, newBindings) - in - let types, bindings, newBindings = - List.fold_right otherStructures structuresAndBinding ([], [], []) - in - types - @ [{pstr_loc; pstr_desc = Pstr_value (recFlag, bindings)}] - @ (match newBindings with - | [] -> [] - | newBindings -> - [{pstr_loc = emptyLoc; pstr_desc = Pstr_value (recFlag, newBindings)}]) - @ returnStructures - | structure -> structure :: returnStructures - [@@raises Invalid_argument] - -let reactComponentTransform nestedModules mapper structures = - List.fold_right - (transformComponentDefinition nestedModules mapper) - structures [] - [@@raises Invalid_argument] - -let transformComponentSignature _mapper signature returnSignatures = - match signature with - | { - psig_loc; - psig_desc = Psig_value ({pval_attributes; pval_type} as psig_desc); - } as psig -> ( - match List.filter hasAttr pval_attributes with - | [] -> signature :: returnSignatures - | [_] -> - let rec getPropTypes types ({ptyp_loc; ptyp_desc} as fullType) = - match ptyp_desc with - | Ptyp_arrow (name, type_, ({ptyp_desc = Ptyp_arrow _} as rest)) - when isOptional name || isLabelled name -> - getPropTypes ((name, ptyp_loc, type_) :: types) rest - | Ptyp_arrow (Nolabel, _type, rest) -> getPropTypes types rest - | Ptyp_arrow (name, type_, returnValue) - when isOptional name || isLabelled name -> - (returnValue, (name, returnValue.ptyp_loc, type_) :: types) - | _ -> (fullType, types) - in - let innerType, propTypes = getPropTypes [] pval_type in - let namedTypeList = List.fold_left argToConcreteType [] propTypes in - let retPropsType = - Typ.constr - (Location.mkloc (Lident "props") psig_loc) - (makePropsTypeParamsSig namedTypeList) - in - let propsRecordType = - makePropsRecordTypeSig "props" Location.none - ((true, "key", [], keyType Location.none) :: namedTypeList) - in - (* can't be an arrow because it will defensively uncurry *) - let newExternalType = - Ptyp_constr - ( {loc = psig_loc; txt = Ldot (Lident "React", "componentLike")}, - [retPropsType; innerType] ) - in - let newStructure = - { - psig with - psig_desc = - Psig_value - { - psig_desc with - pval_type = {pval_type with ptyp_desc = newExternalType}; - pval_attributes = List.filter otherAttrsPure pval_attributes; - }; - } - in - propsRecordType :: newStructure :: returnSignatures - | _ -> - raise - (Invalid_argument - "Only one react.component call can exist on a component at one time") - ) - | signature -> signature :: returnSignatures - [@@raises Invalid_argument] - -let reactComponentSignatureTransform mapper signatures = - List.fold_right (transformComponentSignature mapper) signatures [] - [@@raises Invalid_argument] - -let transformJsxCall ~config mapper callExpression callArguments attrs = - match callExpression.pexp_desc with - | Pexp_ident caller -> ( - match caller with - | {txt = Lident "createElement"} -> - raise - (Invalid_argument - "JSX: `createElement` should be preceeded by a module name.") - (* Foo.createElement(~prop1=foo, ~prop2=bar, ~children=[], ()) *) - | {loc; txt = Ldot (modulePath, ("createElement" | "make"))} -> - transformUppercaseCall3 ~config modulePath mapper loc attrs callExpression - callArguments - (* div(~prop1=foo, ~prop2=bar, ~children=[bla], ()) *) - (* turn that into - ReactDOMRe.createElement(~props=ReactDOMRe.props(~props1=foo, ~props2=bar, ()), [|bla|]) *) - | {loc; txt = Lident id} -> - transformLowercaseCall3 ~config mapper loc attrs callExpression - callArguments id - | {txt = Ldot (_, anythingNotCreateElementOrMake)} -> - raise - (Invalid_argument - ("JSX: the JSX attribute should be attached to a \ - `YourModuleName.createElement` or `YourModuleName.make` call. We \ - saw `" ^ anythingNotCreateElementOrMake ^ "` instead")) - | {txt = Lapply _} -> - (* don't think there's ever a case where this is reached *) - raise - (Invalid_argument - "JSX: encountered a weird case while processing the code. Please \ - report this!")) - | _ -> - raise - (Invalid_argument - "JSX: `createElement` should be preceeded by a simple, direct module \ - name.") - [@@raises Invalid_argument] - -let signatureV4 mapper signature = - default_mapper.signature mapper - @@ reactComponentSignatureTransform mapper signature - [@@raises Invalid_argument] - -let structureV4 nestedModules mapper items = - match items with - | items -> - default_mapper.structure mapper - @@ reactComponentTransform nestedModules mapper items - [@@raises Invalid_argument] - -let exprV4 ~config mapper expression = - match expression with - (* Does the function application have the @JSX attribute? *) - | {pexp_desc = Pexp_apply (callExpression, callArguments); pexp_attributes} - -> ( - let jsxAttribute, nonJSXAttributes = - List.partition - (fun (attribute, _) -> attribute.txt = "JSX") - pexp_attributes - in - match (jsxAttribute, nonJSXAttributes) with - (* no JSX attribute *) - | [], _ -> default_mapper.expr mapper expression - | _, nonJSXAttributes -> - transformJsxCall ~config mapper callExpression callArguments - nonJSXAttributes) - (* is it a list with jsx attribute? Reason <>foo desugars to [@JSX][foo]*) - | { - pexp_desc = - ( Pexp_construct - ({txt = Lident "::"; loc}, Some {pexp_desc = Pexp_tuple _}) - | Pexp_construct ({txt = Lident "[]"; loc}, None) ); - pexp_attributes; - } as listItems -> ( - let jsxAttribute, nonJSXAttributes = - List.partition - (fun (attribute, _) -> attribute.txt = "JSX") - pexp_attributes - in - match (jsxAttribute, nonJSXAttributes) with - (* no JSX attribute *) - | [], _ -> default_mapper.expr mapper expression - | _, nonJSXAttributes -> - let loc = {loc with loc_ghost = true} in - let fragment = - match config.mode with - | "automatic" -> - Exp.ident ~loc {loc; txt = Ldot (Lident "React", "jsxFragment")} - | "classic" | _ -> - Exp.ident ~loc {loc; txt = Ldot (Lident "ReasonReact", "fragment")} - in - let childrenExpr = transformChildrenIfList ~loc ~mapper listItems in - let args = - [ - (nolabel, fragment); - (match config.mode with - | "automatic" -> - ( nolabel, - Exp.record - [ - ( Location.mknoloc @@ Lident "children", - match childrenExpr with - | {pexp_desc = Pexp_array children} -> ( - match children with - | [] -> recordWithOnlyKey ~loc:Location.none - | [child] -> child - | _ -> childrenExpr) - | _ -> childrenExpr ); - ] - None ) - | "classic" | _ -> (nolabel, childrenExpr)); - ] - in - let countOfChildren = function - | {pexp_desc = Pexp_array children} -> List.length children - | _ -> 0 - in - Exp.apply - ~loc (* throw away the [@JSX] attribute and keep the others, if any *) - ~attrs:nonJSXAttributes - (* ReactDOMRe.createElement *) - (match config.mode with - | "automatic" -> - if countOfChildren childrenExpr > 1 then - Exp.ident ~loc {loc; txt = Ldot (Lident "React", "jsxs")} - else Exp.ident ~loc {loc; txt = Ldot (Lident "React", "jsx")} - | "classic" | _ -> - Exp.ident ~loc - {loc; txt = Ldot (Lident "ReactDOMRe", "createElement")}) - args) - (* Delegate to the default mapper, a deep identity traversal *) - | e -> default_mapper.expr mapper e - [@@raises Invalid_argument] - -let module_bindingV4 nestedModules mapper module_binding = - let _ = nestedModules := module_binding.pmb_name.txt :: !nestedModules in - let mapped = default_mapper.module_binding mapper module_binding in - let _ = nestedModules := List.tl !nestedModules in - mapped - [@@raises Failure] - -(* TODO: some line number might still be wrong *) -let jsxMapper ~config nestedModules = - let structure_item mapper item = - (match item.pstr_desc with - | Pstr_attribute attr -> processConfigAttribute attr config - | _ -> ()); - default_mapper.structure_item mapper item - in - let signature_item mapper item = - (match item.psig_desc with - | Psig_attribute attr -> processConfigAttribute attr config - | _ -> ()); - default_mapper.signature_item mapper item - in - - let structure = structureV4 nestedModules in - let module_binding = module_bindingV4 nestedModules in - let expr = exprV4 ~config in - { - default_mapper with - expr; - module_binding; - signature; - signature_item; - structure; - structure_item; - } - [@@raises Invalid_argument, Failure] - -let rewrite_implementation ~jsxMode (code : Parsetree.structure) : - Parsetree.structure = - let nestedModules = ref [] in - let config = {mode = jsxMode; module_ = ""; version = 4} in - let mapper = jsxMapper ~config nestedModules in - mapper.structure mapper code - [@@raises Invalid_argument, Failure] - -let rewrite_signature ~jsxMode (code : Parsetree.signature) : - Parsetree.signature = - let nestedModules = ref [] in - let config = {mode = jsxMode; module_ = ""; version = 4} in - let mapper = jsxMapper ~config nestedModules in - mapper.signature mapper code - [@@raises Invalid_argument, Failure] diff --git a/cli/reactjs_jsx_ppx_v4.mli b/cli/reactjs_jsx_ppx_v4.mli deleted file mode 100644 index 79b97297..00000000 --- a/cli/reactjs_jsx_ppx_v4.mli +++ /dev/null @@ -1,69 +0,0 @@ -(* - This is the module that handles turning Reason JSX' agnostic function call into - a ReasonReact-specific function call. Aka, this is a macro, using OCaml's ppx - facilities; https://whitequark.org/blog/2014/04/16/a-guide-to-extension- - points-in-ocaml/ - You wouldn't use this file directly; it's used by ReScript's - bsconfig.json. Specifically, there's a field called `react-jsx` inside the - field `reason`, which enables this ppx through some internal call in bsb -*) - -(* - There are two different transforms that can be selected in this file (v2 and v3): - v2: - transform `[@JSX] div(~props1=a, ~props2=b, ~children=[foo, bar], ())` into - `ReactDOMRe.createElement("div", ~props={"props1": 1, "props2": b}, [|foo, - bar|])`. - transform `[@JSX] div(~props1=a, ~props2=b, ~children=foo, ())` into - `ReactDOMRe.createElementVariadic("div", ~props={"props1": 1, "props2": b}, foo)`. - transform the upper-cased case - `[@JSX] Foo.createElement(~key=a, ~ref=b, ~foo=bar, ~children=[], ())` into - `ReasonReact.element(~key=a, ~ref=b, Foo.make(~foo=bar, [||]))` - transform `[@JSX] [foo]` into - `ReactDOMRe.createElement(ReasonReact.fragment, [|foo|])` - v3: - transform `[@JSX] div(~props1=a, ~props2=b, ~children=[foo, bar], ())` into - `ReactDOMRe.createDOMElementVariadic("div", ReactDOMRe.domProps(~props1=1, ~props2=b), [|foo, bar|])`. - transform the upper-cased case - `[@JSX] Foo.createElement(~key=a, ~ref=b, ~foo=bar, ~children=[], ())` into - `React.createElement(Foo.make, Foo.makeProps(~key=a, ~ref=b, ~foo=bar, ()))` - transform the upper-cased case - `[@JSX] Foo.createElement(~foo=bar, ~children=[foo, bar], ())` into - `React.createElementVariadic(Foo.make, Foo.makeProps(~foo=bar, ~children=React.null, ()), [|foo, bar|])` - transform `[@JSX] [foo]` into - `ReactDOMRe.createElement(ReasonReact.fragment, [|foo|])` - - v4: - transform `[@JSX] div(~props1=a, ~props2=b, ~spreadProps=props3 ~children=[foo, bar], ())` into - `ReactDOMRe.createDOMElementVariadic("div", ~props=ReactDOMRe.domProps(~props1=1, ~props2=b), [|foo, bar|])`. - transform the upper-cased case - `[@JSX] Foo.createElement(~key=a, ~ref=b, ~foo=bar, ~spreadProps=baz ~children=[], ())` into - `React.createElement(Foo.make, {...baz, key: a, ref: b, foo: bar})` - transform the upper-cased case - `[@JSX] Foo.createElement(~foo=bar, ~spreadProps=baz, ~children=[foo, bar], ())` into - `React.createElement(Foo.make, {...baz, foo: bar, children: React.null}), [|foo, bar|])` - transform `[@JSX] [foo]` into - `ReactDOMRe.createElement(ReasonReact.fragment, [|foo|])` -*) - -(* - New JSX transform with React v17 - - if has key - `jsxKeyed("div", { ... }, "key") or jsxsKeyed("div", { ... }, "key")` - - upper case - child X -> `jsx(Foo.make, { ... })` - child -> `jsx(Foo.make, { ... , children: ... })` - children O -> `jsxs(Foo.make, { ..., children: [ ... ]})` - - lower case - child X -> `jsx("div", { ... })` - child O -> `jsx("div", { ..., children: ... })` - children O -> `jsxs("div", { ..., children: [ ... ]})` -*) -val rewrite_implementation : - jsxMode:string -> Parsetree.structure -> Parsetree.structure - -val rewrite_signature : - jsxMode:string -> Parsetree.signature -> Parsetree.signature From cecb1470576bbc7324c51b53d57f5fe9964a3d38 Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Thu, 7 Jul 2022 16:35:45 +0200 Subject: [PATCH 33/94] Move V3 inside. --- cli/reactjs_jsx_ppx.ml | 1314 +++++++++++++++++++++++++++++++++++- cli/reactjs_jsx_ppx_v3.ml | 1277 ----------------------------------- cli/reactjs_jsx_ppx_v3.mli | 39 -- 3 files changed, 1312 insertions(+), 1318 deletions(-) delete mode 100644 cli/reactjs_jsx_ppx_v3.ml delete mode 100644 cli/reactjs_jsx_ppx_v3.mli diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index 783da5c2..5badb66f 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -1,5 +1,1315 @@ open Parsetree +module V3 = struct + open Ast_helper + open Ast_mapper + open Asttypes + open Parsetree + open Longident + + let rec find_opt p = function + | [] -> None + | x :: l -> if p x then Some x else find_opt p l + + let nolabel = Nolabel + + let labelled str = Labelled str + + let optional str = Optional str + + let isOptional str = + match str with + | Optional _ -> true + | _ -> false + + let isLabelled str = + match str with + | Labelled _ -> true + | _ -> false + + let getLabel str = + match str with + | Optional str | Labelled str -> str + | Nolabel -> "" + + let optionIdent = Lident "option" + + let constantString ~loc str = + Ast_helper.Exp.constant ~loc (Pconst_string (str, None)) + + let safeTypeFromValue valueStr = + let valueStr = getLabel valueStr in + match String.sub valueStr 0 1 with + | "_" -> "T" ^ valueStr + | _ -> valueStr + [@@raises Invalid_argument] + + let keyType loc = + Typ.constr ~loc {loc; txt = optionIdent} + [Typ.constr ~loc {loc; txt = Lident "string"} []] + + type 'a children = ListLiteral of 'a | Exact of 'a + + type componentConfig = {propsName: string} + + (* if children is a list, convert it to an array while mapping each element. If not, just map over it, as usual *) + let transformChildrenIfListUpper ~loc ~mapper theList = + let rec transformChildren_ theList accum = + (* not in the sense of converting a list to an array; convert the AST + reprensentation of a list to the AST reprensentation of an array *) + match theList with + | {pexp_desc = Pexp_construct ({txt = Lident "[]"}, None)} -> ( + match accum with + | [singleElement] -> Exact singleElement + | accum -> ListLiteral (Exp.array ~loc (List.rev accum))) + | { + pexp_desc = + Pexp_construct + ({txt = Lident "::"}, Some {pexp_desc = Pexp_tuple [v; acc]}); + } -> + transformChildren_ acc (mapper.expr mapper v :: accum) + | notAList -> Exact (mapper.expr mapper notAList) + in + transformChildren_ theList [] + + let transformChildrenIfList ~loc ~mapper theList = + let rec transformChildren_ theList accum = + (* not in the sense of converting a list to an array; convert the AST + reprensentation of a list to the AST reprensentation of an array *) + match theList with + | {pexp_desc = Pexp_construct ({txt = Lident "[]"}, None)} -> + Exp.array ~loc (List.rev accum) + | { + pexp_desc = + Pexp_construct + ({txt = Lident "::"}, Some {pexp_desc = Pexp_tuple [v; acc]}); + } -> + transformChildren_ acc (mapper.expr mapper v :: accum) + | notAList -> mapper.expr mapper notAList + in + transformChildren_ theList [] + + let extractChildren ?(removeLastPositionUnit = false) ~loc propsAndChildren = + let rec allButLast_ lst acc = + match lst with + | [] -> [] + | [(Nolabel, {pexp_desc = Pexp_construct ({txt = Lident "()"}, None)})] -> + acc + | (Nolabel, _) :: _rest -> + raise + (Invalid_argument + "JSX: found non-labelled argument before the last position") + | arg :: rest -> allButLast_ rest (arg :: acc) + [@@raises Invalid_argument] + in + let allButLast lst = + allButLast_ lst [] |> List.rev + [@@raises Invalid_argument] + in + match + List.partition + (fun (label, _) -> label = labelled "children") + propsAndChildren + with + | [], props -> + (* no children provided? Place a placeholder list *) + ( Exp.construct ~loc {loc; txt = Lident "[]"} None, + if removeLastPositionUnit then allButLast props else props ) + | [(_, childrenExpr)], props -> + (childrenExpr, if removeLastPositionUnit then allButLast props else props) + | _ -> + raise + (Invalid_argument "JSX: somehow there's more than one `children` label") + [@@raises Invalid_argument] + + let unerasableIgnore loc = + ( {loc; txt = "warning"}, + PStr [Str.eval (Exp.constant (Pconst_string ("-16", None)))] ) + + let merlinFocus = ({loc = Location.none; txt = "merlin.focus"}, PStr []) + + (* Helper method to look up the [@react.component] attribute *) + let hasAttr (loc, _) = loc.txt = "react.component" + + (* Helper method to filter out any attribute that isn't [@react.component] *) + let otherAttrsPure (loc, _) = loc.txt <> "react.component" + + (* Iterate over the attributes and try to find the [@react.component] attribute *) + let hasAttrOnBinding {pvb_attributes} = + find_opt hasAttr pvb_attributes <> None + + (* Finds the name of the variable the binding is assigned to, otherwise raises Invalid_argument *) + let rec getFnName binding = + match binding with + | {ppat_desc = Ppat_var {txt}} -> txt + | {ppat_desc = Ppat_constraint (pat, _)} -> getFnName pat + | _ -> + raise (Invalid_argument "react.component calls cannot be destructured.") + [@@raises Invalid_argument] + + let makeNewBinding binding expression newName = + match binding with + | {pvb_pat = {ppat_desc = Ppat_var ppat_var} as pvb_pat} -> + { + binding with + pvb_pat = + {pvb_pat with ppat_desc = Ppat_var {ppat_var with txt = newName}}; + pvb_expr = expression; + pvb_attributes = [merlinFocus]; + } + | _ -> + raise (Invalid_argument "react.component calls cannot be destructured.") + [@@raises Invalid_argument] + + (* Lookup the value of `props` otherwise raise Invalid_argument error *) + let getPropsNameValue _acc (loc, exp) = + match (loc, exp) with + | {txt = Lident "props"}, {pexp_desc = Pexp_ident {txt = Lident str}} -> + {propsName = str} + | {txt}, _ -> + raise + (Invalid_argument + ("react.component only accepts props as an option, given: " + ^ Longident.last txt)) + [@@raises Invalid_argument] + + (* Lookup the `props` record or string as part of [@react.component] and store the name for use when rewriting *) + let getPropsAttr payload = + let defaultProps = {propsName = "Props"} in + match payload with + | Some + (PStr + ({ + pstr_desc = + Pstr_eval ({pexp_desc = Pexp_record (recordFields, None)}, _); + } + :: _rest)) -> + List.fold_left getPropsNameValue defaultProps recordFields + | Some + (PStr + ({ + pstr_desc = + Pstr_eval ({pexp_desc = Pexp_ident {txt = Lident "props"}}, _); + } + :: _rest)) -> + {propsName = "props"} + | Some (PStr ({pstr_desc = Pstr_eval (_, _)} :: _rest)) -> + raise + (Invalid_argument + "react.component accepts a record config with props as an options.") + | _ -> defaultProps + [@@raises Invalid_argument] + + (* Plucks the label, loc, and type_ from an AST node *) + let pluckLabelDefaultLocType (label, default, _, _, loc, type_) = + (label, default, loc, type_) + + (* Lookup the filename from the location information on the AST node and turn it into a valid module identifier *) + let filenameFromLoc (pstr_loc : Location.t) = + let fileName = + match pstr_loc.loc_start.pos_fname with + | "" -> !Location.input_name + | fileName -> fileName + in + let fileName = + try Filename.chop_extension (Filename.basename fileName) + with Invalid_argument _ -> fileName + in + let fileName = String.capitalize_ascii fileName in + fileName + + (* Build a string representation of a module name with segments separated by $ *) + let makeModuleName fileName nestedModules fnName = + let fullModuleName = + match (fileName, nestedModules, fnName) with + (* TODO: is this even reachable? It seems like the fileName always exists *) + | "", nestedModules, "make" -> nestedModules + | "", nestedModules, fnName -> List.rev (fnName :: nestedModules) + | fileName, nestedModules, "make" -> fileName :: List.rev nestedModules + | fileName, nestedModules, fnName -> + fileName :: List.rev (fnName :: nestedModules) + in + let fullModuleName = String.concat "$" fullModuleName in + fullModuleName + + (* + AST node builders + These functions help us build AST nodes that are needed when transforming a [@react.component] into a + constructor and a props external +*) + + (* Build an AST node representing all named args for the `external` definition for a component's props *) + let rec recursivelyMakeNamedArgsForExternal list args = + match list with + | (label, default, loc, interiorType) :: tl -> + recursivelyMakeNamedArgsForExternal tl + (Typ.arrow ~loc label + (match (label, interiorType, default) with + (* ~foo=1 *) + | label, None, Some _ -> + { + ptyp_desc = Ptyp_var (safeTypeFromValue label); + ptyp_loc = loc; + ptyp_attributes = []; + } + (* ~foo: int=1 *) + | _label, Some type_, Some _ -> type_ + (* ~foo: option(int)=? *) + | ( label, + Some {ptyp_desc = Ptyp_constr ({txt = Lident "option"}, [type_])}, + _ ) + | ( label, + Some + { + ptyp_desc = + Ptyp_constr + ({txt = Ldot (Lident "*predef*", "option")}, [type_]); + }, + _ ) + (* ~foo: int=? - note this isnt valid. but we want to get a type error *) + | label, Some type_, _ + when isOptional label -> + type_ + (* ~foo=? *) + | label, None, _ when isOptional label -> + { + ptyp_desc = Ptyp_var (safeTypeFromValue label); + ptyp_loc = loc; + ptyp_attributes = []; + } + (* ~foo *) + | label, None, _ -> + { + ptyp_desc = Ptyp_var (safeTypeFromValue label); + ptyp_loc = loc; + ptyp_attributes = []; + } + | _label, Some type_, _ -> type_) + args) + | [] -> args + [@@raises Invalid_argument] + + (* Build an AST node for the [@bs.obj] representing props for a component *) + let makePropsValue fnName loc namedArgListWithKeyAndRef propsType = + let propsName = fnName ^ "Props" in + { + pval_name = {txt = propsName; loc}; + pval_type = + recursivelyMakeNamedArgsForExternal namedArgListWithKeyAndRef + (Typ.arrow nolabel + { + ptyp_desc = Ptyp_constr ({txt = Lident "unit"; loc}, []); + ptyp_loc = loc; + ptyp_attributes = []; + } + propsType); + pval_prim = [""]; + pval_attributes = [({txt = "bs.obj"; loc}, PStr [])]; + pval_loc = loc; + } + [@@raises Invalid_argument] + + (* Build an AST node representing an `external` with the definition of the [@bs.obj] *) + let makePropsExternal fnName loc namedArgListWithKeyAndRef propsType = + { + pstr_loc = loc; + pstr_desc = + Pstr_primitive + (makePropsValue fnName loc namedArgListWithKeyAndRef propsType); + } + [@@raises Invalid_argument] + + (* Build an AST node for the signature of the `external` definition *) + let makePropsExternalSig fnName loc namedArgListWithKeyAndRef propsType = + { + psig_loc = loc; + psig_desc = + Psig_value + (makePropsValue fnName loc namedArgListWithKeyAndRef propsType); + } + [@@raises Invalid_argument] + + (* Build an AST node for the props name when converted to an object inside the function signature *) + let makePropsName ~loc name = + { + ppat_desc = Ppat_var {txt = name; loc}; + ppat_loc = loc; + ppat_attributes = []; + } + + let makeObjectField loc (str, attrs, type_) = + Otag ({loc; txt = str}, attrs, type_) + + (* Build an AST node representing a "closed" object representing a component's props *) + let makePropsType ~loc namedTypeList = + Typ.mk ~loc + (Ptyp_object (List.map (makeObjectField loc) namedTypeList, Closed)) + + (* Builds an AST node for the entire `external` definition of props *) + let makeExternalDecl fnName loc namedArgListWithKeyAndRef namedTypeList = + makePropsExternal fnName loc + (List.map pluckLabelDefaultLocType namedArgListWithKeyAndRef) + (makePropsType ~loc namedTypeList) + [@@raises Invalid_argument] + + let newtypeToVar newtype type_ = + let var_desc = Ptyp_var ("type-" ^ newtype) in + let typ (mapper : Ast_mapper.mapper) typ = + match typ.ptyp_desc with + | Ptyp_constr ({txt = Lident name}, _) when name = newtype -> + {typ with ptyp_desc = var_desc} + | _ -> Ast_mapper.default_mapper.typ mapper typ + in + let mapper = {Ast_mapper.default_mapper with typ} in + mapper.typ mapper type_ + + (* TODO: some line number might still be wrong *) + let jsxMapper () = + let jsxVersion = ref None in + + let transformUppercaseCall3 modulePath mapper loc attrs _ callArguments = + let children, argsWithLabels = + extractChildren ~loc ~removeLastPositionUnit:true callArguments + in + let argsForMake = argsWithLabels in + let childrenExpr = transformChildrenIfListUpper ~loc ~mapper children in + let recursivelyTransformedArgsForMake = + argsForMake + |> List.map (fun (label, expression) -> + (label, mapper.expr mapper expression)) + in + let childrenArg = ref None in + let args = + recursivelyTransformedArgsForMake + @ (match childrenExpr with + | Exact children -> [(labelled "children", children)] + | ListLiteral {pexp_desc = Pexp_array list} when list = [] -> [] + | ListLiteral expression -> + (* this is a hack to support react components that introspect into their children *) + childrenArg := Some expression; + [ + ( labelled "children", + Exp.ident ~loc {loc; txt = Ldot (Lident "React", "null")} ); + ]) + @ [(nolabel, Exp.construct ~loc {loc; txt = Lident "()"} None)] + in + let isCap str = + let first = String.sub str 0 1 [@@raises Invalid_argument] in + let capped = String.uppercase_ascii first in + first = capped + [@@raises Invalid_argument] + in + let ident = + match modulePath with + | Lident _ -> Ldot (modulePath, "make") + | Ldot (_modulePath, value) as fullPath when isCap value -> + Ldot (fullPath, "make") + | modulePath -> modulePath + in + let propsIdent = + match ident with + | Lident path -> Lident (path ^ "Props") + | Ldot (ident, path) -> Ldot (ident, path ^ "Props") + | _ -> + raise + (Invalid_argument + "JSX name can't be the result of function applications") + in + let props = + Exp.apply ~attrs ~loc (Exp.ident ~loc {loc; txt = propsIdent}) args + in + (* handle key, ref, children *) + (* React.createElement(Component.make, props, ...children) *) + match !childrenArg with + | None -> + Exp.apply ~loc ~attrs + (Exp.ident ~loc {loc; txt = Ldot (Lident "React", "createElement")}) + [(nolabel, Exp.ident ~loc {txt = ident; loc}); (nolabel, props)] + | Some children -> + Exp.apply ~loc ~attrs + (Exp.ident ~loc + {loc; txt = Ldot (Lident "React", "createElementVariadic")}) + [ + (nolabel, Exp.ident ~loc {txt = ident; loc}); + (nolabel, props); + (nolabel, children); + ] + [@@raises Invalid_argument] + in + + let transformLowercaseCall3 mapper loc attrs callArguments id = + let children, nonChildrenProps = extractChildren ~loc callArguments in + let componentNameExpr = constantString ~loc id in + let childrenExpr = transformChildrenIfList ~loc ~mapper children in + let createElementCall = + match children with + (* [@JSX] div(~children=[a]), coming from
a
*) + | { + pexp_desc = + ( Pexp_construct + ({txt = Lident "::"}, Some {pexp_desc = Pexp_tuple _}) + | Pexp_construct ({txt = Lident "[]"}, None) ); + } -> + "createDOMElementVariadic" + (* [@JSX] div(~children= value), coming from
...(value)
*) + | _ -> + raise + (Invalid_argument + "A spread as a DOM element's children don't make sense written \ + together. You can simply remove the spread.") + in + let args = + match nonChildrenProps with + | [_justTheUnitArgumentAtEnd] -> + [ + (* "div" *) + (nolabel, componentNameExpr); + (* [|moreCreateElementCallsHere|] *) + (nolabel, childrenExpr); + ] + | nonEmptyProps -> + let propsCall = + Exp.apply ~loc + (Exp.ident ~loc + {loc; txt = Ldot (Lident "ReactDOMRe", "domProps")}) + (nonEmptyProps + |> List.map (fun (label, expression) -> + (label, mapper.expr mapper expression))) + in + [ + (* "div" *) + (nolabel, componentNameExpr); + (* ReactDOMRe.props(~className=blabla, ~foo=bar, ()) *) + (labelled "props", propsCall); + (* [|moreCreateElementCallsHere|] *) + (nolabel, childrenExpr); + ] + in + Exp.apply + ~loc (* throw away the [@JSX] attribute and keep the others, if any *) + ~attrs + (* ReactDOMRe.createElement *) + (Exp.ident ~loc + {loc; txt = Ldot (Lident "ReactDOMRe", createElementCall)}) + args + [@@raises Invalid_argument] + in + + let rec recursivelyTransformNamedArgsForMake mapper expr args newtypes = + let expr = mapper.expr mapper expr in + match expr.pexp_desc with + (* TODO: make this show up with a loc. *) + | Pexp_fun (Labelled "key", _, _, _) | Pexp_fun (Optional "key", _, _, _) + -> + raise + (Invalid_argument + "Key cannot be accessed inside of a component. Don't worry - you \ + can always key a component from its parent!") + | Pexp_fun (Labelled "ref", _, _, _) | Pexp_fun (Optional "ref", _, _, _) + -> + raise + (Invalid_argument + "Ref cannot be passed as a normal prop. Please use `forwardRef` \ + API instead.") + | Pexp_fun (arg, default, pattern, expression) + when isOptional arg || isLabelled arg -> + let () = + match (isOptional arg, pattern, default) with + | true, {ppat_desc = Ppat_constraint (_, {ptyp_desc})}, None -> ( + match ptyp_desc with + | Ptyp_constr ({txt = Lident "option"}, [_]) -> () + | _ -> + let currentType = + match ptyp_desc with + | Ptyp_constr ({txt}, []) -> + String.concat "." (Longident.flatten txt) + | Ptyp_constr ({txt}, _innerTypeArgs) -> + String.concat "." (Longident.flatten txt) ^ "(...)" + | _ -> "..." + in + Location.prerr_warning pattern.ppat_loc + (Preprocessor + (Printf.sprintf + "React: optional argument annotations must have explicit \ + `option`. Did you mean `option(%s)=?`?" + currentType))) + | _ -> () + in + let alias = + match pattern with + | {ppat_desc = Ppat_alias (_, {txt}) | Ppat_var {txt}} -> txt + | {ppat_desc = Ppat_any} -> "_" + | _ -> getLabel arg + in + let type_ = + match pattern with + | {ppat_desc = Ppat_constraint (_, type_)} -> Some type_ + | _ -> None + in + + recursivelyTransformNamedArgsForMake mapper expression + ((arg, default, pattern, alias, pattern.ppat_loc, type_) :: args) + newtypes + | Pexp_fun + ( Nolabel, + _, + {ppat_desc = Ppat_construct ({txt = Lident "()"}, _) | Ppat_any}, + _expression ) -> + (args, newtypes, None) + | Pexp_fun + ( Nolabel, + _, + { + ppat_desc = + ( Ppat_var {txt} + | Ppat_constraint ({ppat_desc = Ppat_var {txt}}, _) ); + }, + _expression ) -> + (args, newtypes, Some txt) + | Pexp_fun (Nolabel, _, pattern, _expression) -> + Location.raise_errorf ~loc:pattern.ppat_loc + "React: react.component refs only support plain arguments and type \ + annotations." + | Pexp_newtype (label, expression) -> + recursivelyTransformNamedArgsForMake mapper expression args + (label :: newtypes) + | Pexp_constraint (expression, _typ) -> + recursivelyTransformNamedArgsForMake mapper expression args newtypes + | _ -> (args, newtypes, None) + [@@raises Invalid_argument] + in + + let argToType types (name, default, _noLabelName, _alias, loc, type_) = + match (type_, name, default) with + | ( Some {ptyp_desc = Ptyp_constr ({txt = Lident "option"}, [type_])}, + name, + _ ) + when isOptional name -> + ( getLabel name, + [], + { + type_ with + ptyp_desc = + Ptyp_constr ({loc = type_.ptyp_loc; txt = optionIdent}, [type_]); + } ) + :: types + | Some type_, name, Some _default -> + ( getLabel name, + [], + { + ptyp_desc = Ptyp_constr ({loc; txt = optionIdent}, [type_]); + ptyp_loc = loc; + ptyp_attributes = []; + } ) + :: types + | Some type_, name, _ -> (getLabel name, [], type_) :: types + | None, name, _ when isOptional name -> + ( getLabel name, + [], + { + ptyp_desc = + Ptyp_constr + ( {loc; txt = optionIdent}, + [ + { + ptyp_desc = Ptyp_var (safeTypeFromValue name); + ptyp_loc = loc; + ptyp_attributes = []; + }; + ] ); + ptyp_loc = loc; + ptyp_attributes = []; + } ) + :: types + | None, name, _ when isLabelled name -> + ( getLabel name, + [], + { + ptyp_desc = Ptyp_var (safeTypeFromValue name); + ptyp_loc = loc; + ptyp_attributes = []; + } ) + :: types + | _ -> types + [@@raises Invalid_argument] + in + + let argToConcreteType types (name, loc, type_) = + match name with + | name when isLabelled name -> (getLabel name, [], type_) :: types + | name when isOptional name -> + (getLabel name, [], Typ.constr ~loc {loc; txt = optionIdent} [type_]) + :: types + | _ -> types + in + + let nestedModules = ref [] in + let transformComponentDefinition mapper structure returnStructures = + match structure with + (* external *) + | { + pstr_loc; + pstr_desc = + Pstr_primitive + ({pval_name = {txt = fnName}; pval_attributes; pval_type} as + value_description); + } as pstr -> ( + match List.filter hasAttr pval_attributes with + | [] -> structure :: returnStructures + | [_] -> + let rec getPropTypes types ({ptyp_loc; ptyp_desc} as fullType) = + match ptyp_desc with + | Ptyp_arrow (name, type_, ({ptyp_desc = Ptyp_arrow _} as rest)) + when isLabelled name || isOptional name -> + getPropTypes ((name, ptyp_loc, type_) :: types) rest + | Ptyp_arrow (Nolabel, _type, rest) -> getPropTypes types rest + | Ptyp_arrow (name, type_, returnValue) + when isLabelled name || isOptional name -> + (returnValue, (name, returnValue.ptyp_loc, type_) :: types) + | _ -> (fullType, types) + in + let innerType, propTypes = getPropTypes [] pval_type in + let namedTypeList = List.fold_left argToConcreteType [] propTypes in + let pluckLabelAndLoc (label, loc, type_) = + (label, None (* default *), loc, Some type_) + in + let retPropsType = makePropsType ~loc:pstr_loc namedTypeList in + let externalPropsDecl = + makePropsExternal fnName pstr_loc + ((optional "key", None, pstr_loc, Some (keyType pstr_loc)) + :: List.map pluckLabelAndLoc propTypes) + retPropsType + in + (* can't be an arrow because it will defensively uncurry *) + let newExternalType = + Ptyp_constr + ( {loc = pstr_loc; txt = Ldot (Lident "React", "componentLike")}, + [retPropsType; innerType] ) + in + let newStructure = + { + pstr with + pstr_desc = + Pstr_primitive + { + value_description with + pval_type = {pval_type with ptyp_desc = newExternalType}; + pval_attributes = List.filter otherAttrsPure pval_attributes; + }; + } + in + externalPropsDecl :: newStructure :: returnStructures + | _ -> + raise + (Invalid_argument + "Only one react.component call can exist on a component at one \ + time")) + (* let component = ... *) + | {pstr_loc; pstr_desc = Pstr_value (recFlag, valueBindings)} -> + let fileName = filenameFromLoc pstr_loc in + let emptyLoc = Location.in_file fileName in + let mapBinding binding = + if hasAttrOnBinding binding then + let bindingLoc = binding.pvb_loc in + let bindingPatLoc = binding.pvb_pat.ppat_loc in + let binding = + { + binding with + pvb_pat = {binding.pvb_pat with ppat_loc = emptyLoc}; + pvb_loc = emptyLoc; + } + in + let fnName = getFnName binding.pvb_pat in + let internalFnName = fnName ^ "$Internal" in + let fullModuleName = + makeModuleName fileName !nestedModules fnName + in + let modifiedBindingOld binding = + let expression = binding.pvb_expr in + (* TODO: there is a long-tail of unsupported features inside of blocks - Pexp_letmodule , Pexp_letexception , Pexp_ifthenelse *) + let rec spelunkForFunExpression expression = + match expression with + (* let make = (~prop) => ... *) + | {pexp_desc = Pexp_fun _} | {pexp_desc = Pexp_newtype _} -> + expression + (* let make = {let foo = bar in (~prop) => ...} *) + | {pexp_desc = Pexp_let (_recursive, _vbs, returnExpression)} -> + (* here's where we spelunk! *) + spelunkForFunExpression returnExpression + (* let make = React.forwardRef((~prop) => ...) *) + | { + pexp_desc = + Pexp_apply + (_wrapperExpression, [(Nolabel, innerFunctionExpression)]); + } -> + spelunkForFunExpression innerFunctionExpression + | { + pexp_desc = + Pexp_sequence (_wrapperExpression, innerFunctionExpression); + } -> + spelunkForFunExpression innerFunctionExpression + | {pexp_desc = Pexp_constraint (innerFunctionExpression, _typ)} + -> + spelunkForFunExpression innerFunctionExpression + | _ -> + raise + (Invalid_argument + "react.component calls can only be on function \ + definitions or component wrappers (forwardRef, memo).") + [@@raises Invalid_argument] + in + spelunkForFunExpression expression + in + let modifiedBinding binding = + let hasApplication = ref false in + let wrapExpressionWithBinding expressionFn expression = + Vb.mk ~loc:bindingLoc + ~attrs:(List.filter otherAttrsPure binding.pvb_attributes) + (Pat.var ~loc:bindingPatLoc + {loc = bindingPatLoc; txt = fnName}) + (expressionFn expression) + in + let expression = binding.pvb_expr in + let unerasableIgnoreExp exp = + { + exp with + pexp_attributes = + unerasableIgnore emptyLoc :: exp.pexp_attributes; + } + in + (* TODO: there is a long-tail of unsupported features inside of blocks - Pexp_letmodule , Pexp_letexception , Pexp_ifthenelse *) + let rec spelunkForFunExpression expression = + match expression with + (* let make = (~prop) => ... with no final unit *) + | { + pexp_desc = + Pexp_fun + ( ((Labelled _ | Optional _) as label), + default, + pattern, + ({pexp_desc = Pexp_fun _} as internalExpression) ); + } -> + let wrap, hasUnit, exp = + spelunkForFunExpression internalExpression + in + ( wrap, + hasUnit, + unerasableIgnoreExp + { + expression with + pexp_desc = Pexp_fun (label, default, pattern, exp); + } ) + (* let make = (()) => ... *) + (* let make = (_) => ... *) + | { + pexp_desc = + Pexp_fun + ( Nolabel, + _default, + { + ppat_desc = + Ppat_construct ({txt = Lident "()"}, _) | Ppat_any; + }, + _internalExpression ); + } -> + ((fun a -> a), true, expression) + (* let make = (~prop) => ... *) + | { + pexp_desc = + Pexp_fun + ( (Labelled _ | Optional _), + _default, + _pattern, + _internalExpression ); + } -> + ((fun a -> a), false, unerasableIgnoreExp expression) + (* let make = (prop) => ... *) + | { + pexp_desc = + Pexp_fun (_nolabel, _default, pattern, _internalExpression); + } -> + if hasApplication.contents then + ((fun a -> a), false, unerasableIgnoreExp expression) + else + Location.raise_errorf ~loc:pattern.ppat_loc + "React: props need to be labelled arguments.\n\ + \ If you are working with refs be sure to wrap with \ + React.forwardRef.\n\ + \ If your component doesn't have any props use () or _ \ + instead of a name." + (* let make = {let foo = bar in (~prop) => ...} *) + | {pexp_desc = Pexp_let (recursive, vbs, internalExpression)} -> + (* here's where we spelunk! *) + let wrap, hasUnit, exp = + spelunkForFunExpression internalExpression + in + ( wrap, + hasUnit, + {expression with pexp_desc = Pexp_let (recursive, vbs, exp)} + ) + (* let make = React.forwardRef((~prop) => ...) *) + | { + pexp_desc = + Pexp_apply + (wrapperExpression, [(Nolabel, internalExpression)]); + } -> + let () = hasApplication := true in + let _, hasUnit, exp = + spelunkForFunExpression internalExpression + in + ( (fun exp -> Exp.apply wrapperExpression [(nolabel, exp)]), + hasUnit, + exp ) + | { + pexp_desc = + Pexp_sequence (wrapperExpression, internalExpression); + } -> + let wrap, hasUnit, exp = + spelunkForFunExpression internalExpression + in + ( wrap, + hasUnit, + { + expression with + pexp_desc = Pexp_sequence (wrapperExpression, exp); + } ) + | e -> ((fun a -> a), false, e) + in + let wrapExpression, hasUnit, expression = + spelunkForFunExpression expression + in + (wrapExpressionWithBinding wrapExpression, hasUnit, expression) + in + let bindingWrapper, hasUnit, expression = modifiedBinding binding in + let reactComponentAttribute = + try Some (List.find hasAttr binding.pvb_attributes) + with Not_found -> None + in + let _attr_loc, payload = + match reactComponentAttribute with + | Some (loc, payload) -> (loc.loc, Some payload) + | None -> (emptyLoc, None) + in + let props = getPropsAttr payload in + (* do stuff here! *) + let namedArgList, newtypes, forwardRef = + recursivelyTransformNamedArgsForMake mapper + (modifiedBindingOld binding) + [] [] + in + let namedArgListWithKeyAndRef = + ( optional "key", + None, + Pat.var {txt = "key"; loc = emptyLoc}, + "key", + emptyLoc, + Some (keyType emptyLoc) ) + :: namedArgList + in + let namedArgListWithKeyAndRef = + match forwardRef with + | Some _ -> + ( optional "ref", + None, + Pat.var {txt = "key"; loc = emptyLoc}, + "ref", + emptyLoc, + None ) + :: namedArgListWithKeyAndRef + | None -> namedArgListWithKeyAndRef + in + let namedArgListWithKeyAndRefForNew = + match forwardRef with + | Some txt -> + namedArgList + @ [ + ( nolabel, + None, + Pat.var {txt; loc = emptyLoc}, + txt, + emptyLoc, + None ); + ] + | None -> namedArgList + in + let pluckArg (label, _, _, alias, loc, _) = + let labelString = + match label with + | label when isOptional label || isLabelled label -> + getLabel label + | _ -> "" + in + ( label, + match labelString with + | "" -> Exp.ident ~loc {txt = Lident alias; loc} + | labelString -> + Exp.apply ~loc + (Exp.ident ~loc {txt = Lident "##"; loc}) + [ + ( nolabel, + Exp.ident ~loc {txt = Lident props.propsName; loc} ); + (nolabel, Exp.ident ~loc {txt = Lident labelString; loc}); + ] ) + in + let namedTypeList = List.fold_left argToType [] namedArgList in + let loc = emptyLoc in + let externalArgs = + (* translate newtypes to type variables *) + List.fold_left + (fun args newtype -> + List.map + (fun (a, b, c, d, e, maybeTyp) -> + match maybeTyp with + | Some typ -> + (a, b, c, d, e, Some (newtypeToVar newtype.txt typ)) + | None -> (a, b, c, d, e, None)) + args) + namedArgListWithKeyAndRef newtypes + in + let externalTypes = + (* translate newtypes to type variables *) + List.fold_left + (fun args newtype -> + List.map + (fun (a, b, typ) -> (a, b, newtypeToVar newtype.txt typ)) + args) + namedTypeList newtypes + in + let externalDecl = + makeExternalDecl fnName loc externalArgs externalTypes + in + let innerExpressionArgs = + List.map pluckArg namedArgListWithKeyAndRefForNew + @ + if hasUnit then + [(Nolabel, Exp.construct {loc; txt = Lident "()"} None)] + else [] + in + let innerExpression = + Exp.apply + (Exp.ident + { + loc; + txt = + Lident + (match recFlag with + | Recursive -> internalFnName + | Nonrecursive -> fnName); + }) + innerExpressionArgs + in + let innerExpressionWithRef = + match forwardRef with + | Some txt -> + { + innerExpression with + pexp_desc = + Pexp_fun + ( nolabel, + None, + { + ppat_desc = Ppat_var {txt; loc = emptyLoc}; + ppat_loc = emptyLoc; + ppat_attributes = []; + }, + innerExpression ); + } + | None -> innerExpression + in + let fullExpression = + Exp.fun_ nolabel None + { + ppat_desc = + Ppat_constraint + ( makePropsName ~loc:emptyLoc props.propsName, + makePropsType ~loc:emptyLoc externalTypes ); + ppat_loc = emptyLoc; + ppat_attributes = []; + } + innerExpressionWithRef + in + let fullExpression = + match fullModuleName with + | "" -> fullExpression + | txt -> + Exp.let_ Nonrecursive + [ + Vb.mk ~loc:emptyLoc + (Pat.var ~loc:emptyLoc {loc = emptyLoc; txt}) + fullExpression; + ] + (Exp.ident ~loc:emptyLoc {loc = emptyLoc; txt = Lident txt}) + in + let bindings, newBinding = + match recFlag with + | Recursive -> + ( [ + bindingWrapper + (Exp.let_ ~loc:emptyLoc Recursive + [ + makeNewBinding binding expression internalFnName; + Vb.mk + (Pat.var {loc = emptyLoc; txt = fnName}) + fullExpression; + ] + (Exp.ident {loc = emptyLoc; txt = Lident fnName})); + ], + None ) + | Nonrecursive -> + ( [{binding with pvb_expr = expression; pvb_attributes = []}], + Some (bindingWrapper fullExpression) ) + in + (Some externalDecl, bindings, newBinding) + else (None, [binding], None) + [@@raises Invalid_argument] + in + let structuresAndBinding = List.map mapBinding valueBindings in + let otherStructures (extern, binding, newBinding) + (externs, bindings, newBindings) = + let externs = + match extern with + | Some extern -> extern :: externs + | None -> externs + in + let newBindings = + match newBinding with + | Some newBinding -> newBinding :: newBindings + | None -> newBindings + in + (externs, binding @ bindings, newBindings) + in + let externs, bindings, newBindings = + List.fold_right otherStructures structuresAndBinding ([], [], []) + in + externs + @ [{pstr_loc; pstr_desc = Pstr_value (recFlag, bindings)}] + @ (match newBindings with + | [] -> [] + | newBindings -> + [ + { + pstr_loc = emptyLoc; + pstr_desc = Pstr_value (recFlag, newBindings); + }; + ]) + @ returnStructures + | structure -> structure :: returnStructures + [@@raises Invalid_argument] + in + + let reactComponentTransform mapper structures = + List.fold_right (transformComponentDefinition mapper) structures [] + [@@raises Invalid_argument] + in + + let transformComponentSignature _mapper signature returnSignatures = + match signature with + | { + psig_loc; + psig_desc = + Psig_value + ({pval_name = {txt = fnName}; pval_attributes; pval_type} as + psig_desc); + } as psig -> ( + match List.filter hasAttr pval_attributes with + | [] -> signature :: returnSignatures + | [_] -> + let rec getPropTypes types ({ptyp_loc; ptyp_desc} as fullType) = + match ptyp_desc with + | Ptyp_arrow (name, type_, ({ptyp_desc = Ptyp_arrow _} as rest)) + when isOptional name || isLabelled name -> + getPropTypes ((name, ptyp_loc, type_) :: types) rest + | Ptyp_arrow (Nolabel, _type, rest) -> getPropTypes types rest + | Ptyp_arrow (name, type_, returnValue) + when isOptional name || isLabelled name -> + (returnValue, (name, returnValue.ptyp_loc, type_) :: types) + | _ -> (fullType, types) + in + let innerType, propTypes = getPropTypes [] pval_type in + let namedTypeList = List.fold_left argToConcreteType [] propTypes in + let pluckLabelAndLoc (label, loc, type_) = + (label, None, loc, Some type_) + in + let retPropsType = makePropsType ~loc:psig_loc namedTypeList in + let externalPropsDecl = + makePropsExternalSig fnName psig_loc + ((optional "key", None, psig_loc, Some (keyType psig_loc)) + :: List.map pluckLabelAndLoc propTypes) + retPropsType + in + (* can't be an arrow because it will defensively uncurry *) + let newExternalType = + Ptyp_constr + ( {loc = psig_loc; txt = Ldot (Lident "React", "componentLike")}, + [retPropsType; innerType] ) + in + let newStructure = + { + psig with + psig_desc = + Psig_value + { + psig_desc with + pval_type = {pval_type with ptyp_desc = newExternalType}; + pval_attributes = List.filter otherAttrsPure pval_attributes; + }; + } + in + externalPropsDecl :: newStructure :: returnSignatures + | _ -> + raise + (Invalid_argument + "Only one react.component call can exist on a component at one \ + time")) + | signature -> signature :: returnSignatures + [@@raises Invalid_argument] + in + + let reactComponentSignatureTransform mapper signatures = + List.fold_right (transformComponentSignature mapper) signatures [] + [@@raises Invalid_argument] + in + + let transformJsxCall mapper callExpression callArguments attrs = + match callExpression.pexp_desc with + | Pexp_ident caller -> ( + match caller with + | {txt = Lident "createElement"} -> + raise + (Invalid_argument + "JSX: `createElement` should be preceeded by a module name.") + (* Foo.createElement(~prop1=foo, ~prop2=bar, ~children=[], ()) *) + | {loc; txt = Ldot (modulePath, ("createElement" | "make"))} -> ( + match !jsxVersion with + | None | Some 3 -> + transformUppercaseCall3 modulePath mapper loc attrs callExpression + callArguments + | Some _ -> raise (Invalid_argument "JSX: the JSX version must be 3") + ) + (* div(~prop1=foo, ~prop2=bar, ~children=[bla], ()) *) + (* turn that into + ReactDOMRe.createElement(~props=ReactDOMRe.props(~props1=foo, ~props2=bar, ()), [|bla|]) *) + | {loc; txt = Lident id} -> ( + match !jsxVersion with + | None | Some 3 -> + transformLowercaseCall3 mapper loc attrs callArguments id + | Some _ -> raise (Invalid_argument "JSX: the JSX version must be 3")) + | {txt = Ldot (_, anythingNotCreateElementOrMake)} -> + raise + (Invalid_argument + ("JSX: the JSX attribute should be attached to a \ + `YourModuleName.createElement` or `YourModuleName.make` call. \ + We saw `" ^ anythingNotCreateElementOrMake ^ "` instead")) + | {txt = Lapply _} -> + (* don't think there's ever a case where this is reached *) + raise + (Invalid_argument + "JSX: encountered a weird case while processing the code. \ + Please report this!")) + | _ -> + raise + (Invalid_argument + "JSX: `createElement` should be preceeded by a simple, direct \ + module name.") + [@@raises Invalid_argument] + in + + let signature mapper signature = + default_mapper.signature mapper + @@ reactComponentSignatureTransform mapper signature + [@@raises Invalid_argument] + in + + let structure mapper structure = + match structure with + | structures -> + default_mapper.structure mapper + @@ reactComponentTransform mapper structures + [@@raises Invalid_argument] + in + + let expr mapper expression = + match expression with + (* Does the function application have the @JSX attribute? *) + | { + pexp_desc = Pexp_apply (callExpression, callArguments); + pexp_attributes; + } -> ( + let jsxAttribute, nonJSXAttributes = + List.partition + (fun (attribute, _) -> attribute.txt = "JSX") + pexp_attributes + in + match (jsxAttribute, nonJSXAttributes) with + (* no JSX attribute *) + | [], _ -> default_mapper.expr mapper expression + | _, nonJSXAttributes -> + transformJsxCall mapper callExpression callArguments nonJSXAttributes) + (* is it a list with jsx attribute? Reason <>foo desugars to [@JSX][foo]*) + | { + pexp_desc = + ( Pexp_construct + ({txt = Lident "::"; loc}, Some {pexp_desc = Pexp_tuple _}) + | Pexp_construct ({txt = Lident "[]"; loc}, None) ); + pexp_attributes; + } as listItems -> ( + let jsxAttribute, nonJSXAttributes = + List.partition + (fun (attribute, _) -> attribute.txt = "JSX") + pexp_attributes + in + match (jsxAttribute, nonJSXAttributes) with + (* no JSX attribute *) + | [], _ -> default_mapper.expr mapper expression + | _, nonJSXAttributes -> + let loc = {loc with loc_ghost = true} in + let fragment = + Exp.ident ~loc {loc; txt = Ldot (Lident "ReasonReact", "fragment")} + in + let childrenExpr = transformChildrenIfList ~loc ~mapper listItems in + let args = + [ + (* "div" *) + (nolabel, fragment); + (* [|moreCreateElementCallsHere|] *) + (nolabel, childrenExpr); + ] + in + Exp.apply + ~loc + (* throw away the [@JSX] attribute and keep the others, if any *) + ~attrs:nonJSXAttributes + (* ReactDOMRe.createElement *) + (Exp.ident ~loc + {loc; txt = Ldot (Lident "ReactDOMRe", "createElement")}) + args) + (* Delegate to the default mapper, a deep identity traversal *) + | e -> default_mapper.expr mapper e + [@@raises Invalid_argument] + in + + let module_binding mapper module_binding = + let _ = nestedModules := module_binding.pmb_name.txt :: !nestedModules in + let mapped = default_mapper.module_binding mapper module_binding in + let _ = nestedModules := List.tl !nestedModules in + mapped + [@@raises Failure] + in + {default_mapper with structure; expr; signature; module_binding} + [@@raises Invalid_argument, Failure] + + let rewrite_implementation (code : Parsetree.structure) : Parsetree.structure + = + let mapper = jsxMapper () in + mapper.structure mapper code + [@@raises Invalid_argument, Failure] + + let rewrite_signature (code : Parsetree.signature) : Parsetree.signature = + let mapper = jsxMapper () in + mapper.signature mapper code + [@@raises Invalid_argument, Failure] +end + module V4 = struct open Ast_helper open Ast_mapper @@ -1489,7 +2799,7 @@ end let rewrite_implementation ~jsxVersion ~jsxModule ~jsxMode (code : Parsetree.structure) : Parsetree.structure = match (jsxVersion, jsxModule, jsxMode) with - | 3, _, _ -> Reactjs_jsx_ppx_v3.rewrite_implementation code + | 3, _, _ -> V3.rewrite_implementation code | 4, _, "classic" -> V4.rewrite_implementation ~jsxMode code | 4, _, "automatic" -> V4.rewrite_implementation ~jsxMode code | _ -> code @@ -1498,7 +2808,7 @@ let rewrite_implementation ~jsxVersion ~jsxModule ~jsxMode let rewrite_signature ~jsxVersion ~jsxModule ~jsxMode (code : Parsetree.signature) : Parsetree.signature = match (jsxVersion, jsxModule, jsxMode) with - | 3, _, _ -> Reactjs_jsx_ppx_v3.rewrite_signature code + | 3, _, _ -> V3.rewrite_signature code | 4, _, "classic" -> V4.rewrite_signature ~jsxMode code | 4, _, "automatic" -> V4.rewrite_signature ~jsxMode code | _ -> code diff --git a/cli/reactjs_jsx_ppx_v3.ml b/cli/reactjs_jsx_ppx_v3.ml deleted file mode 100644 index b0f060f6..00000000 --- a/cli/reactjs_jsx_ppx_v3.ml +++ /dev/null @@ -1,1277 +0,0 @@ -open Ast_helper -open Ast_mapper -open Asttypes -open Parsetree -open Longident - -let rec find_opt p = function - | [] -> None - | x :: l -> if p x then Some x else find_opt p l - -let nolabel = Nolabel - -let labelled str = Labelled str - -let optional str = Optional str - -let isOptional str = - match str with - | Optional _ -> true - | _ -> false - -let isLabelled str = - match str with - | Labelled _ -> true - | _ -> false - -let getLabel str = - match str with - | Optional str | Labelled str -> str - | Nolabel -> "" - -let optionIdent = Lident "option" - -let constantString ~loc str = - Ast_helper.Exp.constant ~loc (Pconst_string (str, None)) - -let safeTypeFromValue valueStr = - let valueStr = getLabel valueStr in - match String.sub valueStr 0 1 with - | "_" -> "T" ^ valueStr - | _ -> valueStr - [@@raises Invalid_argument] - -let keyType loc = - Typ.constr ~loc {loc; txt = optionIdent} - [Typ.constr ~loc {loc; txt = Lident "string"} []] - -type 'a children = ListLiteral of 'a | Exact of 'a - -type componentConfig = {propsName: string} - -(* if children is a list, convert it to an array while mapping each element. If not, just map over it, as usual *) -let transformChildrenIfListUpper ~loc ~mapper theList = - let rec transformChildren_ theList accum = - (* not in the sense of converting a list to an array; convert the AST - reprensentation of a list to the AST reprensentation of an array *) - match theList with - | {pexp_desc = Pexp_construct ({txt = Lident "[]"}, None)} -> ( - match accum with - | [singleElement] -> Exact singleElement - | accum -> ListLiteral (Exp.array ~loc (List.rev accum))) - | { - pexp_desc = - Pexp_construct - ({txt = Lident "::"}, Some {pexp_desc = Pexp_tuple [v; acc]}); - } -> - transformChildren_ acc (mapper.expr mapper v :: accum) - | notAList -> Exact (mapper.expr mapper notAList) - in - transformChildren_ theList [] - -let transformChildrenIfList ~loc ~mapper theList = - let rec transformChildren_ theList accum = - (* not in the sense of converting a list to an array; convert the AST - reprensentation of a list to the AST reprensentation of an array *) - match theList with - | {pexp_desc = Pexp_construct ({txt = Lident "[]"}, None)} -> - Exp.array ~loc (List.rev accum) - | { - pexp_desc = - Pexp_construct - ({txt = Lident "::"}, Some {pexp_desc = Pexp_tuple [v; acc]}); - } -> - transformChildren_ acc (mapper.expr mapper v :: accum) - | notAList -> mapper.expr mapper notAList - in - transformChildren_ theList [] - -let extractChildren ?(removeLastPositionUnit = false) ~loc propsAndChildren = - let rec allButLast_ lst acc = - match lst with - | [] -> [] - | [(Nolabel, {pexp_desc = Pexp_construct ({txt = Lident "()"}, None)})] -> - acc - | (Nolabel, _) :: _rest -> - raise - (Invalid_argument - "JSX: found non-labelled argument before the last position") - | arg :: rest -> allButLast_ rest (arg :: acc) - [@@raises Invalid_argument] - in - let allButLast lst = - allButLast_ lst [] |> List.rev - [@@raises Invalid_argument] - in - match - List.partition - (fun (label, _) -> label = labelled "children") - propsAndChildren - with - | [], props -> - (* no children provided? Place a placeholder list *) - ( Exp.construct ~loc {loc; txt = Lident "[]"} None, - if removeLastPositionUnit then allButLast props else props ) - | [(_, childrenExpr)], props -> - (childrenExpr, if removeLastPositionUnit then allButLast props else props) - | _ -> - raise - (Invalid_argument "JSX: somehow there's more than one `children` label") - [@@raises Invalid_argument] - -let unerasableIgnore loc = - ( {loc; txt = "warning"}, - PStr [Str.eval (Exp.constant (Pconst_string ("-16", None)))] ) - -let merlinFocus = ({loc = Location.none; txt = "merlin.focus"}, PStr []) - -(* Helper method to look up the [@react.component] attribute *) -let hasAttr (loc, _) = loc.txt = "react.component" - -(* Helper method to filter out any attribute that isn't [@react.component] *) -let otherAttrsPure (loc, _) = loc.txt <> "react.component" - -(* Iterate over the attributes and try to find the [@react.component] attribute *) -let hasAttrOnBinding {pvb_attributes} = find_opt hasAttr pvb_attributes <> None - -(* Finds the name of the variable the binding is assigned to, otherwise raises Invalid_argument *) -let rec getFnName binding = - match binding with - | {ppat_desc = Ppat_var {txt}} -> txt - | {ppat_desc = Ppat_constraint (pat, _)} -> getFnName pat - | _ -> - raise (Invalid_argument "react.component calls cannot be destructured.") - [@@raises Invalid_argument] - -let makeNewBinding binding expression newName = - match binding with - | {pvb_pat = {ppat_desc = Ppat_var ppat_var} as pvb_pat} -> - { - binding with - pvb_pat = - {pvb_pat with ppat_desc = Ppat_var {ppat_var with txt = newName}}; - pvb_expr = expression; - pvb_attributes = [merlinFocus]; - } - | _ -> - raise (Invalid_argument "react.component calls cannot be destructured.") - [@@raises Invalid_argument] - -(* Lookup the value of `props` otherwise raise Invalid_argument error *) -let getPropsNameValue _acc (loc, exp) = - match (loc, exp) with - | {txt = Lident "props"}, {pexp_desc = Pexp_ident {txt = Lident str}} -> - {propsName = str} - | {txt}, _ -> - raise - (Invalid_argument - ("react.component only accepts props as an option, given: " - ^ Longident.last txt)) - [@@raises Invalid_argument] - -(* Lookup the `props` record or string as part of [@react.component] and store the name for use when rewriting *) -let getPropsAttr payload = - let defaultProps = {propsName = "Props"} in - match payload with - | Some - (PStr - ({ - pstr_desc = - Pstr_eval ({pexp_desc = Pexp_record (recordFields, None)}, _); - } - :: _rest)) -> - List.fold_left getPropsNameValue defaultProps recordFields - | Some - (PStr - ({ - pstr_desc = - Pstr_eval ({pexp_desc = Pexp_ident {txt = Lident "props"}}, _); - } - :: _rest)) -> - {propsName = "props"} - | Some (PStr ({pstr_desc = Pstr_eval (_, _)} :: _rest)) -> - raise - (Invalid_argument - "react.component accepts a record config with props as an options.") - | _ -> defaultProps - [@@raises Invalid_argument] - -(* Plucks the label, loc, and type_ from an AST node *) -let pluckLabelDefaultLocType (label, default, _, _, loc, type_) = - (label, default, loc, type_) - -(* Lookup the filename from the location information on the AST node and turn it into a valid module identifier *) -let filenameFromLoc (pstr_loc : Location.t) = - let fileName = - match pstr_loc.loc_start.pos_fname with - | "" -> !Location.input_name - | fileName -> fileName - in - let fileName = - try Filename.chop_extension (Filename.basename fileName) - with Invalid_argument _ -> fileName - in - let fileName = String.capitalize_ascii fileName in - fileName - -(* Build a string representation of a module name with segments separated by $ *) -let makeModuleName fileName nestedModules fnName = - let fullModuleName = - match (fileName, nestedModules, fnName) with - (* TODO: is this even reachable? It seems like the fileName always exists *) - | "", nestedModules, "make" -> nestedModules - | "", nestedModules, fnName -> List.rev (fnName :: nestedModules) - | fileName, nestedModules, "make" -> fileName :: List.rev nestedModules - | fileName, nestedModules, fnName -> - fileName :: List.rev (fnName :: nestedModules) - in - let fullModuleName = String.concat "$" fullModuleName in - fullModuleName - -(* - AST node builders - These functions help us build AST nodes that are needed when transforming a [@react.component] into a - constructor and a props external -*) - -(* Build an AST node representing all named args for the `external` definition for a component's props *) -let rec recursivelyMakeNamedArgsForExternal list args = - match list with - | (label, default, loc, interiorType) :: tl -> - recursivelyMakeNamedArgsForExternal tl - (Typ.arrow ~loc label - (match (label, interiorType, default) with - (* ~foo=1 *) - | label, None, Some _ -> - { - ptyp_desc = Ptyp_var (safeTypeFromValue label); - ptyp_loc = loc; - ptyp_attributes = []; - } - (* ~foo: int=1 *) - | _label, Some type_, Some _ -> type_ - (* ~foo: option(int)=? *) - | ( label, - Some {ptyp_desc = Ptyp_constr ({txt = Lident "option"}, [type_])}, - _ ) - | ( label, - Some - { - ptyp_desc = - Ptyp_constr - ({txt = Ldot (Lident "*predef*", "option")}, [type_]); - }, - _ ) - (* ~foo: int=? - note this isnt valid. but we want to get a type error *) - | label, Some type_, _ - when isOptional label -> - type_ - (* ~foo=? *) - | label, None, _ when isOptional label -> - { - ptyp_desc = Ptyp_var (safeTypeFromValue label); - ptyp_loc = loc; - ptyp_attributes = []; - } - (* ~foo *) - | label, None, _ -> - { - ptyp_desc = Ptyp_var (safeTypeFromValue label); - ptyp_loc = loc; - ptyp_attributes = []; - } - | _label, Some type_, _ -> type_) - args) - | [] -> args - [@@raises Invalid_argument] - -(* Build an AST node for the [@bs.obj] representing props for a component *) -let makePropsValue fnName loc namedArgListWithKeyAndRef propsType = - let propsName = fnName ^ "Props" in - { - pval_name = {txt = propsName; loc}; - pval_type = - recursivelyMakeNamedArgsForExternal namedArgListWithKeyAndRef - (Typ.arrow nolabel - { - ptyp_desc = Ptyp_constr ({txt = Lident "unit"; loc}, []); - ptyp_loc = loc; - ptyp_attributes = []; - } - propsType); - pval_prim = [""]; - pval_attributes = [({txt = "bs.obj"; loc}, PStr [])]; - pval_loc = loc; - } - [@@raises Invalid_argument] - -(* Build an AST node representing an `external` with the definition of the [@bs.obj] *) -let makePropsExternal fnName loc namedArgListWithKeyAndRef propsType = - { - pstr_loc = loc; - pstr_desc = - Pstr_primitive - (makePropsValue fnName loc namedArgListWithKeyAndRef propsType); - } - [@@raises Invalid_argument] - -(* Build an AST node for the signature of the `external` definition *) -let makePropsExternalSig fnName loc namedArgListWithKeyAndRef propsType = - { - psig_loc = loc; - psig_desc = - Psig_value (makePropsValue fnName loc namedArgListWithKeyAndRef propsType); - } - [@@raises Invalid_argument] - -(* Build an AST node for the props name when converted to an object inside the function signature *) -let makePropsName ~loc name = - {ppat_desc = Ppat_var {txt = name; loc}; ppat_loc = loc; ppat_attributes = []} - -let makeObjectField loc (str, attrs, type_) = - Otag ({loc; txt = str}, attrs, type_) - -(* Build an AST node representing a "closed" object representing a component's props *) -let makePropsType ~loc namedTypeList = - Typ.mk ~loc - (Ptyp_object (List.map (makeObjectField loc) namedTypeList, Closed)) - -(* Builds an AST node for the entire `external` definition of props *) -let makeExternalDecl fnName loc namedArgListWithKeyAndRef namedTypeList = - makePropsExternal fnName loc - (List.map pluckLabelDefaultLocType namedArgListWithKeyAndRef) - (makePropsType ~loc namedTypeList) - [@@raises Invalid_argument] - -let newtypeToVar newtype type_ = - let var_desc = Ptyp_var ("type-" ^ newtype) in - let typ (mapper : Ast_mapper.mapper) typ = - match typ.ptyp_desc with - | Ptyp_constr ({txt = Lident name}, _) when name = newtype -> - {typ with ptyp_desc = var_desc} - | _ -> Ast_mapper.default_mapper.typ mapper typ - in - let mapper = {Ast_mapper.default_mapper with typ} in - mapper.typ mapper type_ - -(* TODO: some line number might still be wrong *) -let jsxMapper () = - let jsxVersion = ref None in - - let transformUppercaseCall3 modulePath mapper loc attrs _ callArguments = - let children, argsWithLabels = - extractChildren ~loc ~removeLastPositionUnit:true callArguments - in - let argsForMake = argsWithLabels in - let childrenExpr = transformChildrenIfListUpper ~loc ~mapper children in - let recursivelyTransformedArgsForMake = - argsForMake - |> List.map (fun (label, expression) -> - (label, mapper.expr mapper expression)) - in - let childrenArg = ref None in - let args = - recursivelyTransformedArgsForMake - @ (match childrenExpr with - | Exact children -> [(labelled "children", children)] - | ListLiteral {pexp_desc = Pexp_array list} when list = [] -> [] - | ListLiteral expression -> - (* this is a hack to support react components that introspect into their children *) - childrenArg := Some expression; - [ - ( labelled "children", - Exp.ident ~loc {loc; txt = Ldot (Lident "React", "null")} ); - ]) - @ [(nolabel, Exp.construct ~loc {loc; txt = Lident "()"} None)] - in - let isCap str = - let first = String.sub str 0 1 [@@raises Invalid_argument] in - let capped = String.uppercase_ascii first in - first = capped - [@@raises Invalid_argument] - in - let ident = - match modulePath with - | Lident _ -> Ldot (modulePath, "make") - | Ldot (_modulePath, value) as fullPath when isCap value -> - Ldot (fullPath, "make") - | modulePath -> modulePath - in - let propsIdent = - match ident with - | Lident path -> Lident (path ^ "Props") - | Ldot (ident, path) -> Ldot (ident, path ^ "Props") - | _ -> - raise - (Invalid_argument - "JSX name can't be the result of function applications") - in - let props = - Exp.apply ~attrs ~loc (Exp.ident ~loc {loc; txt = propsIdent}) args - in - (* handle key, ref, children *) - (* React.createElement(Component.make, props, ...children) *) - match !childrenArg with - | None -> - Exp.apply ~loc ~attrs - (Exp.ident ~loc {loc; txt = Ldot (Lident "React", "createElement")}) - [(nolabel, Exp.ident ~loc {txt = ident; loc}); (nolabel, props)] - | Some children -> - Exp.apply ~loc ~attrs - (Exp.ident ~loc - {loc; txt = Ldot (Lident "React", "createElementVariadic")}) - [ - (nolabel, Exp.ident ~loc {txt = ident; loc}); - (nolabel, props); - (nolabel, children); - ] - [@@raises Invalid_argument] - in - - let transformLowercaseCall3 mapper loc attrs callArguments id = - let children, nonChildrenProps = extractChildren ~loc callArguments in - let componentNameExpr = constantString ~loc id in - let childrenExpr = transformChildrenIfList ~loc ~mapper children in - let createElementCall = - match children with - (* [@JSX] div(~children=[a]), coming from
a
*) - | { - pexp_desc = - ( Pexp_construct ({txt = Lident "::"}, Some {pexp_desc = Pexp_tuple _}) - | Pexp_construct ({txt = Lident "[]"}, None) ); - } -> - "createDOMElementVariadic" - (* [@JSX] div(~children= value), coming from
...(value)
*) - | _ -> - raise - (Invalid_argument - "A spread as a DOM element's children don't make sense written \ - together. You can simply remove the spread.") - in - let args = - match nonChildrenProps with - | [_justTheUnitArgumentAtEnd] -> - [ - (* "div" *) - (nolabel, componentNameExpr); - (* [|moreCreateElementCallsHere|] *) - (nolabel, childrenExpr); - ] - | nonEmptyProps -> - let propsCall = - Exp.apply ~loc - (Exp.ident ~loc {loc; txt = Ldot (Lident "ReactDOMRe", "domProps")}) - (nonEmptyProps - |> List.map (fun (label, expression) -> - (label, mapper.expr mapper expression))) - in - [ - (* "div" *) - (nolabel, componentNameExpr); - (* ReactDOMRe.props(~className=blabla, ~foo=bar, ()) *) - (labelled "props", propsCall); - (* [|moreCreateElementCallsHere|] *) - (nolabel, childrenExpr); - ] - in - Exp.apply - ~loc (* throw away the [@JSX] attribute and keep the others, if any *) - ~attrs - (* ReactDOMRe.createElement *) - (Exp.ident ~loc - {loc; txt = Ldot (Lident "ReactDOMRe", createElementCall)}) - args - [@@raises Invalid_argument] - in - - let rec recursivelyTransformNamedArgsForMake mapper expr args newtypes = - let expr = mapper.expr mapper expr in - match expr.pexp_desc with - (* TODO: make this show up with a loc. *) - | Pexp_fun (Labelled "key", _, _, _) | Pexp_fun (Optional "key", _, _, _) -> - raise - (Invalid_argument - "Key cannot be accessed inside of a component. Don't worry - you \ - can always key a component from its parent!") - | Pexp_fun (Labelled "ref", _, _, _) | Pexp_fun (Optional "ref", _, _, _) -> - raise - (Invalid_argument - "Ref cannot be passed as a normal prop. Please use `forwardRef` API \ - instead.") - | Pexp_fun (arg, default, pattern, expression) - when isOptional arg || isLabelled arg -> - let () = - match (isOptional arg, pattern, default) with - | true, {ppat_desc = Ppat_constraint (_, {ptyp_desc})}, None -> ( - match ptyp_desc with - | Ptyp_constr ({txt = Lident "option"}, [_]) -> () - | _ -> - let currentType = - match ptyp_desc with - | Ptyp_constr ({txt}, []) -> - String.concat "." (Longident.flatten txt) - | Ptyp_constr ({txt}, _innerTypeArgs) -> - String.concat "." (Longident.flatten txt) ^ "(...)" - | _ -> "..." - in - Location.prerr_warning pattern.ppat_loc - (Preprocessor - (Printf.sprintf - "React: optional argument annotations must have explicit \ - `option`. Did you mean `option(%s)=?`?" - currentType))) - | _ -> () - in - let alias = - match pattern with - | {ppat_desc = Ppat_alias (_, {txt}) | Ppat_var {txt}} -> txt - | {ppat_desc = Ppat_any} -> "_" - | _ -> getLabel arg - in - let type_ = - match pattern with - | {ppat_desc = Ppat_constraint (_, type_)} -> Some type_ - | _ -> None - in - - recursivelyTransformNamedArgsForMake mapper expression - ((arg, default, pattern, alias, pattern.ppat_loc, type_) :: args) - newtypes - | Pexp_fun - ( Nolabel, - _, - {ppat_desc = Ppat_construct ({txt = Lident "()"}, _) | Ppat_any}, - _expression ) -> - (args, newtypes, None) - | Pexp_fun - ( Nolabel, - _, - { - ppat_desc = - Ppat_var {txt} | Ppat_constraint ({ppat_desc = Ppat_var {txt}}, _); - }, - _expression ) -> - (args, newtypes, Some txt) - | Pexp_fun (Nolabel, _, pattern, _expression) -> - Location.raise_errorf ~loc:pattern.ppat_loc - "React: react.component refs only support plain arguments and type \ - annotations." - | Pexp_newtype (label, expression) -> - recursivelyTransformNamedArgsForMake mapper expression args - (label :: newtypes) - | Pexp_constraint (expression, _typ) -> - recursivelyTransformNamedArgsForMake mapper expression args newtypes - | _ -> (args, newtypes, None) - [@@raises Invalid_argument] - in - - let argToType types (name, default, _noLabelName, _alias, loc, type_) = - match (type_, name, default) with - | Some {ptyp_desc = Ptyp_constr ({txt = Lident "option"}, [type_])}, name, _ - when isOptional name -> - ( getLabel name, - [], - { - type_ with - ptyp_desc = - Ptyp_constr ({loc = type_.ptyp_loc; txt = optionIdent}, [type_]); - } ) - :: types - | Some type_, name, Some _default -> - ( getLabel name, - [], - { - ptyp_desc = Ptyp_constr ({loc; txt = optionIdent}, [type_]); - ptyp_loc = loc; - ptyp_attributes = []; - } ) - :: types - | Some type_, name, _ -> (getLabel name, [], type_) :: types - | None, name, _ when isOptional name -> - ( getLabel name, - [], - { - ptyp_desc = - Ptyp_constr - ( {loc; txt = optionIdent}, - [ - { - ptyp_desc = Ptyp_var (safeTypeFromValue name); - ptyp_loc = loc; - ptyp_attributes = []; - }; - ] ); - ptyp_loc = loc; - ptyp_attributes = []; - } ) - :: types - | None, name, _ when isLabelled name -> - ( getLabel name, - [], - { - ptyp_desc = Ptyp_var (safeTypeFromValue name); - ptyp_loc = loc; - ptyp_attributes = []; - } ) - :: types - | _ -> types - [@@raises Invalid_argument] - in - - let argToConcreteType types (name, loc, type_) = - match name with - | name when isLabelled name -> (getLabel name, [], type_) :: types - | name when isOptional name -> - (getLabel name, [], Typ.constr ~loc {loc; txt = optionIdent} [type_]) - :: types - | _ -> types - in - - let nestedModules = ref [] in - let transformComponentDefinition mapper structure returnStructures = - match structure with - (* external *) - | { - pstr_loc; - pstr_desc = - Pstr_primitive - ({pval_name = {txt = fnName}; pval_attributes; pval_type} as - value_description); - } as pstr -> ( - match List.filter hasAttr pval_attributes with - | [] -> structure :: returnStructures - | [_] -> - let rec getPropTypes types ({ptyp_loc; ptyp_desc} as fullType) = - match ptyp_desc with - | Ptyp_arrow (name, type_, ({ptyp_desc = Ptyp_arrow _} as rest)) - when isLabelled name || isOptional name -> - getPropTypes ((name, ptyp_loc, type_) :: types) rest - | Ptyp_arrow (Nolabel, _type, rest) -> getPropTypes types rest - | Ptyp_arrow (name, type_, returnValue) - when isLabelled name || isOptional name -> - (returnValue, (name, returnValue.ptyp_loc, type_) :: types) - | _ -> (fullType, types) - in - let innerType, propTypes = getPropTypes [] pval_type in - let namedTypeList = List.fold_left argToConcreteType [] propTypes in - let pluckLabelAndLoc (label, loc, type_) = - (label, None (* default *), loc, Some type_) - in - let retPropsType = makePropsType ~loc:pstr_loc namedTypeList in - let externalPropsDecl = - makePropsExternal fnName pstr_loc - ((optional "key", None, pstr_loc, Some (keyType pstr_loc)) - :: List.map pluckLabelAndLoc propTypes) - retPropsType - in - (* can't be an arrow because it will defensively uncurry *) - let newExternalType = - Ptyp_constr - ( {loc = pstr_loc; txt = Ldot (Lident "React", "componentLike")}, - [retPropsType; innerType] ) - in - let newStructure = - { - pstr with - pstr_desc = - Pstr_primitive - { - value_description with - pval_type = {pval_type with ptyp_desc = newExternalType}; - pval_attributes = List.filter otherAttrsPure pval_attributes; - }; - } - in - externalPropsDecl :: newStructure :: returnStructures - | _ -> - raise - (Invalid_argument - "Only one react.component call can exist on a component at one \ - time")) - (* let component = ... *) - | {pstr_loc; pstr_desc = Pstr_value (recFlag, valueBindings)} -> - let fileName = filenameFromLoc pstr_loc in - let emptyLoc = Location.in_file fileName in - let mapBinding binding = - if hasAttrOnBinding binding then - let bindingLoc = binding.pvb_loc in - let bindingPatLoc = binding.pvb_pat.ppat_loc in - let binding = - { - binding with - pvb_pat = {binding.pvb_pat with ppat_loc = emptyLoc}; - pvb_loc = emptyLoc; - } - in - let fnName = getFnName binding.pvb_pat in - let internalFnName = fnName ^ "$Internal" in - let fullModuleName = makeModuleName fileName !nestedModules fnName in - let modifiedBindingOld binding = - let expression = binding.pvb_expr in - (* TODO: there is a long-tail of unsupported features inside of blocks - Pexp_letmodule , Pexp_letexception , Pexp_ifthenelse *) - let rec spelunkForFunExpression expression = - match expression with - (* let make = (~prop) => ... *) - | {pexp_desc = Pexp_fun _} | {pexp_desc = Pexp_newtype _} -> - expression - (* let make = {let foo = bar in (~prop) => ...} *) - | {pexp_desc = Pexp_let (_recursive, _vbs, returnExpression)} -> - (* here's where we spelunk! *) - spelunkForFunExpression returnExpression - (* let make = React.forwardRef((~prop) => ...) *) - | { - pexp_desc = - Pexp_apply - (_wrapperExpression, [(Nolabel, innerFunctionExpression)]); - } -> - spelunkForFunExpression innerFunctionExpression - | { - pexp_desc = - Pexp_sequence (_wrapperExpression, innerFunctionExpression); - } -> - spelunkForFunExpression innerFunctionExpression - | {pexp_desc = Pexp_constraint (innerFunctionExpression, _typ)} -> - spelunkForFunExpression innerFunctionExpression - | _ -> - raise - (Invalid_argument - "react.component calls can only be on function \ - definitions or component wrappers (forwardRef, memo).") - [@@raises Invalid_argument] - in - spelunkForFunExpression expression - in - let modifiedBinding binding = - let hasApplication = ref false in - let wrapExpressionWithBinding expressionFn expression = - Vb.mk ~loc:bindingLoc - ~attrs:(List.filter otherAttrsPure binding.pvb_attributes) - (Pat.var ~loc:bindingPatLoc {loc = bindingPatLoc; txt = fnName}) - (expressionFn expression) - in - let expression = binding.pvb_expr in - let unerasableIgnoreExp exp = - { - exp with - pexp_attributes = - unerasableIgnore emptyLoc :: exp.pexp_attributes; - } - in - (* TODO: there is a long-tail of unsupported features inside of blocks - Pexp_letmodule , Pexp_letexception , Pexp_ifthenelse *) - let rec spelunkForFunExpression expression = - match expression with - (* let make = (~prop) => ... with no final unit *) - | { - pexp_desc = - Pexp_fun - ( ((Labelled _ | Optional _) as label), - default, - pattern, - ({pexp_desc = Pexp_fun _} as internalExpression) ); - } -> - let wrap, hasUnit, exp = - spelunkForFunExpression internalExpression - in - ( wrap, - hasUnit, - unerasableIgnoreExp - { - expression with - pexp_desc = Pexp_fun (label, default, pattern, exp); - } ) - (* let make = (()) => ... *) - (* let make = (_) => ... *) - | { - pexp_desc = - Pexp_fun - ( Nolabel, - _default, - { - ppat_desc = - Ppat_construct ({txt = Lident "()"}, _) | Ppat_any; - }, - _internalExpression ); - } -> - ((fun a -> a), true, expression) - (* let make = (~prop) => ... *) - | { - pexp_desc = - Pexp_fun - ( (Labelled _ | Optional _), - _default, - _pattern, - _internalExpression ); - } -> - ((fun a -> a), false, unerasableIgnoreExp expression) - (* let make = (prop) => ... *) - | { - pexp_desc = - Pexp_fun (_nolabel, _default, pattern, _internalExpression); - } -> - if hasApplication.contents then - ((fun a -> a), false, unerasableIgnoreExp expression) - else - Location.raise_errorf ~loc:pattern.ppat_loc - "React: props need to be labelled arguments.\n\ - \ If you are working with refs be sure to wrap with \ - React.forwardRef.\n\ - \ If your component doesn't have any props use () or _ \ - instead of a name." - (* let make = {let foo = bar in (~prop) => ...} *) - | {pexp_desc = Pexp_let (recursive, vbs, internalExpression)} -> - (* here's where we spelunk! *) - let wrap, hasUnit, exp = - spelunkForFunExpression internalExpression - in - ( wrap, - hasUnit, - {expression with pexp_desc = Pexp_let (recursive, vbs, exp)} - ) - (* let make = React.forwardRef((~prop) => ...) *) - | { - pexp_desc = - Pexp_apply (wrapperExpression, [(Nolabel, internalExpression)]); - } -> - let () = hasApplication := true in - let _, hasUnit, exp = - spelunkForFunExpression internalExpression - in - ( (fun exp -> Exp.apply wrapperExpression [(nolabel, exp)]), - hasUnit, - exp ) - | { - pexp_desc = Pexp_sequence (wrapperExpression, internalExpression); - } -> - let wrap, hasUnit, exp = - spelunkForFunExpression internalExpression - in - ( wrap, - hasUnit, - { - expression with - pexp_desc = Pexp_sequence (wrapperExpression, exp); - } ) - | e -> ((fun a -> a), false, e) - in - let wrapExpression, hasUnit, expression = - spelunkForFunExpression expression - in - (wrapExpressionWithBinding wrapExpression, hasUnit, expression) - in - let bindingWrapper, hasUnit, expression = modifiedBinding binding in - let reactComponentAttribute = - try Some (List.find hasAttr binding.pvb_attributes) - with Not_found -> None - in - let _attr_loc, payload = - match reactComponentAttribute with - | Some (loc, payload) -> (loc.loc, Some payload) - | None -> (emptyLoc, None) - in - let props = getPropsAttr payload in - (* do stuff here! *) - let namedArgList, newtypes, forwardRef = - recursivelyTransformNamedArgsForMake mapper - (modifiedBindingOld binding) - [] [] - in - let namedArgListWithKeyAndRef = - ( optional "key", - None, - Pat.var {txt = "key"; loc = emptyLoc}, - "key", - emptyLoc, - Some (keyType emptyLoc) ) - :: namedArgList - in - let namedArgListWithKeyAndRef = - match forwardRef with - | Some _ -> - ( optional "ref", - None, - Pat.var {txt = "key"; loc = emptyLoc}, - "ref", - emptyLoc, - None ) - :: namedArgListWithKeyAndRef - | None -> namedArgListWithKeyAndRef - in - let namedArgListWithKeyAndRefForNew = - match forwardRef with - | Some txt -> - namedArgList - @ [ - ( nolabel, - None, - Pat.var {txt; loc = emptyLoc}, - txt, - emptyLoc, - None ); - ] - | None -> namedArgList - in - let pluckArg (label, _, _, alias, loc, _) = - let labelString = - match label with - | label when isOptional label || isLabelled label -> - getLabel label - | _ -> "" - in - ( label, - match labelString with - | "" -> Exp.ident ~loc {txt = Lident alias; loc} - | labelString -> - Exp.apply ~loc - (Exp.ident ~loc {txt = Lident "##"; loc}) - [ - (nolabel, Exp.ident ~loc {txt = Lident props.propsName; loc}); - (nolabel, Exp.ident ~loc {txt = Lident labelString; loc}); - ] ) - in - let namedTypeList = List.fold_left argToType [] namedArgList in - let loc = emptyLoc in - let externalArgs = - (* translate newtypes to type variables *) - List.fold_left - (fun args newtype -> - List.map - (fun (a, b, c, d, e, maybeTyp) -> - match maybeTyp with - | Some typ -> - (a, b, c, d, e, Some (newtypeToVar newtype.txt typ)) - | None -> (a, b, c, d, e, None)) - args) - namedArgListWithKeyAndRef newtypes - in - let externalTypes = - (* translate newtypes to type variables *) - List.fold_left - (fun args newtype -> - List.map - (fun (a, b, typ) -> (a, b, newtypeToVar newtype.txt typ)) - args) - namedTypeList newtypes - in - let externalDecl = - makeExternalDecl fnName loc externalArgs externalTypes - in - let innerExpressionArgs = - List.map pluckArg namedArgListWithKeyAndRefForNew - @ - if hasUnit then - [(Nolabel, Exp.construct {loc; txt = Lident "()"} None)] - else [] - in - let innerExpression = - Exp.apply - (Exp.ident - { - loc; - txt = - Lident - (match recFlag with - | Recursive -> internalFnName - | Nonrecursive -> fnName); - }) - innerExpressionArgs - in - let innerExpressionWithRef = - match forwardRef with - | Some txt -> - { - innerExpression with - pexp_desc = - Pexp_fun - ( nolabel, - None, - { - ppat_desc = Ppat_var {txt; loc = emptyLoc}; - ppat_loc = emptyLoc; - ppat_attributes = []; - }, - innerExpression ); - } - | None -> innerExpression - in - let fullExpression = - Exp.fun_ nolabel None - { - ppat_desc = - Ppat_constraint - ( makePropsName ~loc:emptyLoc props.propsName, - makePropsType ~loc:emptyLoc externalTypes ); - ppat_loc = emptyLoc; - ppat_attributes = []; - } - innerExpressionWithRef - in - let fullExpression = - match fullModuleName with - | "" -> fullExpression - | txt -> - Exp.let_ Nonrecursive - [ - Vb.mk ~loc:emptyLoc - (Pat.var ~loc:emptyLoc {loc = emptyLoc; txt}) - fullExpression; - ] - (Exp.ident ~loc:emptyLoc {loc = emptyLoc; txt = Lident txt}) - in - let bindings, newBinding = - match recFlag with - | Recursive -> - ( [ - bindingWrapper - (Exp.let_ ~loc:emptyLoc Recursive - [ - makeNewBinding binding expression internalFnName; - Vb.mk - (Pat.var {loc = emptyLoc; txt = fnName}) - fullExpression; - ] - (Exp.ident {loc = emptyLoc; txt = Lident fnName})); - ], - None ) - | Nonrecursive -> - ( [{binding with pvb_expr = expression; pvb_attributes = []}], - Some (bindingWrapper fullExpression) ) - in - (Some externalDecl, bindings, newBinding) - else (None, [binding], None) - [@@raises Invalid_argument] - in - let structuresAndBinding = List.map mapBinding valueBindings in - let otherStructures (extern, binding, newBinding) - (externs, bindings, newBindings) = - let externs = - match extern with - | Some extern -> extern :: externs - | None -> externs - in - let newBindings = - match newBinding with - | Some newBinding -> newBinding :: newBindings - | None -> newBindings - in - (externs, binding @ bindings, newBindings) - in - let externs, bindings, newBindings = - List.fold_right otherStructures structuresAndBinding ([], [], []) - in - externs - @ [{pstr_loc; pstr_desc = Pstr_value (recFlag, bindings)}] - @ (match newBindings with - | [] -> [] - | newBindings -> - [{pstr_loc = emptyLoc; pstr_desc = Pstr_value (recFlag, newBindings)}]) - @ returnStructures - | structure -> structure :: returnStructures - [@@raises Invalid_argument] - in - - let reactComponentTransform mapper structures = - List.fold_right (transformComponentDefinition mapper) structures [] - [@@raises Invalid_argument] - in - - let transformComponentSignature _mapper signature returnSignatures = - match signature with - | { - psig_loc; - psig_desc = - Psig_value - ({pval_name = {txt = fnName}; pval_attributes; pval_type} as - psig_desc); - } as psig -> ( - match List.filter hasAttr pval_attributes with - | [] -> signature :: returnSignatures - | [_] -> - let rec getPropTypes types ({ptyp_loc; ptyp_desc} as fullType) = - match ptyp_desc with - | Ptyp_arrow (name, type_, ({ptyp_desc = Ptyp_arrow _} as rest)) - when isOptional name || isLabelled name -> - getPropTypes ((name, ptyp_loc, type_) :: types) rest - | Ptyp_arrow (Nolabel, _type, rest) -> getPropTypes types rest - | Ptyp_arrow (name, type_, returnValue) - when isOptional name || isLabelled name -> - (returnValue, (name, returnValue.ptyp_loc, type_) :: types) - | _ -> (fullType, types) - in - let innerType, propTypes = getPropTypes [] pval_type in - let namedTypeList = List.fold_left argToConcreteType [] propTypes in - let pluckLabelAndLoc (label, loc, type_) = - (label, None, loc, Some type_) - in - let retPropsType = makePropsType ~loc:psig_loc namedTypeList in - let externalPropsDecl = - makePropsExternalSig fnName psig_loc - ((optional "key", None, psig_loc, Some (keyType psig_loc)) - :: List.map pluckLabelAndLoc propTypes) - retPropsType - in - (* can't be an arrow because it will defensively uncurry *) - let newExternalType = - Ptyp_constr - ( {loc = psig_loc; txt = Ldot (Lident "React", "componentLike")}, - [retPropsType; innerType] ) - in - let newStructure = - { - psig with - psig_desc = - Psig_value - { - psig_desc with - pval_type = {pval_type with ptyp_desc = newExternalType}; - pval_attributes = List.filter otherAttrsPure pval_attributes; - }; - } - in - externalPropsDecl :: newStructure :: returnSignatures - | _ -> - raise - (Invalid_argument - "Only one react.component call can exist on a component at one \ - time")) - | signature -> signature :: returnSignatures - [@@raises Invalid_argument] - in - - let reactComponentSignatureTransform mapper signatures = - List.fold_right (transformComponentSignature mapper) signatures [] - [@@raises Invalid_argument] - in - - let transformJsxCall mapper callExpression callArguments attrs = - match callExpression.pexp_desc with - | Pexp_ident caller -> ( - match caller with - | {txt = Lident "createElement"} -> - raise - (Invalid_argument - "JSX: `createElement` should be preceeded by a module name.") - (* Foo.createElement(~prop1=foo, ~prop2=bar, ~children=[], ()) *) - | {loc; txt = Ldot (modulePath, ("createElement" | "make"))} -> ( - match !jsxVersion with - | None | Some 3 -> - transformUppercaseCall3 modulePath mapper loc attrs callExpression - callArguments - | Some _ -> raise (Invalid_argument "JSX: the JSX version must be 3")) - (* div(~prop1=foo, ~prop2=bar, ~children=[bla], ()) *) - (* turn that into - ReactDOMRe.createElement(~props=ReactDOMRe.props(~props1=foo, ~props2=bar, ()), [|bla|]) *) - | {loc; txt = Lident id} -> ( - match !jsxVersion with - | None | Some 3 -> - transformLowercaseCall3 mapper loc attrs callArguments id - | Some _ -> raise (Invalid_argument "JSX: the JSX version must be 3")) - | {txt = Ldot (_, anythingNotCreateElementOrMake)} -> - raise - (Invalid_argument - ("JSX: the JSX attribute should be attached to a \ - `YourModuleName.createElement` or `YourModuleName.make` call. \ - We saw `" ^ anythingNotCreateElementOrMake ^ "` instead")) - | {txt = Lapply _} -> - (* don't think there's ever a case where this is reached *) - raise - (Invalid_argument - "JSX: encountered a weird case while processing the code. Please \ - report this!")) - | _ -> - raise - (Invalid_argument - "JSX: `createElement` should be preceeded by a simple, direct \ - module name.") - [@@raises Invalid_argument] - in - - let signature mapper signature = - default_mapper.signature mapper - @@ reactComponentSignatureTransform mapper signature - [@@raises Invalid_argument] - in - - let structure mapper structure = - match structure with - | structures -> - default_mapper.structure mapper - @@ reactComponentTransform mapper structures - [@@raises Invalid_argument] - in - - let expr mapper expression = - match expression with - (* Does the function application have the @JSX attribute? *) - | {pexp_desc = Pexp_apply (callExpression, callArguments); pexp_attributes} - -> ( - let jsxAttribute, nonJSXAttributes = - List.partition - (fun (attribute, _) -> attribute.txt = "JSX") - pexp_attributes - in - match (jsxAttribute, nonJSXAttributes) with - (* no JSX attribute *) - | [], _ -> default_mapper.expr mapper expression - | _, nonJSXAttributes -> - transformJsxCall mapper callExpression callArguments nonJSXAttributes) - (* is it a list with jsx attribute? Reason <>foo desugars to [@JSX][foo]*) - | { - pexp_desc = - ( Pexp_construct - ({txt = Lident "::"; loc}, Some {pexp_desc = Pexp_tuple _}) - | Pexp_construct ({txt = Lident "[]"; loc}, None) ); - pexp_attributes; - } as listItems -> ( - let jsxAttribute, nonJSXAttributes = - List.partition - (fun (attribute, _) -> attribute.txt = "JSX") - pexp_attributes - in - match (jsxAttribute, nonJSXAttributes) with - (* no JSX attribute *) - | [], _ -> default_mapper.expr mapper expression - | _, nonJSXAttributes -> - let loc = {loc with loc_ghost = true} in - let fragment = - Exp.ident ~loc {loc; txt = Ldot (Lident "ReasonReact", "fragment")} - in - let childrenExpr = transformChildrenIfList ~loc ~mapper listItems in - let args = - [ - (* "div" *) - (nolabel, fragment); - (* [|moreCreateElementCallsHere|] *) - (nolabel, childrenExpr); - ] - in - Exp.apply - ~loc (* throw away the [@JSX] attribute and keep the others, if any *) - ~attrs:nonJSXAttributes - (* ReactDOMRe.createElement *) - (Exp.ident ~loc - {loc; txt = Ldot (Lident "ReactDOMRe", "createElement")}) - args) - (* Delegate to the default mapper, a deep identity traversal *) - | e -> default_mapper.expr mapper e - [@@raises Invalid_argument] - in - - let module_binding mapper module_binding = - let _ = nestedModules := module_binding.pmb_name.txt :: !nestedModules in - let mapped = default_mapper.module_binding mapper module_binding in - let _ = nestedModules := List.tl !nestedModules in - mapped - [@@raises Failure] - in - {default_mapper with structure; expr; signature; module_binding} - [@@raises Invalid_argument, Failure] - -let rewrite_implementation (code : Parsetree.structure) : Parsetree.structure = - let mapper = jsxMapper () in - mapper.structure mapper code - [@@raises Invalid_argument, Failure] - -let rewrite_signature (code : Parsetree.signature) : Parsetree.signature = - let mapper = jsxMapper () in - mapper.signature mapper code - [@@raises Invalid_argument, Failure] diff --git a/cli/reactjs_jsx_ppx_v3.mli b/cli/reactjs_jsx_ppx_v3.mli deleted file mode 100644 index da60a051..00000000 --- a/cli/reactjs_jsx_ppx_v3.mli +++ /dev/null @@ -1,39 +0,0 @@ -(* - This is the module that handles turning Reason JSX' agnostic function call into - a ReasonReact-specific function call. Aka, this is a macro, using OCaml's ppx - facilities; https://whitequark.org/blog/2014/04/16/a-guide-to-extension- - points-in-ocaml/ - You wouldn't use this file directly; it's used by ReScript's - bsconfig.json. Specifically, there's a field called `react-jsx` inside the - field `reason`, which enables this ppx through some internal call in bsb -*) - -(* - There are two different transforms that can be selected in this file (v2 and v3): - v2: - transform `[@JSX] div(~props1=a, ~props2=b, ~children=[foo, bar], ())` into - `ReactDOMRe.createElement("div", ~props={"props1": 1, "props2": b}, [|foo, - bar|])`. - transform `[@JSX] div(~props1=a, ~props2=b, ~children=foo, ())` into - `ReactDOMRe.createElementVariadic("div", ~props={"props1": 1, "props2": b}, foo)`. - transform the upper-cased case - `[@JSX] Foo.createElement(~key=a, ~ref=b, ~foo=bar, ~children=[], ())` into - `ReasonReact.element(~key=a, ~ref=b, Foo.make(~foo=bar, [||]))` - transform `[@JSX] [foo]` into - `ReactDOMRe.createElement(ReasonReact.fragment, [|foo|])` - v3: - transform `[@JSX] div(~props1=a, ~props2=b, ~children=[foo, bar], ())` into - `ReactDOMRe.createDOMElementVariadic("div", ReactDOMRe.domProps(~props1=1, ~props2=b), [|foo, bar|])`. - transform the upper-cased case - `[@JSX] Foo.createElement(~key=a, ~ref=b, ~foo=bar, ~children=[], ())` into - `React.createElement(Foo.make, Foo.makeProps(~key=a, ~ref=b, ~foo=bar, ()))` - transform the upper-cased case - `[@JSX] Foo.createElement(~foo=bar, ~children=[foo, bar], ())` into - `React.createElementVariadic(Foo.make, Foo.makeProps(~foo=bar, ~children=React.null, ()), [|foo, bar|])` - transform `[@JSX] [foo]` into - `ReactDOMRe.createElement(ReasonReact.fragment, [|foo|])` -*) - -val rewrite_implementation : Parsetree.structure -> Parsetree.structure - -val rewrite_signature : Parsetree.signature -> Parsetree.signature From 44dd62549ec2c69aaa1e67aa19f09f15c27c3eac Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Thu, 7 Jul 2022 16:37:45 +0200 Subject: [PATCH 34/94] rename --- cli/reactjs_jsx_ppx.ml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index 5badb66f..0de7881d 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -1298,13 +1298,13 @@ module V3 = struct {default_mapper with structure; expr; signature; module_binding} [@@raises Invalid_argument, Failure] - let rewrite_implementation (code : Parsetree.structure) : Parsetree.structure - = + let rewrite_implementationV3 (code : Parsetree.structure) : + Parsetree.structure = let mapper = jsxMapper () in mapper.structure mapper code [@@raises Invalid_argument, Failure] - let rewrite_signature (code : Parsetree.signature) : Parsetree.signature = + let rewrite_signatureV3 (code : Parsetree.signature) : Parsetree.signature = let mapper = jsxMapper () in mapper.signature mapper code [@@raises Invalid_argument, Failure] @@ -2779,7 +2779,7 @@ module V4 = struct } [@@raises Invalid_argument, Failure] - let rewrite_implementation ~jsxMode (code : Parsetree.structure) : + let rewrite_implementationV4 ~jsxMode (code : Parsetree.structure) : Parsetree.structure = let nestedModules = ref [] in let config = {mode = jsxMode; module_ = ""; version = 4} in @@ -2787,7 +2787,7 @@ module V4 = struct mapper.structure mapper code [@@raises Invalid_argument, Failure] - let rewrite_signature ~jsxMode (code : Parsetree.signature) : + let rewrite_signatureV4 ~jsxMode (code : Parsetree.signature) : Parsetree.signature = let nestedModules = ref [] in let config = {mode = jsxMode; module_ = ""; version = 4} in @@ -2799,17 +2799,17 @@ end let rewrite_implementation ~jsxVersion ~jsxModule ~jsxMode (code : Parsetree.structure) : Parsetree.structure = match (jsxVersion, jsxModule, jsxMode) with - | 3, _, _ -> V3.rewrite_implementation code - | 4, _, "classic" -> V4.rewrite_implementation ~jsxMode code - | 4, _, "automatic" -> V4.rewrite_implementation ~jsxMode code + | 3, _, _ -> V3.rewrite_implementationV3 code + | 4, _, "classic" -> V4.rewrite_implementationV4 ~jsxMode code + | 4, _, "automatic" -> V4.rewrite_implementationV4 ~jsxMode code | _ -> code [@@raises Invalid_argument, Failure] let rewrite_signature ~jsxVersion ~jsxModule ~jsxMode (code : Parsetree.signature) : Parsetree.signature = match (jsxVersion, jsxModule, jsxMode) with - | 3, _, _ -> V3.rewrite_signature code - | 4, _, "classic" -> V4.rewrite_signature ~jsxMode code - | 4, _, "automatic" -> V4.rewrite_signature ~jsxMode code + | 3, _, _ -> V3.rewrite_signatureV3 code + | 4, _, "classic" -> V4.rewrite_signatureV4 ~jsxMode code + | 4, _, "automatic" -> V4.rewrite_signatureV4 ~jsxMode code | _ -> code [@@raises Invalid_argument, Failure] From 017c9de587b28e9b049334638c4d1376de8d7f69 Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Thu, 7 Jul 2022 16:39:48 +0200 Subject: [PATCH 35/94] Bring toplevel out. --- cli/reactjs_jsx_ppx.ml | 73 ++++++++++++++++++------------------------ 1 file changed, 32 insertions(+), 41 deletions(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index 0de7881d..beab7650 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -1,12 +1,16 @@ +open Ast_helper +open Ast_mapper +open Asttypes open Parsetree +open Longident -module V3 = struct - open Ast_helper - open Ast_mapper - open Asttypes - open Parsetree - open Longident +type jsxConfig = { + mutable version: int; + mutable module_: string; + mutable mode: string; +} +module V3 = struct let rec find_opt p = function | [] -> None | x :: l -> if p x then Some x else find_opt p l @@ -1297,26 +1301,19 @@ module V3 = struct in {default_mapper with structure; expr; signature; module_binding} [@@raises Invalid_argument, Failure] - - let rewrite_implementationV3 (code : Parsetree.structure) : - Parsetree.structure = - let mapper = jsxMapper () in - mapper.structure mapper code - [@@raises Invalid_argument, Failure] - - let rewrite_signatureV3 (code : Parsetree.signature) : Parsetree.signature = - let mapper = jsxMapper () in - mapper.signature mapper code - [@@raises Invalid_argument, Failure] end +let rewrite_implementationV3 (code : Parsetree.structure) : Parsetree.structure + = + let mapper = V3.jsxMapper () in + mapper.structure mapper code + [@@raises Invalid_argument, Failure] -module V4 = struct - open Ast_helper - open Ast_mapper - open Asttypes - open Parsetree - open Longident +let rewrite_signatureV3 (code : Parsetree.signature) : Parsetree.signature = + let mapper = V3.jsxMapper () in + mapper.signature mapper code + [@@raises Invalid_argument, Failure] +module V4 = struct let getJsxConfig payload = match payload with | PStr @@ -1359,12 +1356,6 @@ module V4 = struct let getString ~key fields = fields |> getJsxConfigByKey ~key ~type_:String - type jsxConfig = { - mutable version: int; - mutable module_: string; - mutable mode: string; - } - let updateConfig config payload = let fields = getJsxConfig payload in (match getInt ~key:"version" fields with @@ -2786,20 +2777,20 @@ module V4 = struct let mapper = jsxMapper ~config nestedModules in mapper.structure mapper code [@@raises Invalid_argument, Failure] - - let rewrite_signatureV4 ~jsxMode (code : Parsetree.signature) : - Parsetree.signature = - let nestedModules = ref [] in - let config = {mode = jsxMode; module_ = ""; version = 4} in - let mapper = jsxMapper ~config nestedModules in - mapper.signature mapper code - [@@raises Invalid_argument, Failure] end +let rewrite_signatureV4 ~jsxMode (code : Parsetree.signature) : + Parsetree.signature = + let nestedModules = ref [] in + let config = {mode = jsxMode; module_ = ""; version = 4} in + let mapper = V4.jsxMapper ~config nestedModules in + mapper.signature mapper code + [@@raises Invalid_argument, Failure] + let rewrite_implementation ~jsxVersion ~jsxModule ~jsxMode (code : Parsetree.structure) : Parsetree.structure = match (jsxVersion, jsxModule, jsxMode) with - | 3, _, _ -> V3.rewrite_implementationV3 code + | 3, _, _ -> rewrite_implementationV3 code | 4, _, "classic" -> V4.rewrite_implementationV4 ~jsxMode code | 4, _, "automatic" -> V4.rewrite_implementationV4 ~jsxMode code | _ -> code @@ -2808,8 +2799,8 @@ let rewrite_implementation ~jsxVersion ~jsxModule ~jsxMode let rewrite_signature ~jsxVersion ~jsxModule ~jsxMode (code : Parsetree.signature) : Parsetree.signature = match (jsxVersion, jsxModule, jsxMode) with - | 3, _, _ -> V3.rewrite_signatureV3 code - | 4, _, "classic" -> V4.rewrite_signatureV4 ~jsxMode code - | 4, _, "automatic" -> V4.rewrite_signatureV4 ~jsxMode code + | 3, _, _ -> rewrite_signatureV3 code + | 4, _, "classic" -> rewrite_signatureV4 ~jsxMode code + | 4, _, "automatic" -> rewrite_signatureV4 ~jsxMode code | _ -> code [@@raises Invalid_argument, Failure] From 4d41ef0a91edea55bc0a64548ebb86060d81f8bc Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Thu, 7 Jul 2022 16:40:46 +0200 Subject: [PATCH 36/94] Move out more. --- cli/reactjs_jsx_ppx.ml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index beab7650..84e50573 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -2769,14 +2769,6 @@ module V4 = struct structure_item; } [@@raises Invalid_argument, Failure] - - let rewrite_implementationV4 ~jsxMode (code : Parsetree.structure) : - Parsetree.structure = - let nestedModules = ref [] in - let config = {mode = jsxMode; module_ = ""; version = 4} in - let mapper = jsxMapper ~config nestedModules in - mapper.structure mapper code - [@@raises Invalid_argument, Failure] end let rewrite_signatureV4 ~jsxMode (code : Parsetree.signature) : @@ -2787,12 +2779,20 @@ let rewrite_signatureV4 ~jsxMode (code : Parsetree.signature) : mapper.signature mapper code [@@raises Invalid_argument, Failure] +let rewrite_implementationV4 ~jsxMode (code : Parsetree.structure) : + Parsetree.structure = + let nestedModules = ref [] in + let config = {mode = jsxMode; module_ = ""; version = 4} in + let mapper = V4.jsxMapper ~config nestedModules in + mapper.structure mapper code + [@@raises Invalid_argument, Failure] + let rewrite_implementation ~jsxVersion ~jsxModule ~jsxMode (code : Parsetree.structure) : Parsetree.structure = match (jsxVersion, jsxModule, jsxMode) with | 3, _, _ -> rewrite_implementationV3 code - | 4, _, "classic" -> V4.rewrite_implementationV4 ~jsxMode code - | 4, _, "automatic" -> V4.rewrite_implementationV4 ~jsxMode code + | 4, _, "classic" -> rewrite_implementationV4 ~jsxMode code + | 4, _, "automatic" -> rewrite_implementationV4 ~jsxMode code | _ -> code [@@raises Invalid_argument, Failure] From cec6e7a25d41506d5587c97c0739b453f4eed31a Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Thu, 7 Jul 2022 16:42:40 +0200 Subject: [PATCH 37/94] Create config early. --- cli/reactjs_jsx_ppx.ml | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index 84e50573..c7d53f91 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -2771,36 +2771,37 @@ module V4 = struct [@@raises Invalid_argument, Failure] end -let rewrite_signatureV4 ~jsxMode (code : Parsetree.signature) : +let rewrite_signatureV4 ~config (code : Parsetree.signature) : Parsetree.signature = let nestedModules = ref [] in - let config = {mode = jsxMode; module_ = ""; version = 4} in let mapper = V4.jsxMapper ~config nestedModules in mapper.signature mapper code [@@raises Invalid_argument, Failure] -let rewrite_implementationV4 ~jsxMode (code : Parsetree.structure) : +let rewrite_implementationV4 ~config (code : Parsetree.structure) : Parsetree.structure = let nestedModules = ref [] in - let config = {mode = jsxMode; module_ = ""; version = 4} in let mapper = V4.jsxMapper ~config nestedModules in mapper.structure mapper code [@@raises Invalid_argument, Failure] let rewrite_implementation ~jsxVersion ~jsxModule ~jsxMode (code : Parsetree.structure) : Parsetree.structure = + let config = {mode = jsxMode; module_ = ""; version = jsxVersion} in + match (jsxVersion, jsxModule, jsxMode) with | 3, _, _ -> rewrite_implementationV3 code - | 4, _, "classic" -> rewrite_implementationV4 ~jsxMode code - | 4, _, "automatic" -> rewrite_implementationV4 ~jsxMode code + | 4, _, "classic" -> rewrite_implementationV4 ~config code + | 4, _, "automatic" -> rewrite_implementationV4 ~config code | _ -> code [@@raises Invalid_argument, Failure] let rewrite_signature ~jsxVersion ~jsxModule ~jsxMode (code : Parsetree.signature) : Parsetree.signature = + let config = {mode = jsxMode; module_ = ""; version = jsxVersion} in match (jsxVersion, jsxModule, jsxMode) with | 3, _, _ -> rewrite_signatureV3 code - | 4, _, "classic" -> rewrite_signatureV4 ~jsxMode code - | 4, _, "automatic" -> rewrite_signatureV4 ~jsxMode code + | 4, _, "classic" -> rewrite_signatureV4 ~config code + | 4, _, "automatic" -> rewrite_signatureV4 ~config code | _ -> code [@@raises Invalid_argument, Failure] From 880bf0467f22065403ee422b97e31ec091776036 Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Thu, 7 Jul 2022 16:47:24 +0200 Subject: [PATCH 38/94] Move config creation outside the module. --- cli/reactjs_jsx_ppx.ml | 27 ++++++++++++--------------- cli/reactjs_jsx_ppx.mli | 18 ++++++++---------- cli/res_cli.ml | 9 +++++---- 3 files changed, 25 insertions(+), 29 deletions(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index c7d53f91..3ed5f626 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -2785,23 +2785,20 @@ let rewrite_implementationV4 ~config (code : Parsetree.structure) : mapper.structure mapper code [@@raises Invalid_argument, Failure] -let rewrite_implementation ~jsxVersion ~jsxModule ~jsxMode - (code : Parsetree.structure) : Parsetree.structure = - let config = {mode = jsxMode; module_ = ""; version = jsxVersion} in - - match (jsxVersion, jsxModule, jsxMode) with - | 3, _, _ -> rewrite_implementationV3 code - | 4, _, "classic" -> rewrite_implementationV4 ~config code - | 4, _, "automatic" -> rewrite_implementationV4 ~config code +let rewrite_implementation ~config (code : Parsetree.structure) : + Parsetree.structure = + match (config.version, config.mode) with + | 3, _ -> rewrite_implementationV3 code + | 4, "classic" -> rewrite_implementationV4 ~config code + | 4, "automatic" -> rewrite_implementationV4 ~config code | _ -> code [@@raises Invalid_argument, Failure] -let rewrite_signature ~jsxVersion ~jsxModule ~jsxMode - (code : Parsetree.signature) : Parsetree.signature = - let config = {mode = jsxMode; module_ = ""; version = jsxVersion} in - match (jsxVersion, jsxModule, jsxMode) with - | 3, _, _ -> rewrite_signatureV3 code - | 4, _, "classic" -> rewrite_signatureV4 ~config code - | 4, _, "automatic" -> rewrite_signatureV4 ~config code +let rewrite_signature ~config (code : Parsetree.signature) : Parsetree.signature + = + match (config.version, config.mode) with + | 3, _ -> rewrite_signatureV3 code + | 4, "classic" -> rewrite_signatureV4 ~config code + | 4, "automatic" -> rewrite_signatureV4 ~config code | _ -> code [@@raises Invalid_argument, Failure] diff --git a/cli/reactjs_jsx_ppx.mli b/cli/reactjs_jsx_ppx.mli index 388202ed..d267ca7a 100644 --- a/cli/reactjs_jsx_ppx.mli +++ b/cli/reactjs_jsx_ppx.mli @@ -8,16 +8,14 @@ field `reason`, which enables this ppx through some internal call in bsb *) +type jsxConfig = { + mutable version: int; + mutable module_: string; + mutable mode: string; +} + val rewrite_implementation : - jsxVersion:int -> - jsxModule:string -> - jsxMode:string -> - Parsetree.structure -> - Parsetree.structure + config:jsxConfig -> Parsetree.structure -> Parsetree.structure val rewrite_signature : - jsxVersion:int -> - jsxModule:string -> - jsxMode:string -> - Parsetree.signature -> - Parsetree.signature + config:jsxConfig -> Parsetree.signature -> Parsetree.signature diff --git a/cli/res_cli.ml b/cli/res_cli.ml index ba04d685..0fcb7da0 100644 --- a/cli/res_cli.ml +++ b/cli/res_cli.ml @@ -275,6 +275,9 @@ module CliArgProcessor = struct let (Parser backend) = parsingEngine in (* This is the whole purpose of the Color module above *) Color.setup None; + let config : Reactjs_jsx_ppx.jsxConfig = + {version = jsxVersion; module_ = jsxModule; mode = jsxMode} + in if processInterface then let parseResult = backend.parseInterface ~forPrinter ~filename in if parseResult.invalid then ( @@ -286,8 +289,7 @@ module CliArgProcessor = struct else exit 1) else let parsetree = - Reactjs_jsx_ppx.rewrite_signature ~jsxVersion ~jsxModule ~jsxMode - parseResult.parsetree + Reactjs_jsx_ppx.rewrite_signature ~config parseResult.parsetree in printEngine.printInterface ~width ~filename ~comments:parseResult.comments parsetree @@ -302,8 +304,7 @@ module CliArgProcessor = struct else exit 1) else let parsetree = - Reactjs_jsx_ppx.rewrite_implementation ~jsxVersion ~jsxModule ~jsxMode - parseResult.parsetree + Reactjs_jsx_ppx.rewrite_implementation ~config parseResult.parsetree in printEngine.printImplementation ~width ~filename ~comments:parseResult.comments parsetree From c86b7560b048d0facd95bfedfcb3d710842d71e5 Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Thu, 7 Jul 2022 16:48:19 +0200 Subject: [PATCH 39/94] simplify toplevel --- cli/reactjs_jsx_ppx.ml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index 3ed5f626..96cd0fd3 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -2796,9 +2796,8 @@ let rewrite_implementation ~config (code : Parsetree.structure) : let rewrite_signature ~config (code : Parsetree.signature) : Parsetree.signature = - match (config.version, config.mode) with - | 3, _ -> rewrite_signatureV3 code - | 4, "classic" -> rewrite_signatureV4 ~config code - | 4, "automatic" -> rewrite_signatureV4 ~config code + match config.version with + | 3 -> rewrite_signatureV3 code + | 4 -> rewrite_signatureV4 ~config code | _ -> code [@@raises Invalid_argument, Failure] From 333bbe936d06724b204d11c6ff109bc308c7a52f Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Thu, 7 Jul 2022 16:49:28 +0200 Subject: [PATCH 40/94] Same for implementation. --- cli/reactjs_jsx_ppx.ml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index 96cd0fd3..33082fcf 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -2787,10 +2787,9 @@ let rewrite_implementationV4 ~config (code : Parsetree.structure) : let rewrite_implementation ~config (code : Parsetree.structure) : Parsetree.structure = - match (config.version, config.mode) with - | 3, _ -> rewrite_implementationV3 code - | 4, "classic" -> rewrite_implementationV4 ~config code - | 4, "automatic" -> rewrite_implementationV4 ~config code + match config.version with + | 3 -> rewrite_implementationV3 code + | 4 -> rewrite_implementationV4 ~config code | _ -> code [@@raises Invalid_argument, Failure] From 208c464fbeedc02f60c0356dd97904bbee719f08 Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Thu, 7 Jul 2022 17:02:51 +0200 Subject: [PATCH 41/94] Unify V3 and V4 at top level. --- cli/reactjs_jsx_ppx.ml | 46 ++++++++++++++---------------------------- 1 file changed, 15 insertions(+), 31 deletions(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index 33082fcf..34b389df 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -1302,16 +1302,6 @@ module V3 = struct {default_mapper with structure; expr; signature; module_binding} [@@raises Invalid_argument, Failure] end -let rewrite_implementationV3 (code : Parsetree.structure) : Parsetree.structure - = - let mapper = V3.jsxMapper () in - mapper.structure mapper code - [@@raises Invalid_argument, Failure] - -let rewrite_signatureV3 (code : Parsetree.signature) : Parsetree.signature = - let mapper = V3.jsxMapper () in - mapper.signature mapper code - [@@raises Invalid_argument, Failure] module V4 = struct let getJsxConfig payload = @@ -2771,32 +2761,26 @@ module V4 = struct [@@raises Invalid_argument, Failure] end -let rewrite_signatureV4 ~config (code : Parsetree.signature) : - Parsetree.signature = - let nestedModules = ref [] in - let mapper = V4.jsxMapper ~config nestedModules in - mapper.signature mapper code - [@@raises Invalid_argument, Failure] - -let rewrite_implementationV4 ~config (code : Parsetree.structure) : +let rewrite_implementation ~config (code : Parsetree.structure) : Parsetree.structure = let nestedModules = ref [] in - let mapper = V4.jsxMapper ~config nestedModules in + let mapper = + match config.version with + | 3 -> V3.jsxMapper () + | 4 -> V4.jsxMapper ~config nestedModules + | _ -> default_mapper + in mapper.structure mapper code [@@raises Invalid_argument, Failure] -let rewrite_implementation ~config (code : Parsetree.structure) : - Parsetree.structure = - match config.version with - | 3 -> rewrite_implementationV3 code - | 4 -> rewrite_implementationV4 ~config code - | _ -> code - [@@raises Invalid_argument, Failure] - let rewrite_signature ~config (code : Parsetree.signature) : Parsetree.signature = - match config.version with - | 3 -> rewrite_signatureV3 code - | 4 -> rewrite_signatureV4 ~config code - | _ -> code + let mapper = + let nestedModules = ref [] in + match config.version with + | 3 -> V3.jsxMapper () + | 4 -> V4.jsxMapper ~config nestedModules + | _ -> default_mapper + in + mapper.signature mapper code [@@raises Invalid_argument, Failure] From 362ea6693dc29c224e92acb697c1f21ce0714031 Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Thu, 7 Jul 2022 17:09:55 +0200 Subject: [PATCH 42/94] Include nested modules in config. --- cli/reactjs_jsx_ppx.ml | 58 ++++++++++++++++++----------------------- cli/reactjs_jsx_ppx.mli | 1 + cli/res_cli.ml | 7 ++++- 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index 34b389df..1f5b21f6 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -8,6 +8,7 @@ type jsxConfig = { mutable version: int; mutable module_: string; mutable mode: string; + mutable nestedModules: string list; } module V3 = struct @@ -368,9 +369,7 @@ module V3 = struct mapper.typ mapper type_ (* TODO: some line number might still be wrong *) - let jsxMapper () = - let jsxVersion = ref None in - + let jsxMapper ~config = let transformUppercaseCall3 modulePath mapper loc attrs _ callArguments = let children, argsWithLabels = extractChildren ~loc ~removeLastPositionUnit:true callArguments @@ -1184,20 +1183,18 @@ module V3 = struct "JSX: `createElement` should be preceeded by a module name.") (* Foo.createElement(~prop1=foo, ~prop2=bar, ~children=[], ()) *) | {loc; txt = Ldot (modulePath, ("createElement" | "make"))} -> ( - match !jsxVersion with - | None | Some 3 -> + match config.version with + | 3 -> transformUppercaseCall3 modulePath mapper loc attrs callExpression callArguments - | Some _ -> raise (Invalid_argument "JSX: the JSX version must be 3") - ) + | _ -> raise (Invalid_argument "JSX: the JSX version must be 3")) (* div(~prop1=foo, ~prop2=bar, ~children=[bla], ()) *) (* turn that into ReactDOMRe.createElement(~props=ReactDOMRe.props(~props1=foo, ~props2=bar, ()), [|bla|]) *) | {loc; txt = Lident id} -> ( - match !jsxVersion with - | None | Some 3 -> - transformLowercaseCall3 mapper loc attrs callArguments id - | Some _ -> raise (Invalid_argument "JSX: the JSX version must be 3")) + match config.version with + | 3 -> transformLowercaseCall3 mapper loc attrs callArguments id + | _ -> raise (Invalid_argument "JSX: the JSX version must be 3")) | {txt = Ldot (_, anythingNotCreateElementOrMake)} -> raise (Invalid_argument @@ -2058,8 +2055,7 @@ module V4 = struct | name when isOptional name -> (true, getLabel name, [], type_) :: types | _ -> types - let transformComponentDefinition nestedModules mapper structure - returnStructures = + let transformComponentDefinition ~config mapper structure returnStructures = match structure with (* external *) | { @@ -2134,7 +2130,9 @@ module V4 = struct in let fnName = getFnName binding.pvb_pat in let internalFnName = fnName ^ "$Internal" in - let fullModuleName = makeModuleName fileName !nestedModules fnName in + let fullModuleName = + makeModuleName fileName config.nestedModules fnName + in let modifiedBindingOld binding = let expression = binding.pvb_expr in (* TODO: there is a long-tail of unsupported features inside of blocks - Pexp_letmodule , Pexp_letexception , Pexp_ifthenelse *) @@ -2522,10 +2520,8 @@ module V4 = struct | structure -> structure :: returnStructures [@@raises Invalid_argument] - let reactComponentTransform nestedModules mapper structures = - List.fold_right - (transformComponentDefinition nestedModules mapper) - structures [] + let reactComponentTransform ~config mapper structures = + List.fold_right (transformComponentDefinition ~config mapper) structures [] [@@raises Invalid_argument] let transformComponentSignature _mapper signature returnSignatures = @@ -2632,11 +2628,11 @@ module V4 = struct @@ reactComponentSignatureTransform mapper signature [@@raises Invalid_argument] - let structureV4 nestedModules mapper items = + let structureV4 ~config mapper items = match items with | items -> default_mapper.structure mapper - @@ reactComponentTransform nestedModules mapper items + @@ reactComponentTransform ~config mapper items [@@raises Invalid_argument] let exprV4 ~config mapper expression = @@ -2723,15 +2719,15 @@ module V4 = struct | e -> default_mapper.expr mapper e [@@raises Invalid_argument] - let module_bindingV4 nestedModules mapper module_binding = - let _ = nestedModules := module_binding.pmb_name.txt :: !nestedModules in + let module_bindingV4 ~config mapper module_binding = + config.nestedModules <- module_binding.pmb_name.txt :: config.nestedModules; let mapped = default_mapper.module_binding mapper module_binding in - let _ = nestedModules := List.tl !nestedModules in + config.nestedModules <- List.tl config.nestedModules; mapped [@@raises Failure] (* TODO: some line number might still be wrong *) - let jsxMapper ~config nestedModules = + let jsxMapper ~config = let structure_item mapper item = (match item.pstr_desc with | Pstr_attribute attr -> processConfigAttribute attr config @@ -2745,9 +2741,9 @@ module V4 = struct default_mapper.signature_item mapper item in - let structure = structureV4 nestedModules in + let structure = structureV4 ~config in let signature = signatureV4 in - let module_binding = module_bindingV4 nestedModules in + let module_binding = module_bindingV4 ~config in let expr = exprV4 ~config in { default_mapper with @@ -2763,11 +2759,10 @@ end let rewrite_implementation ~config (code : Parsetree.structure) : Parsetree.structure = - let nestedModules = ref [] in let mapper = match config.version with - | 3 -> V3.jsxMapper () - | 4 -> V4.jsxMapper ~config nestedModules + | 3 -> V3.jsxMapper ~config + | 4 -> V4.jsxMapper ~config | _ -> default_mapper in mapper.structure mapper code @@ -2776,10 +2771,9 @@ let rewrite_implementation ~config (code : Parsetree.structure) : let rewrite_signature ~config (code : Parsetree.signature) : Parsetree.signature = let mapper = - let nestedModules = ref [] in match config.version with - | 3 -> V3.jsxMapper () - | 4 -> V4.jsxMapper ~config nestedModules + | 3 -> V3.jsxMapper ~config + | 4 -> V4.jsxMapper ~config | _ -> default_mapper in mapper.signature mapper code diff --git a/cli/reactjs_jsx_ppx.mli b/cli/reactjs_jsx_ppx.mli index d267ca7a..58f247ba 100644 --- a/cli/reactjs_jsx_ppx.mli +++ b/cli/reactjs_jsx_ppx.mli @@ -12,6 +12,7 @@ type jsxConfig = { mutable version: int; mutable module_: string; mutable mode: string; + mutable nestedModules: string list; } val rewrite_implementation : diff --git a/cli/res_cli.ml b/cli/res_cli.ml index 0fcb7da0..653c731a 100644 --- a/cli/res_cli.ml +++ b/cli/res_cli.ml @@ -276,7 +276,12 @@ module CliArgProcessor = struct (* This is the whole purpose of the Color module above *) Color.setup None; let config : Reactjs_jsx_ppx.jsxConfig = - {version = jsxVersion; module_ = jsxModule; mode = jsxMode} + { + version = jsxVersion; + module_ = jsxModule; + mode = jsxMode; + nestedModules = []; + } in if processInterface then let parseResult = backend.parseInterface ~forPrinter ~filename in From 4b892aa038a26eabdcce6cc6ee4d3e5438581c06 Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Thu, 7 Jul 2022 17:11:04 +0200 Subject: [PATCH 43/94] Clean up. --- cli/reactjs_jsx_ppx.ml | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index 1f5b21f6..0b91a0ae 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -2757,24 +2757,20 @@ module V4 = struct [@@raises Invalid_argument, Failure] end +let getMapper ~config = + match config.version with + | 3 -> V3.jsxMapper ~config + | 4 -> V4.jsxMapper ~config + | _ -> default_mapper + let rewrite_implementation ~config (code : Parsetree.structure) : Parsetree.structure = - let mapper = - match config.version with - | 3 -> V3.jsxMapper ~config - | 4 -> V4.jsxMapper ~config - | _ -> default_mapper - in + let mapper = getMapper ~config in mapper.structure mapper code [@@raises Invalid_argument, Failure] let rewrite_signature ~config (code : Parsetree.signature) : Parsetree.signature = - let mapper = - match config.version with - | 3 -> V3.jsxMapper ~config - | 4 -> V4.jsxMapper ~config - | _ -> default_mapper - in + let mapper = getMapper ~config in mapper.signature mapper code [@@raises Invalid_argument, Failure] From 72cc0ad6751957b33343457284f6a7333c6e8d80 Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Thu, 7 Jul 2022 17:21:47 +0200 Subject: [PATCH 44/94] Unify getMapper. --- cli/reactjs_jsx_ppx.ml | 54 +++++++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index 0b91a0ae..a81b1cae 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -1215,13 +1215,13 @@ module V3 = struct [@@raises Invalid_argument] in - let signature mapper signature = + let signatureV3 mapper signature = default_mapper.signature mapper @@ reactComponentSignatureTransform mapper signature [@@raises Invalid_argument] in - let structure mapper structure = + let structureV3 mapper structure = match structure with | structures -> default_mapper.structure mapper @@ -1229,7 +1229,7 @@ module V3 = struct [@@raises Invalid_argument] in - let expr mapper expression = + let exprV3 mapper expression = match expression with (* Does the function application have the @JSX attribute? *) | { @@ -1289,14 +1289,14 @@ module V3 = struct [@@raises Invalid_argument] in - let module_binding mapper module_binding = + let module_bindingV3 mapper module_binding = let _ = nestedModules := module_binding.pmb_name.txt :: !nestedModules in let mapped = default_mapper.module_binding mapper module_binding in let _ = nestedModules := List.tl !nestedModules in mapped [@@raises Failure] in - {default_mapper with structure; expr; signature; module_binding} + (exprV3, module_bindingV3, signatureV3, structureV3) [@@raises Invalid_argument, Failure] end @@ -2728,23 +2728,34 @@ module V4 = struct (* TODO: some line number might still be wrong *) let jsxMapper ~config = - let structure_item mapper item = - (match item.pstr_desc with - | Pstr_attribute attr -> processConfigAttribute attr config - | _ -> ()); - default_mapper.structure_item mapper item - in - let signature_item mapper item = - (match item.psig_desc with - | Psig_attribute attr -> processConfigAttribute attr config - | _ -> ()); - default_mapper.signature_item mapper item - in - let structure = structureV4 ~config in let signature = signatureV4 in let module_binding = module_bindingV4 ~config in let expr = exprV4 ~config in + (expr, module_binding, signature, structure) + [@@raises Invalid_argument, Failure] +end + +let getMapper ~config = + let structure_item mapper item = + (match item.pstr_desc with + | Pstr_attribute attr -> V4.processConfigAttribute attr config + | _ -> ()); + default_mapper.structure_item mapper item + in + let signature_item mapper item = + (match item.psig_desc with + | Psig_attribute attr -> V4.processConfigAttribute attr config + | _ -> ()); + default_mapper.signature_item mapper item + in + + match config.version with + | 3 -> + let expr, module_binding, signature, structure = V3.jsxMapper ~config in + {default_mapper with expr; module_binding; signature; structure} + | 4 -> + let expr, module_binding, signature, structure = V4.jsxMapper ~config in { default_mapper with expr; @@ -2754,13 +2765,6 @@ module V4 = struct structure; structure_item; } - [@@raises Invalid_argument, Failure] -end - -let getMapper ~config = - match config.version with - | 3 -> V3.jsxMapper ~config - | 4 -> V4.jsxMapper ~config | _ -> default_mapper let rewrite_implementation ~config (code : Parsetree.structure) : From 52ccfe4c0653b1b95c7037ea64aea36617ab3638 Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Thu, 7 Jul 2022 17:22:24 +0200 Subject: [PATCH 45/94] Allow V3 to do the switch. --- cli/reactjs_jsx_ppx.ml | 10 +++++++++- tests/ppx/react/expected/fileLevelConfig.res_v3.txt | 12 +----------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index a81b1cae..41e5e84f 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -2753,7 +2753,15 @@ let getMapper ~config = match config.version with | 3 -> let expr, module_binding, signature, structure = V3.jsxMapper ~config in - {default_mapper with expr; module_binding; signature; structure} + { + default_mapper with + expr; + module_binding; + signature; + signature_item; + structure; + structure_item; + } | 4 -> let expr, module_binding, signature, structure = V4.jsxMapper ~config in { diff --git a/tests/ppx/react/expected/fileLevelConfig.res_v3.txt b/tests/ppx/react/expected/fileLevelConfig.res_v3.txt index d38ec6da..fbc6ee5e 100644 --- a/tests/ppx/react/expected/fileLevelConfig.res_v3.txt +++ b/tests/ppx/react/expected/fileLevelConfig.res_v3.txt @@ -1,11 +1 @@ -@@jsxConfig({version: 4, mode: "automatic"}) -@obj external makeProps: (~msg: 'msg, ~key: string=?, unit) => {"msg": 'msg} = "" - -let make = - (@warning("-16") ~msg) => { - ReactDOMRe.createDOMElementVariadic("div", [{msg->React.string}]) - } -let make = { - let \"FileLevelConfig" = (\"Props": {"msg": 'msg}) => make(~msg=\"Props"["msg"]) - \"FileLevelConfig" -} +Fatal error: exception Invalid_argument("JSX: the JSX version must be 3") From 3613de9dc2389579aaa3dde5746e6d98f8f0d007 Mon Sep 17 00:00:00 2001 From: Woonki Moon Date: Fri, 8 Jul 2022 01:56:17 +0900 Subject: [PATCH 46/94] add test for jsxConfig --- .../expected/fileLevelConfig.res_v4_auto.txt | 22 +++++++++++++++++++ .../expected/fileLevelConfig.res_v4_cls.txt | 22 +++++++++++++++++++ tests/ppx/react/fileLevelConfig.res | 14 ++++++++++++ 3 files changed, 58 insertions(+) diff --git a/tests/ppx/react/expected/fileLevelConfig.res_v4_auto.txt b/tests/ppx/react/expected/fileLevelConfig.res_v4_auto.txt index a67a65e5..0047c50d 100644 --- a/tests/ppx/react/expected/fileLevelConfig.res_v4_auto.txt +++ b/tests/ppx/react/expected/fileLevelConfig.res_v4_auto.txt @@ -8,3 +8,25 @@ let make = { let \"FileLevelConfig" = (props: props<_>) => make(props) \"FileLevelConfig" } + +@@jsxConfig({version: 4, mode: "classic"}) +type props<'msg> = {key?: string, msg: 'msg} + +let make = ({msg, _}: props<'msg>) => { + ReactDOMRe.createDOMElementVariadic("div", [{msg->React.string}]) +} +let make = { + let \"FileLevelConfig" = (props: props<_>) => make(props) + \"FileLevelConfig" +} + +@@jsxConfig({version: 3}) +type props<'msg> = {key?: string, msg: 'msg} + +let make = ({msg, _}: props<'msg>) => { + ReactDOMRe.createDOMElementVariadic("div", [{msg->React.string}]) +} +let make = { + let \"FileLevelConfig" = (props: props<_>) => make(props) + \"FileLevelConfig" +} diff --git a/tests/ppx/react/expected/fileLevelConfig.res_v4_cls.txt b/tests/ppx/react/expected/fileLevelConfig.res_v4_cls.txt index a67a65e5..0047c50d 100644 --- a/tests/ppx/react/expected/fileLevelConfig.res_v4_cls.txt +++ b/tests/ppx/react/expected/fileLevelConfig.res_v4_cls.txt @@ -8,3 +8,25 @@ let make = { let \"FileLevelConfig" = (props: props<_>) => make(props) \"FileLevelConfig" } + +@@jsxConfig({version: 4, mode: "classic"}) +type props<'msg> = {key?: string, msg: 'msg} + +let make = ({msg, _}: props<'msg>) => { + ReactDOMRe.createDOMElementVariadic("div", [{msg->React.string}]) +} +let make = { + let \"FileLevelConfig" = (props: props<_>) => make(props) + \"FileLevelConfig" +} + +@@jsxConfig({version: 3}) +type props<'msg> = {key?: string, msg: 'msg} + +let make = ({msg, _}: props<'msg>) => { + ReactDOMRe.createDOMElementVariadic("div", [{msg->React.string}]) +} +let make = { + let \"FileLevelConfig" = (props: props<_>) => make(props) + \"FileLevelConfig" +} diff --git a/tests/ppx/react/fileLevelConfig.res b/tests/ppx/react/fileLevelConfig.res index 3354e688..5880183b 100644 --- a/tests/ppx/react/fileLevelConfig.res +++ b/tests/ppx/react/fileLevelConfig.res @@ -4,3 +4,17 @@ let make = (~msg) => {
{msg->React.string}
} + +@@jsxConfig({version: 4, mode: "classic"}) + +@react.component +let make = (~msg) => { +
{msg->React.string}
+} + +@@jsxConfig({version: 3}) + +@react.component +let make = (~msg) => { +
{msg->React.string}
+} From fa8318ab6b527a1cfa87d0cdcf96e763163bff1f Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Thu, 7 Jul 2022 19:55:48 +0200 Subject: [PATCH 47/94] Weave the mappers together. --- cli/reactjs_jsx_ppx.ml | 60 +++++++++++-------- .../react/expected/fileLevelConfig.res_v3.txt | 36 ++++++++++- 2 files changed, 71 insertions(+), 25 deletions(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index 41e5e84f..e8ba8fc5 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -2750,30 +2750,42 @@ let getMapper ~config = default_mapper.signature_item mapper item in - match config.version with - | 3 -> - let expr, module_binding, signature, structure = V3.jsxMapper ~config in - { - default_mapper with - expr; - module_binding; - signature; - signature_item; - structure; - structure_item; - } - | 4 -> - let expr, module_binding, signature, structure = V4.jsxMapper ~config in - { - default_mapper with - expr; - module_binding; - signature; - signature_item; - structure; - structure_item; - } - | _ -> default_mapper + let expr3, module_binding3, signature3, structure3 = V3.jsxMapper ~config in + let expr4, module_binding4, signature4, structure4 = V4.jsxMapper ~config in + + let expr mapper e = + match config.version with + | 3 -> expr3 mapper e + | 4 -> expr4 mapper e + | _ -> default_mapper.expr mapper e + in + let module_binding mapper mb = + match config.version with + | 3 -> module_binding3 mapper mb + | 4 -> module_binding4 mapper mb + | _ -> default_mapper.module_binding mapper mb + in + let signature mapper s = + match config.version with + | 3 -> signature3 mapper s + | 4 -> signature4 mapper s + | _ -> default_mapper.signature mapper s + in + let structure mapper s = + match config.version with + | 3 -> structure3 mapper s + | 4 -> structure4 mapper s + | _ -> default_mapper.structure mapper s + in + { + default_mapper with + expr; + module_binding; + signature; + signature_item; + structure; + structure_item; + } let rewrite_implementation ~config (code : Parsetree.structure) : Parsetree.structure = diff --git a/tests/ppx/react/expected/fileLevelConfig.res_v3.txt b/tests/ppx/react/expected/fileLevelConfig.res_v3.txt index fbc6ee5e..b1e38d43 100644 --- a/tests/ppx/react/expected/fileLevelConfig.res_v3.txt +++ b/tests/ppx/react/expected/fileLevelConfig.res_v3.txt @@ -1 +1,35 @@ -Fatal error: exception Invalid_argument("JSX: the JSX version must be 3") +@@jsxConfig({version: 4, mode: "automatic"}) +@obj external makeProps: (~msg: 'msg, ~key: string=?, unit) => {"msg": 'msg} = "" + +let make = + (@warning("-16") ~msg) => { + ReactDOM.jsx("div", {children: {msg->React.string}}) + } +let make = { + let \"FileLevelConfig" = (\"Props": {"msg": 'msg}) => make(~msg=\"Props"["msg"]) + \"FileLevelConfig" +} + +@@jsxConfig({version: 4, mode: "classic"}) +@obj external makeProps: (~msg: 'msg, ~key: string=?, unit) => {"msg": 'msg} = "" + +let make = + (@warning("-16") ~msg) => { + ReactDOMRe.createDOMElementVariadic("div", [{msg->React.string}]) + } +let make = { + let \"FileLevelConfig" = (\"Props": {"msg": 'msg}) => make(~msg=\"Props"["msg"]) + \"FileLevelConfig" +} + +@@jsxConfig({version: 3}) +@obj external makeProps: (~msg: 'msg, ~key: string=?, unit) => {"msg": 'msg} = "" + +let make = + (@warning("-16") ~msg) => { + ReactDOMRe.createDOMElementVariadic("div", [{msg->React.string}]) + } +let make = { + let \"FileLevelConfig" = (\"Props": {"msg": 'msg}) => make(~msg=\"Props"["msg"]) + \"FileLevelConfig" +} From a3216c89b1133b13903b30122105add09322655e Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Thu, 7 Jul 2022 20:06:01 +0200 Subject: [PATCH 48/94] Rename --- cli/reactjs_jsx_ppx.ml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index e8ba8fc5..d5c7c395 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -1215,13 +1215,13 @@ module V3 = struct [@@raises Invalid_argument] in - let signatureV3 mapper signature = + let signature mapper signature = default_mapper.signature mapper @@ reactComponentSignatureTransform mapper signature [@@raises Invalid_argument] in - let structureV3 mapper structure = + let structure mapper structure = match structure with | structures -> default_mapper.structure mapper @@ -1229,7 +1229,7 @@ module V3 = struct [@@raises Invalid_argument] in - let exprV3 mapper expression = + let expr mapper expression = match expression with (* Does the function application have the @JSX attribute? *) | { @@ -1289,14 +1289,14 @@ module V3 = struct [@@raises Invalid_argument] in - let module_bindingV3 mapper module_binding = + let module_binding mapper module_binding = let _ = nestedModules := module_binding.pmb_name.txt :: !nestedModules in let mapped = default_mapper.module_binding mapper module_binding in let _ = nestedModules := List.tl !nestedModules in mapped [@@raises Failure] in - (exprV3, module_bindingV3, signatureV3, structureV3) + (expr, module_binding, signature, structure) [@@raises Invalid_argument, Failure] end From 4bc6f34a2826adfb23a056cd42085786927e7926 Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Thu, 7 Jul 2022 20:07:13 +0200 Subject: [PATCH 49/94] Another rename. --- cli/reactjs_jsx_ppx.ml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index d5c7c395..cfcd9b49 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -2623,19 +2623,19 @@ module V4 = struct module name.") [@@raises Invalid_argument] - let signatureV4 mapper signature = + let signature mapper signature = default_mapper.signature mapper @@ reactComponentSignatureTransform mapper signature [@@raises Invalid_argument] - let structureV4 ~config mapper items = + let structure ~config mapper items = match items with | items -> default_mapper.structure mapper @@ reactComponentTransform ~config mapper items [@@raises Invalid_argument] - let exprV4 ~config mapper expression = + let expr ~config mapper expression = match expression with (* Does the function application have the @JSX attribute? *) | {pexp_desc = Pexp_apply (callExpression, callArguments); pexp_attributes} @@ -2719,7 +2719,7 @@ module V4 = struct | e -> default_mapper.expr mapper e [@@raises Invalid_argument] - let module_bindingV4 ~config mapper module_binding = + let module_binding ~config mapper module_binding = config.nestedModules <- module_binding.pmb_name.txt :: config.nestedModules; let mapped = default_mapper.module_binding mapper module_binding in config.nestedModules <- List.tl config.nestedModules; @@ -2728,10 +2728,9 @@ module V4 = struct (* TODO: some line number might still be wrong *) let jsxMapper ~config = - let structure = structureV4 ~config in - let signature = signatureV4 in - let module_binding = module_bindingV4 ~config in - let expr = exprV4 ~config in + let structure = structure ~config in + let module_binding = module_binding ~config in + let expr = expr ~config in (expr, module_binding, signature, structure) [@@raises Invalid_argument, Failure] end From 9ddd7217e371c377dbf38d5f65a57dade9d830ad Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Thu, 7 Jul 2022 20:08:20 +0200 Subject: [PATCH 50/94] cleanup --- cli/reactjs_jsx_ppx.ml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index cfcd9b49..3c53e3d1 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -2629,10 +2629,8 @@ module V4 = struct [@@raises Invalid_argument] let structure ~config mapper items = - match items with - | items -> - default_mapper.structure mapper - @@ reactComponentTransform ~config mapper items + default_mapper.structure mapper + @@ reactComponentTransform ~config mapper items [@@raises Invalid_argument] let expr ~config mapper expression = From 0d1bcbd347ef8e1952f57f224997bef3da06f5b7 Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Thu, 7 Jul 2022 20:11:04 +0200 Subject: [PATCH 51/94] Rename toplevel transforms. --- cli/reactjs_jsx_ppx.ml | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index 3c53e3d1..76d79f4c 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -2055,7 +2055,7 @@ module V4 = struct | name when isOptional name -> (true, getLabel name, [], type_) :: types | _ -> types - let transformComponentDefinition ~config mapper structure returnStructures = + let transformStructureItem ~config mapper structure returnStructures = match structure with (* external *) | { @@ -2520,11 +2520,11 @@ module V4 = struct | structure -> structure :: returnStructures [@@raises Invalid_argument] - let reactComponentTransform ~config mapper structures = - List.fold_right (transformComponentDefinition ~config mapper) structures [] + let transformStructure ~config mapper structures = + List.fold_right (transformStructureItem ~config mapper) structures [] [@@raises Invalid_argument] - let transformComponentSignature _mapper signature returnSignatures = + let transformSignatureItem _mapper signature returnSignatures = match signature with | { psig_loc; @@ -2582,10 +2582,6 @@ module V4 = struct | signature -> signature :: returnSignatures [@@raises Invalid_argument] - let reactComponentSignatureTransform mapper signatures = - List.fold_right (transformComponentSignature mapper) signatures [] - [@@raises Invalid_argument] - let transformJsxCall ~config mapper callExpression callArguments attrs = match callExpression.pexp_desc with | Pexp_ident caller -> ( @@ -2623,14 +2619,16 @@ module V4 = struct module name.") [@@raises Invalid_argument] + let transformSignature mapper signatures = + List.fold_right (transformSignatureItem mapper) signatures [] + [@@raises Invalid_argument] + let signature mapper signature = - default_mapper.signature mapper - @@ reactComponentSignatureTransform mapper signature + default_mapper.signature mapper @@ transformSignature mapper signature [@@raises Invalid_argument] let structure ~config mapper items = - default_mapper.structure mapper - @@ reactComponentTransform ~config mapper items + default_mapper.structure mapper @@ transformStructure ~config mapper items [@@raises Invalid_argument] let expr ~config mapper expression = From 9c500e2d787d2d28d4881b472972119f428352a5 Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Thu, 7 Jul 2022 20:21:30 +0200 Subject: [PATCH 52/94] Refactor V4 process structure to be more granular. --- cli/reactjs_jsx_ppx.ml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index 76d79f4c..c0ad3227 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -2055,8 +2055,8 @@ module V4 = struct | name when isOptional name -> (true, getLabel name, [], type_) :: types | _ -> types - let transformStructureItem ~config mapper structure returnStructures = - match structure with + let transformStructureItem ~config mapper item = + match item with (* external *) | { pstr_loc; @@ -2064,7 +2064,7 @@ module V4 = struct Pstr_primitive ({pval_attributes; pval_type} as value_description); } as pstr -> ( match List.filter hasAttr pval_attributes with - | [] -> structure :: returnStructures + | [] -> [item] | [_] -> let rec getPropTypes types ({ptyp_loc; ptyp_desc} as fullType) = match ptyp_desc with @@ -2107,14 +2107,14 @@ module V4 = struct }; } in - propsRecordType :: newStructure :: returnStructures + [propsRecordType; newStructure] | _ -> raise (Invalid_argument "Only one react.component call can exist on a component at one \ time")) (* let component = ... *) - | {pstr_loc; pstr_desc = Pstr_value (recFlag, valueBindings)} -> + | {pstr_loc; pstr_desc = Pstr_value (recFlag, valueBindings)} -> ( let fileName = filenameFromLoc pstr_loc in let emptyLoc = Location.in_file fileName in let mapBinding binding = @@ -2512,16 +2512,16 @@ module V4 = struct in types @ [{pstr_loc; pstr_desc = Pstr_value (recFlag, bindings)}] - @ (match newBindings with - | [] -> [] - | newBindings -> - [{pstr_loc = emptyLoc; pstr_desc = Pstr_value (recFlag, newBindings)}]) - @ returnStructures - | structure -> structure :: returnStructures + @ + match newBindings with + | [] -> [] + | newBindings -> + [{pstr_loc = emptyLoc; pstr_desc = Pstr_value (recFlag, newBindings)}]) + | _ -> [item] [@@raises Invalid_argument] - let transformStructure ~config mapper structures = - List.fold_right (transformStructureItem ~config mapper) structures [] + let transformStructure ~config mapper items = + List.map (transformStructureItem ~config mapper) items |> List.flatten [@@raises Invalid_argument] let transformSignatureItem _mapper signature returnSignatures = From ed58791d15d8471b0edf9c7def71f0104549d745 Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Thu, 7 Jul 2022 20:26:08 +0200 Subject: [PATCH 53/94] Inline toplevel transforms. --- cli/reactjs_jsx_ppx.ml | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index c0ad3227..81ce8515 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -2520,18 +2520,14 @@ module V4 = struct | _ -> [item] [@@raises Invalid_argument] - let transformStructure ~config mapper items = - List.map (transformStructureItem ~config mapper) items |> List.flatten - [@@raises Invalid_argument] - - let transformSignatureItem _mapper signature returnSignatures = - match signature with + let transformSignatureItem _mapper item = + match item with | { psig_loc; psig_desc = Psig_value ({pval_attributes; pval_type} as psig_desc); } as psig -> ( match List.filter hasAttr pval_attributes with - | [] -> signature :: returnSignatures + | [] -> [item] | [_] -> let rec getPropTypes types ({ptyp_loc; ptyp_desc} as fullType) = match ptyp_desc with @@ -2573,13 +2569,13 @@ module V4 = struct }; } in - propsRecordType :: newStructure :: returnSignatures + [propsRecordType; newStructure] | _ -> raise (Invalid_argument "Only one react.component call can exist on a component at one \ time")) - | signature -> signature :: returnSignatures + | _ -> [item] [@@raises Invalid_argument] let transformJsxCall ~config mapper callExpression callArguments attrs = @@ -2619,16 +2615,14 @@ module V4 = struct module name.") [@@raises Invalid_argument] - let transformSignature mapper signatures = - List.fold_right (transformSignatureItem mapper) signatures [] - [@@raises Invalid_argument] - - let signature mapper signature = - default_mapper.signature mapper @@ transformSignature mapper signature + let signature mapper items = + default_mapper.signature mapper + @@ (List.map (transformSignatureItem mapper) items |> List.flatten) [@@raises Invalid_argument] let structure ~config mapper items = - default_mapper.structure mapper @@ transformStructure ~config mapper items + default_mapper.structure mapper + @@ (List.map (transformStructureItem ~config mapper) items |> List.flatten) [@@raises Invalid_argument] let expr ~config mapper expression = From 62aa219a419d3691c548601e1808736d8c4db1cb Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Thu, 7 Jul 2022 20:28:08 +0200 Subject: [PATCH 54/94] More explicit. --- cli/reactjs_jsx_ppx.ml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index 81ce8515..2cdb8c4c 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -2616,13 +2616,13 @@ module V4 = struct [@@raises Invalid_argument] let signature mapper items = - default_mapper.signature mapper - @@ (List.map (transformSignatureItem mapper) items |> List.flatten) + let items = default_mapper.signature mapper items in + List.map (transformSignatureItem mapper) items |> List.flatten [@@raises Invalid_argument] let structure ~config mapper items = - default_mapper.structure mapper - @@ (List.map (transformStructureItem ~config mapper) items |> List.flatten) + let items = default_mapper.structure mapper items in + List.map (transformStructureItem ~config mapper) items |> List.flatten [@@raises Invalid_argument] let expr ~config mapper expression = From c7a10e0688337937395215e4f21c0d36ef536654 Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Thu, 7 Jul 2022 20:32:27 +0200 Subject: [PATCH 55/94] In V4, gate structure item transform to V4. --- cli/reactjs_jsx_ppx.ml | 17 ++++++++++--- .../expected/fileLevelConfig.res_v4_auto.txt | 24 +++++-------------- .../expected/fileLevelConfig.res_v4_cls.txt | 24 +++++-------------- 3 files changed, 26 insertions(+), 39 deletions(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index 2cdb8c4c..aba74e96 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -2615,14 +2615,24 @@ module V4 = struct module name.") [@@raises Invalid_argument] - let signature mapper items = + let signature ~config mapper items = let items = default_mapper.signature mapper items in - List.map (transformSignatureItem mapper) items |> List.flatten + List.map + (fun item -> + if config.version = 4 then transformSignatureItem mapper item + else [item]) + items + |> List.flatten [@@raises Invalid_argument] let structure ~config mapper items = let items = default_mapper.structure mapper items in - List.map (transformStructureItem ~config mapper) items |> List.flatten + List.map + (fun item -> + if config.version = 4 then transformStructureItem ~config mapper item + else [item]) + items + |> List.flatten [@@raises Invalid_argument] let expr ~config mapper expression = @@ -2718,6 +2728,7 @@ module V4 = struct (* TODO: some line number might still be wrong *) let jsxMapper ~config = + let signature = signature ~config in let structure = structure ~config in let module_binding = module_binding ~config in let expr = expr ~config in diff --git a/tests/ppx/react/expected/fileLevelConfig.res_v4_auto.txt b/tests/ppx/react/expected/fileLevelConfig.res_v4_auto.txt index 0047c50d..1c9834a9 100644 --- a/tests/ppx/react/expected/fileLevelConfig.res_v4_auto.txt +++ b/tests/ppx/react/expected/fileLevelConfig.res_v4_auto.txt @@ -1,32 +1,20 @@ @@jsxConfig({version: 4, mode: "automatic"}) -type props<'msg> = {key?: string, msg: 'msg} -let make = ({msg, _}: props<'msg>) => { +@react.component +let make = (~msg) => { ReactDOM.jsx("div", {children: {msg->React.string}}) } -let make = { - let \"FileLevelConfig" = (props: props<_>) => make(props) - \"FileLevelConfig" -} @@jsxConfig({version: 4, mode: "classic"}) -type props<'msg> = {key?: string, msg: 'msg} -let make = ({msg, _}: props<'msg>) => { +@react.component +let make = (~msg) => { ReactDOMRe.createDOMElementVariadic("div", [{msg->React.string}]) } -let make = { - let \"FileLevelConfig" = (props: props<_>) => make(props) - \"FileLevelConfig" -} @@jsxConfig({version: 3}) -type props<'msg> = {key?: string, msg: 'msg} -let make = ({msg, _}: props<'msg>) => { +@react.component +let make = (~msg) => { ReactDOMRe.createDOMElementVariadic("div", [{msg->React.string}]) } -let make = { - let \"FileLevelConfig" = (props: props<_>) => make(props) - \"FileLevelConfig" -} diff --git a/tests/ppx/react/expected/fileLevelConfig.res_v4_cls.txt b/tests/ppx/react/expected/fileLevelConfig.res_v4_cls.txt index 0047c50d..1c9834a9 100644 --- a/tests/ppx/react/expected/fileLevelConfig.res_v4_cls.txt +++ b/tests/ppx/react/expected/fileLevelConfig.res_v4_cls.txt @@ -1,32 +1,20 @@ @@jsxConfig({version: 4, mode: "automatic"}) -type props<'msg> = {key?: string, msg: 'msg} -let make = ({msg, _}: props<'msg>) => { +@react.component +let make = (~msg) => { ReactDOM.jsx("div", {children: {msg->React.string}}) } -let make = { - let \"FileLevelConfig" = (props: props<_>) => make(props) - \"FileLevelConfig" -} @@jsxConfig({version: 4, mode: "classic"}) -type props<'msg> = {key?: string, msg: 'msg} -let make = ({msg, _}: props<'msg>) => { +@react.component +let make = (~msg) => { ReactDOMRe.createDOMElementVariadic("div", [{msg->React.string}]) } -let make = { - let \"FileLevelConfig" = (props: props<_>) => make(props) - \"FileLevelConfig" -} @@jsxConfig({version: 3}) -type props<'msg> = {key?: string, msg: 'msg} -let make = ({msg, _}: props<'msg>) => { +@react.component +let make = (~msg) => { ReactDOMRe.createDOMElementVariadic("div", [{msg->React.string}]) } -let make = { - let \"FileLevelConfig" = (props: props<_>) => make(props) - \"FileLevelConfig" -} From e69efffb701ce8ad1842809f39195efcdbbcf9e5 Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Thu, 7 Jul 2022 20:34:16 +0200 Subject: [PATCH 56/94] Refactor toplevel V3. --- cli/reactjs_jsx_ppx.ml | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index aba74e96..c527abcc 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -1215,17 +1215,15 @@ module V3 = struct [@@raises Invalid_argument] in - let signature mapper signature = - default_mapper.signature mapper - @@ reactComponentSignatureTransform mapper signature + let signature mapper items = + let items = default_mapper.signature mapper items in + reactComponentSignatureTransform mapper items [@@raises Invalid_argument] in - let structure mapper structure = - match structure with - | structures -> - default_mapper.structure mapper - @@ reactComponentTransform mapper structures + let structure mapper items = + let items = default_mapper.structure mapper items in + reactComponentTransform mapper items [@@raises Invalid_argument] in From d5280f248d6a428d856ec229e90f34f4461ca199 Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Thu, 7 Jul 2022 20:36:21 +0200 Subject: [PATCH 57/94] Rename V3. --- cli/reactjs_jsx_ppx.ml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index c527abcc..4e1329a4 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -647,7 +647,7 @@ module V3 = struct in let nestedModules = ref [] in - let transformComponentDefinition mapper structure returnStructures = + let transformStructureItem mapper structure returnStructures = match structure with (* external *) | { @@ -1100,12 +1100,12 @@ module V3 = struct [@@raises Invalid_argument] in - let reactComponentTransform mapper structures = - List.fold_right (transformComponentDefinition mapper) structures [] + let transformStucture mapper structures = + List.fold_right (transformStructureItem mapper) structures [] [@@raises Invalid_argument] in - let transformComponentSignature _mapper signature returnSignatures = + let transformSignatureItem _mapper signature returnSignatures = match signature with | { psig_loc; @@ -1168,8 +1168,8 @@ module V3 = struct [@@raises Invalid_argument] in - let reactComponentSignatureTransform mapper signatures = - List.fold_right (transformComponentSignature mapper) signatures [] + let transformSignature mapper items = + List.fold_right (transformSignatureItem mapper) items [] [@@raises Invalid_argument] in @@ -1217,13 +1217,13 @@ module V3 = struct let signature mapper items = let items = default_mapper.signature mapper items in - reactComponentSignatureTransform mapper items + transformSignature mapper items [@@raises Invalid_argument] in let structure mapper items = let items = default_mapper.structure mapper items in - reactComponentTransform mapper items + transformStucture mapper items [@@raises Invalid_argument] in From ea52f010e7846ab70873c4d2ecbbe0561d1691bd Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Thu, 7 Jul 2022 20:40:34 +0200 Subject: [PATCH 58/94] Make V3 structure and signature more explicit. --- cli/reactjs_jsx_ppx.ml | 52 +++++++++++++++--------------------------- 1 file changed, 19 insertions(+), 33 deletions(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index 4e1329a4..92db5361 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -647,8 +647,8 @@ module V3 = struct in let nestedModules = ref [] in - let transformStructureItem mapper structure returnStructures = - match structure with + let transformStructureItem mapper item = + match item with (* external *) | { pstr_loc; @@ -658,7 +658,7 @@ module V3 = struct value_description); } as pstr -> ( match List.filter hasAttr pval_attributes with - | [] -> structure :: returnStructures + | [] -> [item] | [_] -> let rec getPropTypes types ({ptyp_loc; ptyp_desc} as fullType) = match ptyp_desc with @@ -701,14 +701,14 @@ module V3 = struct }; } in - externalPropsDecl :: newStructure :: returnStructures + [externalPropsDecl; newStructure] | _ -> raise (Invalid_argument "Only one react.component call can exist on a component at one \ time")) (* let component = ... *) - | {pstr_loc; pstr_desc = Pstr_value (recFlag, valueBindings)} -> + | {pstr_loc; pstr_desc = Pstr_value (recFlag, valueBindings)} -> ( let fileName = filenameFromLoc pstr_loc in let emptyLoc = Location.in_file fileName in let mapBinding binding = @@ -1086,27 +1086,18 @@ module V3 = struct in externs @ [{pstr_loc; pstr_desc = Pstr_value (recFlag, bindings)}] - @ (match newBindings with - | [] -> [] - | newBindings -> - [ - { - pstr_loc = emptyLoc; - pstr_desc = Pstr_value (recFlag, newBindings); - }; - ]) - @ returnStructures - | structure -> structure :: returnStructures - [@@raises Invalid_argument] - in - - let transformStucture mapper structures = - List.fold_right (transformStructureItem mapper) structures [] + @ + match newBindings with + | [] -> [] + | newBindings -> + [{pstr_loc = emptyLoc; pstr_desc = Pstr_value (recFlag, newBindings)}] + ) + | _ -> [item] [@@raises Invalid_argument] in - let transformSignatureItem _mapper signature returnSignatures = - match signature with + let transformSignatureItem _mapper item = + match item with | { psig_loc; psig_desc = @@ -1115,7 +1106,7 @@ module V3 = struct psig_desc); } as psig -> ( match List.filter hasAttr pval_attributes with - | [] -> signature :: returnSignatures + | [] -> [item] | [_] -> let rec getPropTypes types ({ptyp_loc; ptyp_desc} as fullType) = match ptyp_desc with @@ -1158,18 +1149,13 @@ module V3 = struct }; } in - externalPropsDecl :: newStructure :: returnSignatures + [externalPropsDecl; newStructure] | _ -> raise (Invalid_argument "Only one react.component call can exist on a component at one \ time")) - | signature -> signature :: returnSignatures - [@@raises Invalid_argument] - in - - let transformSignature mapper items = - List.fold_right (transformSignatureItem mapper) items [] + | _ -> [item] [@@raises Invalid_argument] in @@ -1217,13 +1203,13 @@ module V3 = struct let signature mapper items = let items = default_mapper.signature mapper items in - transformSignature mapper items + List.map (transformSignatureItem mapper) items |> List.flatten [@@raises Invalid_argument] in let structure mapper items = let items = default_mapper.structure mapper items in - transformStucture mapper items + List.map (transformStructureItem mapper) items |> List.flatten [@@raises Invalid_argument] in From a1e245af63c9d8abcecf9526bfa5aa32986a2dc2 Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Thu, 7 Jul 2022 20:42:25 +0200 Subject: [PATCH 59/94] Gate V3 structure and signature to when config version is 3. --- cli/reactjs_jsx_ppx.ml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index 92db5361..0161e983 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -1203,13 +1203,23 @@ module V3 = struct let signature mapper items = let items = default_mapper.signature mapper items in - List.map (transformSignatureItem mapper) items |> List.flatten + List.map + (fun item -> + if config.version = 3 then transformSignatureItem mapper item + else [item]) + items + |> List.flatten [@@raises Invalid_argument] in let structure mapper items = let items = default_mapper.structure mapper items in - List.map (transformStructureItem mapper) items |> List.flatten + List.map + (fun item -> + if config.version = 3 then transformStructureItem mapper item + else [item]) + items + |> List.flatten [@@raises Invalid_argument] in From 483ad96b08aab749490fc4c33e5193d5e6672a27 Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Thu, 7 Jul 2022 20:44:45 +0200 Subject: [PATCH 60/94] Run both V3 and V4 structure and signature now that they are gated. --- cli/reactjs_jsx_ppx.ml | 14 ++------ .../expected/fileLevelConfig.res_v4_auto.txt | 33 ++++++++++++++----- .../expected/fileLevelConfig.res_v4_cls.txt | 33 ++++++++++++++----- 3 files changed, 50 insertions(+), 30 deletions(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index 0161e983..561fa029 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -2759,18 +2759,8 @@ let getMapper ~config = | 4 -> module_binding4 mapper mb | _ -> default_mapper.module_binding mapper mb in - let signature mapper s = - match config.version with - | 3 -> signature3 mapper s - | 4 -> signature4 mapper s - | _ -> default_mapper.signature mapper s - in - let structure mapper s = - match config.version with - | 3 -> structure3 mapper s - | 4 -> structure4 mapper s - | _ -> default_mapper.structure mapper s - in + let signature mapper s = signature4 mapper (signature3 mapper s) in + let structure mapper s = structure4 mapper (structure3 mapper s) in { default_mapper with expr; diff --git a/tests/ppx/react/expected/fileLevelConfig.res_v4_auto.txt b/tests/ppx/react/expected/fileLevelConfig.res_v4_auto.txt index 1c9834a9..b1e38d43 100644 --- a/tests/ppx/react/expected/fileLevelConfig.res_v4_auto.txt +++ b/tests/ppx/react/expected/fileLevelConfig.res_v4_auto.txt @@ -1,20 +1,35 @@ @@jsxConfig({version: 4, mode: "automatic"}) +@obj external makeProps: (~msg: 'msg, ~key: string=?, unit) => {"msg": 'msg} = "" -@react.component -let make = (~msg) => { - ReactDOM.jsx("div", {children: {msg->React.string}}) +let make = + (@warning("-16") ~msg) => { + ReactDOM.jsx("div", {children: {msg->React.string}}) + } +let make = { + let \"FileLevelConfig" = (\"Props": {"msg": 'msg}) => make(~msg=\"Props"["msg"]) + \"FileLevelConfig" } @@jsxConfig({version: 4, mode: "classic"}) +@obj external makeProps: (~msg: 'msg, ~key: string=?, unit) => {"msg": 'msg} = "" -@react.component -let make = (~msg) => { - ReactDOMRe.createDOMElementVariadic("div", [{msg->React.string}]) +let make = + (@warning("-16") ~msg) => { + ReactDOMRe.createDOMElementVariadic("div", [{msg->React.string}]) + } +let make = { + let \"FileLevelConfig" = (\"Props": {"msg": 'msg}) => make(~msg=\"Props"["msg"]) + \"FileLevelConfig" } @@jsxConfig({version: 3}) +@obj external makeProps: (~msg: 'msg, ~key: string=?, unit) => {"msg": 'msg} = "" -@react.component -let make = (~msg) => { - ReactDOMRe.createDOMElementVariadic("div", [{msg->React.string}]) +let make = + (@warning("-16") ~msg) => { + ReactDOMRe.createDOMElementVariadic("div", [{msg->React.string}]) + } +let make = { + let \"FileLevelConfig" = (\"Props": {"msg": 'msg}) => make(~msg=\"Props"["msg"]) + \"FileLevelConfig" } diff --git a/tests/ppx/react/expected/fileLevelConfig.res_v4_cls.txt b/tests/ppx/react/expected/fileLevelConfig.res_v4_cls.txt index 1c9834a9..b1e38d43 100644 --- a/tests/ppx/react/expected/fileLevelConfig.res_v4_cls.txt +++ b/tests/ppx/react/expected/fileLevelConfig.res_v4_cls.txt @@ -1,20 +1,35 @@ @@jsxConfig({version: 4, mode: "automatic"}) +@obj external makeProps: (~msg: 'msg, ~key: string=?, unit) => {"msg": 'msg} = "" -@react.component -let make = (~msg) => { - ReactDOM.jsx("div", {children: {msg->React.string}}) +let make = + (@warning("-16") ~msg) => { + ReactDOM.jsx("div", {children: {msg->React.string}}) + } +let make = { + let \"FileLevelConfig" = (\"Props": {"msg": 'msg}) => make(~msg=\"Props"["msg"]) + \"FileLevelConfig" } @@jsxConfig({version: 4, mode: "classic"}) +@obj external makeProps: (~msg: 'msg, ~key: string=?, unit) => {"msg": 'msg} = "" -@react.component -let make = (~msg) => { - ReactDOMRe.createDOMElementVariadic("div", [{msg->React.string}]) +let make = + (@warning("-16") ~msg) => { + ReactDOMRe.createDOMElementVariadic("div", [{msg->React.string}]) + } +let make = { + let \"FileLevelConfig" = (\"Props": {"msg": 'msg}) => make(~msg=\"Props"["msg"]) + \"FileLevelConfig" } @@jsxConfig({version: 3}) +@obj external makeProps: (~msg: 'msg, ~key: string=?, unit) => {"msg": 'msg} = "" -@react.component -let make = (~msg) => { - ReactDOMRe.createDOMElementVariadic("div", [{msg->React.string}]) +let make = + (@warning("-16") ~msg) => { + ReactDOMRe.createDOMElementVariadic("div", [{msg->React.string}]) + } +let make = { + let \"FileLevelConfig" = (\"Props": {"msg": 'msg}) => make(~msg=\"Props"["msg"]) + \"FileLevelConfig" } From ec1cdafdddcf98223af574073fe628ea4efee407 Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Thu, 7 Jul 2022 20:58:52 +0200 Subject: [PATCH 61/94] Expose the structure (signature) item transformers and call them directly. --- cli/reactjs_jsx_ppx.ml | 62 ++++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index 561fa029..afbd7eba 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -1201,28 +1201,6 @@ module V3 = struct [@@raises Invalid_argument] in - let signature mapper items = - let items = default_mapper.signature mapper items in - List.map - (fun item -> - if config.version = 3 then transformSignatureItem mapper item - else [item]) - items - |> List.flatten - [@@raises Invalid_argument] - in - - let structure mapper items = - let items = default_mapper.structure mapper items in - List.map - (fun item -> - if config.version = 3 then transformStructureItem mapper item - else [item]) - items - |> List.flatten - [@@raises Invalid_argument] - in - let expr mapper expression = match expression with (* Does the function application have the @JSX attribute? *) @@ -1290,7 +1268,7 @@ module V3 = struct mapped [@@raises Failure] in - (expr, module_binding, signature, structure) + (expr, module_binding, transformSignatureItem, transformStructureItem) [@@raises Invalid_argument, Failure] end @@ -2722,11 +2700,9 @@ module V4 = struct (* TODO: some line number might still be wrong *) let jsxMapper ~config = - let signature = signature ~config in - let structure = structure ~config in let module_binding = module_binding ~config in let expr = expr ~config in - (expr, module_binding, signature, structure) + (expr, module_binding, transformSignatureItem, transformStructureItem) [@@raises Invalid_argument, Failure] end @@ -2744,8 +2720,12 @@ let getMapper ~config = default_mapper.signature_item mapper item in - let expr3, module_binding3, signature3, structure3 = V3.jsxMapper ~config in - let expr4, module_binding4, signature4, structure4 = V4.jsxMapper ~config in + let expr3, module_binding3, transformSignatureItem3, transformStructureItem3 = + V3.jsxMapper ~config + in + let expr4, module_binding4, transformSignatureItem4, transformStructureItem4 = + V4.jsxMapper ~config + in let expr mapper e = match config.version with @@ -2759,8 +2739,30 @@ let getMapper ~config = | 4 -> module_binding4 mapper mb | _ -> default_mapper.module_binding mapper mb in - let signature mapper s = signature4 mapper (signature3 mapper s) in - let structure mapper s = structure4 mapper (structure3 mapper s) in + let signature mapper items = + let items = default_mapper.signature mapper items in + List.map + (fun item -> + if config.version = 3 then transformSignatureItem3 mapper item + else if config.version = 4 then transformSignatureItem4 mapper item + else [item]) + items + |> List.flatten + [@@raises Invalid_argument] + in + let structure mapper items = + let items = default_mapper.structure mapper items in + List.map + (fun item -> + if config.version = 3 then transformStructureItem3 mapper item + else if config.version = 4 then + transformStructureItem4 ~config mapper item + else [item]) + items + |> List.flatten + [@@raises Invalid_argument] + in + { default_mapper with expr; From eb6e6810dae4c0ab4e949d306e41259cbc4af0a1 Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Thu, 7 Jul 2022 21:04:52 +0200 Subject: [PATCH 62/94] Reorganize interface a bit. --- cli/reactjs_jsx_ppx.ml | 24 ++++++++++++++++++++---- cli/reactjs_jsx_ppx.mli | 19 ++++++++++--------- cli/res_cli.ml | 14 ++++---------- 3 files changed, 34 insertions(+), 23 deletions(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index afbd7eba..3df78ba8 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -2773,14 +2773,30 @@ let getMapper ~config = structure_item; } -let rewrite_implementation ~config (code : Parsetree.structure) : - Parsetree.structure = +let rewrite_implementation ~jsxVersion ~jsxModule ~jsxMode + (code : Parsetree.structure) : Parsetree.structure = + let config = + { + version = jsxVersion; + module_ = jsxModule; + mode = jsxMode; + nestedModules = []; + } + in let mapper = getMapper ~config in mapper.structure mapper code [@@raises Invalid_argument, Failure] -let rewrite_signature ~config (code : Parsetree.signature) : Parsetree.signature - = +let rewrite_signature ~jsxVersion ~jsxModule ~jsxMode + (code : Parsetree.signature) : Parsetree.signature = + let config = + { + version = jsxVersion; + module_ = jsxModule; + mode = jsxMode; + nestedModules = []; + } + in let mapper = getMapper ~config in mapper.signature mapper code [@@raises Invalid_argument, Failure] diff --git a/cli/reactjs_jsx_ppx.mli b/cli/reactjs_jsx_ppx.mli index 58f247ba..388202ed 100644 --- a/cli/reactjs_jsx_ppx.mli +++ b/cli/reactjs_jsx_ppx.mli @@ -8,15 +8,16 @@ field `reason`, which enables this ppx through some internal call in bsb *) -type jsxConfig = { - mutable version: int; - mutable module_: string; - mutable mode: string; - mutable nestedModules: string list; -} - val rewrite_implementation : - config:jsxConfig -> Parsetree.structure -> Parsetree.structure + jsxVersion:int -> + jsxModule:string -> + jsxMode:string -> + Parsetree.structure -> + Parsetree.structure val rewrite_signature : - config:jsxConfig -> Parsetree.signature -> Parsetree.signature + jsxVersion:int -> + jsxModule:string -> + jsxMode:string -> + Parsetree.signature -> + Parsetree.signature diff --git a/cli/res_cli.ml b/cli/res_cli.ml index 653c731a..ba04d685 100644 --- a/cli/res_cli.ml +++ b/cli/res_cli.ml @@ -275,14 +275,6 @@ module CliArgProcessor = struct let (Parser backend) = parsingEngine in (* This is the whole purpose of the Color module above *) Color.setup None; - let config : Reactjs_jsx_ppx.jsxConfig = - { - version = jsxVersion; - module_ = jsxModule; - mode = jsxMode; - nestedModules = []; - } - in if processInterface then let parseResult = backend.parseInterface ~forPrinter ~filename in if parseResult.invalid then ( @@ -294,7 +286,8 @@ module CliArgProcessor = struct else exit 1) else let parsetree = - Reactjs_jsx_ppx.rewrite_signature ~config parseResult.parsetree + Reactjs_jsx_ppx.rewrite_signature ~jsxVersion ~jsxModule ~jsxMode + parseResult.parsetree in printEngine.printInterface ~width ~filename ~comments:parseResult.comments parsetree @@ -309,7 +302,8 @@ module CliArgProcessor = struct else exit 1) else let parsetree = - Reactjs_jsx_ppx.rewrite_implementation ~config parseResult.parsetree + Reactjs_jsx_ppx.rewrite_implementation ~jsxVersion ~jsxModule ~jsxMode + parseResult.parsetree in printEngine.printImplementation ~width ~filename ~comments:parseResult.comments parsetree From b556a3e18249ec8911e67b4578b7238aefdd97a3 Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Thu, 7 Jul 2022 21:14:18 +0200 Subject: [PATCH 63/94] Don't recursively process all the structure items at once. As otherwise all the structure uses the final mode. --- cli/reactjs_jsx_ppx.ml | 4 +-- .../react/expected/fileLevelConfig.res_v3.txt | 2 +- .../expected/fileLevelConfig.res_v4_auto.txt | 33 +++++++++---------- .../expected/fileLevelConfig.res_v4_cls.txt | 33 +++++++++---------- 4 files changed, 33 insertions(+), 39 deletions(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index 3df78ba8..14ed235f 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -2740,9 +2740,9 @@ let getMapper ~config = | _ -> default_mapper.module_binding mapper mb in let signature mapper items = - let items = default_mapper.signature mapper items in List.map (fun item -> + let item = default_mapper.signature_item mapper item in if config.version = 3 then transformSignatureItem3 mapper item else if config.version = 4 then transformSignatureItem4 mapper item else [item]) @@ -2751,9 +2751,9 @@ let getMapper ~config = [@@raises Invalid_argument] in let structure mapper items = - let items = default_mapper.structure mapper items in List.map (fun item -> + let item = default_mapper.structure_item mapper item in if config.version = 3 then transformStructureItem3 mapper item else if config.version = 4 then transformStructureItem4 ~config mapper item diff --git a/tests/ppx/react/expected/fileLevelConfig.res_v3.txt b/tests/ppx/react/expected/fileLevelConfig.res_v3.txt index b1e38d43..ffb75d4a 100644 --- a/tests/ppx/react/expected/fileLevelConfig.res_v3.txt +++ b/tests/ppx/react/expected/fileLevelConfig.res_v3.txt @@ -3,7 +3,7 @@ let make = (@warning("-16") ~msg) => { - ReactDOM.jsx("div", {children: {msg->React.string}}) + ReactDOMRe.createDOMElementVariadic("div", [{msg->React.string}]) } let make = { let \"FileLevelConfig" = (\"Props": {"msg": 'msg}) => make(~msg=\"Props"["msg"]) diff --git a/tests/ppx/react/expected/fileLevelConfig.res_v4_auto.txt b/tests/ppx/react/expected/fileLevelConfig.res_v4_auto.txt index b1e38d43..3cba4f6e 100644 --- a/tests/ppx/react/expected/fileLevelConfig.res_v4_auto.txt +++ b/tests/ppx/react/expected/fileLevelConfig.res_v4_auto.txt @@ -1,35 +1,32 @@ @@jsxConfig({version: 4, mode: "automatic"}) -@obj external makeProps: (~msg: 'msg, ~key: string=?, unit) => {"msg": 'msg} = "" +type props<'msg> = {key?: string, msg: 'msg} -let make = - (@warning("-16") ~msg) => { - ReactDOM.jsx("div", {children: {msg->React.string}}) - } +let make = ({msg, _}: props<'msg>) => { + ReactDOM.jsx("div", {children: {msg->React.string}}) +} let make = { - let \"FileLevelConfig" = (\"Props": {"msg": 'msg}) => make(~msg=\"Props"["msg"]) + let \"FileLevelConfig" = (props: props<_>) => make(props) \"FileLevelConfig" } @@jsxConfig({version: 4, mode: "classic"}) -@obj external makeProps: (~msg: 'msg, ~key: string=?, unit) => {"msg": 'msg} = "" +type props<'msg> = {key?: string, msg: 'msg} -let make = - (@warning("-16") ~msg) => { - ReactDOMRe.createDOMElementVariadic("div", [{msg->React.string}]) - } +let make = ({msg, _}: props<'msg>) => { + ReactDOM.jsx("div", {children: {msg->React.string}}) +} let make = { - let \"FileLevelConfig" = (\"Props": {"msg": 'msg}) => make(~msg=\"Props"["msg"]) + let \"FileLevelConfig" = (props: props<_>) => make(props) \"FileLevelConfig" } @@jsxConfig({version: 3}) -@obj external makeProps: (~msg: 'msg, ~key: string=?, unit) => {"msg": 'msg} = "" +type props<'msg> = {key?: string, msg: 'msg} -let make = - (@warning("-16") ~msg) => { - ReactDOMRe.createDOMElementVariadic("div", [{msg->React.string}]) - } +let make = ({msg, _}: props<'msg>) => { + ReactDOM.jsx("div", {children: {msg->React.string}}) +} let make = { - let \"FileLevelConfig" = (\"Props": {"msg": 'msg}) => make(~msg=\"Props"["msg"]) + let \"FileLevelConfig" = (props: props<_>) => make(props) \"FileLevelConfig" } diff --git a/tests/ppx/react/expected/fileLevelConfig.res_v4_cls.txt b/tests/ppx/react/expected/fileLevelConfig.res_v4_cls.txt index b1e38d43..ebd0d741 100644 --- a/tests/ppx/react/expected/fileLevelConfig.res_v4_cls.txt +++ b/tests/ppx/react/expected/fileLevelConfig.res_v4_cls.txt @@ -1,35 +1,32 @@ @@jsxConfig({version: 4, mode: "automatic"}) -@obj external makeProps: (~msg: 'msg, ~key: string=?, unit) => {"msg": 'msg} = "" +type props<'msg> = {key?: string, msg: 'msg} -let make = - (@warning("-16") ~msg) => { - ReactDOM.jsx("div", {children: {msg->React.string}}) - } +let make = ({msg, _}: props<'msg>) => { + ReactDOMRe.createDOMElementVariadic("div", [{msg->React.string}]) +} let make = { - let \"FileLevelConfig" = (\"Props": {"msg": 'msg}) => make(~msg=\"Props"["msg"]) + let \"FileLevelConfig" = (props: props<_>) => make(props) \"FileLevelConfig" } @@jsxConfig({version: 4, mode: "classic"}) -@obj external makeProps: (~msg: 'msg, ~key: string=?, unit) => {"msg": 'msg} = "" +type props<'msg> = {key?: string, msg: 'msg} -let make = - (@warning("-16") ~msg) => { - ReactDOMRe.createDOMElementVariadic("div", [{msg->React.string}]) - } +let make = ({msg, _}: props<'msg>) => { + ReactDOMRe.createDOMElementVariadic("div", [{msg->React.string}]) +} let make = { - let \"FileLevelConfig" = (\"Props": {"msg": 'msg}) => make(~msg=\"Props"["msg"]) + let \"FileLevelConfig" = (props: props<_>) => make(props) \"FileLevelConfig" } @@jsxConfig({version: 3}) -@obj external makeProps: (~msg: 'msg, ~key: string=?, unit) => {"msg": 'msg} = "" +type props<'msg> = {key?: string, msg: 'msg} -let make = - (@warning("-16") ~msg) => { - ReactDOMRe.createDOMElementVariadic("div", [{msg->React.string}]) - } +let make = ({msg, _}: props<'msg>) => { + ReactDOMRe.createDOMElementVariadic("div", [{msg->React.string}]) +} let make = { - let \"FileLevelConfig" = (\"Props": {"msg": 'msg}) => make(~msg=\"Props"["msg"]) + let \"FileLevelConfig" = (props: props<_>) => make(props) \"FileLevelConfig" } From 64f49b5fc4ddac740c3db67def7cb971f2852975 Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Thu, 7 Jul 2022 21:30:17 +0200 Subject: [PATCH 64/94] Handle config update directly. --- cli/reactjs_jsx_ppx.ml | 29 +++++-------------- .../react/expected/fileLevelConfig.res_v3.txt | 22 +++++++------- .../expected/fileLevelConfig.res_v4_auto.txt | 13 +++++---- .../expected/fileLevelConfig.res_v4_cls.txt | 13 +++++---- 4 files changed, 31 insertions(+), 46 deletions(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index 14ed235f..13b6586d 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -2707,19 +2707,6 @@ module V4 = struct end let getMapper ~config = - let structure_item mapper item = - (match item.pstr_desc with - | Pstr_attribute attr -> V4.processConfigAttribute attr config - | _ -> ()); - default_mapper.structure_item mapper item - in - let signature_item mapper item = - (match item.psig_desc with - | Psig_attribute attr -> V4.processConfigAttribute attr config - | _ -> ()); - default_mapper.signature_item mapper item - in - let expr3, module_binding3, transformSignatureItem3, transformStructureItem3 = V3.jsxMapper ~config in @@ -2742,6 +2729,9 @@ let getMapper ~config = let signature mapper items = List.map (fun item -> + (match item.psig_desc with + | Psig_attribute attr -> V4.processConfigAttribute attr config + | _ -> ()); let item = default_mapper.signature_item mapper item in if config.version = 3 then transformSignatureItem3 mapper item else if config.version = 4 then transformSignatureItem4 mapper item @@ -2753,6 +2743,9 @@ let getMapper ~config = let structure mapper items = List.map (fun item -> + (match item.pstr_desc with + | Pstr_attribute attr -> V4.processConfigAttribute attr config + | _ -> ()); let item = default_mapper.structure_item mapper item in if config.version = 3 then transformStructureItem3 mapper item else if config.version = 4 then @@ -2763,15 +2756,7 @@ let getMapper ~config = [@@raises Invalid_argument] in - { - default_mapper with - expr; - module_binding; - signature; - signature_item; - structure; - structure_item; - } + {default_mapper with expr; module_binding; signature; structure} let rewrite_implementation ~jsxVersion ~jsxModule ~jsxMode (code : Parsetree.structure) : Parsetree.structure = diff --git a/tests/ppx/react/expected/fileLevelConfig.res_v3.txt b/tests/ppx/react/expected/fileLevelConfig.res_v3.txt index ffb75d4a..810292e5 100644 --- a/tests/ppx/react/expected/fileLevelConfig.res_v3.txt +++ b/tests/ppx/react/expected/fileLevelConfig.res_v3.txt @@ -1,24 +1,22 @@ @@jsxConfig({version: 4, mode: "automatic"}) -@obj external makeProps: (~msg: 'msg, ~key: string=?, unit) => {"msg": 'msg} = "" +type props<'msg> = {key?: string, msg: 'msg} -let make = - (@warning("-16") ~msg) => { - ReactDOMRe.createDOMElementVariadic("div", [{msg->React.string}]) - } +let make = ({msg, _}: props<'msg>) => { + ReactDOM.jsx("div", {children: {msg->React.string}}) +} let make = { - let \"FileLevelConfig" = (\"Props": {"msg": 'msg}) => make(~msg=\"Props"["msg"]) + let \"FileLevelConfig" = (props: props<_>) => make(props) \"FileLevelConfig" } @@jsxConfig({version: 4, mode: "classic"}) -@obj external makeProps: (~msg: 'msg, ~key: string=?, unit) => {"msg": 'msg} = "" +type props<'msg> = {key?: string, msg: 'msg} -let make = - (@warning("-16") ~msg) => { - ReactDOMRe.createDOMElementVariadic("div", [{msg->React.string}]) - } +let make = ({msg, _}: props<'msg>) => { + ReactDOMRe.createDOMElementVariadic("div", [{msg->React.string}]) +} let make = { - let \"FileLevelConfig" = (\"Props": {"msg": 'msg}) => make(~msg=\"Props"["msg"]) + let \"FileLevelConfig" = (props: props<_>) => make(props) \"FileLevelConfig" } diff --git a/tests/ppx/react/expected/fileLevelConfig.res_v4_auto.txt b/tests/ppx/react/expected/fileLevelConfig.res_v4_auto.txt index 3cba4f6e..810292e5 100644 --- a/tests/ppx/react/expected/fileLevelConfig.res_v4_auto.txt +++ b/tests/ppx/react/expected/fileLevelConfig.res_v4_auto.txt @@ -13,7 +13,7 @@ let make = { type props<'msg> = {key?: string, msg: 'msg} let make = ({msg, _}: props<'msg>) => { - ReactDOM.jsx("div", {children: {msg->React.string}}) + ReactDOMRe.createDOMElementVariadic("div", [{msg->React.string}]) } let make = { let \"FileLevelConfig" = (props: props<_>) => make(props) @@ -21,12 +21,13 @@ let make = { } @@jsxConfig({version: 3}) -type props<'msg> = {key?: string, msg: 'msg} +@obj external makeProps: (~msg: 'msg, ~key: string=?, unit) => {"msg": 'msg} = "" -let make = ({msg, _}: props<'msg>) => { - ReactDOM.jsx("div", {children: {msg->React.string}}) -} +let make = + (@warning("-16") ~msg) => { + ReactDOMRe.createDOMElementVariadic("div", [{msg->React.string}]) + } let make = { - let \"FileLevelConfig" = (props: props<_>) => make(props) + let \"FileLevelConfig" = (\"Props": {"msg": 'msg}) => make(~msg=\"Props"["msg"]) \"FileLevelConfig" } diff --git a/tests/ppx/react/expected/fileLevelConfig.res_v4_cls.txt b/tests/ppx/react/expected/fileLevelConfig.res_v4_cls.txt index ebd0d741..810292e5 100644 --- a/tests/ppx/react/expected/fileLevelConfig.res_v4_cls.txt +++ b/tests/ppx/react/expected/fileLevelConfig.res_v4_cls.txt @@ -2,7 +2,7 @@ type props<'msg> = {key?: string, msg: 'msg} let make = ({msg, _}: props<'msg>) => { - ReactDOMRe.createDOMElementVariadic("div", [{msg->React.string}]) + ReactDOM.jsx("div", {children: {msg->React.string}}) } let make = { let \"FileLevelConfig" = (props: props<_>) => make(props) @@ -21,12 +21,13 @@ let make = { } @@jsxConfig({version: 3}) -type props<'msg> = {key?: string, msg: 'msg} +@obj external makeProps: (~msg: 'msg, ~key: string=?, unit) => {"msg": 'msg} = "" -let make = ({msg, _}: props<'msg>) => { - ReactDOMRe.createDOMElementVariadic("div", [{msg->React.string}]) -} +let make = + (@warning("-16") ~msg) => { + ReactDOMRe.createDOMElementVariadic("div", [{msg->React.string}]) + } let make = { - let \"FileLevelConfig" = (props: props<_>) => make(props) + let \"FileLevelConfig" = (\"Props": {"msg": 'msg}) => make(~msg=\"Props"["msg"]) \"FileLevelConfig" } From b739cb88526e1ad762b9955d3042ac991a9121d9 Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Thu, 7 Jul 2022 21:38:23 +0200 Subject: [PATCH 65/94] Move config processing outside V4. --- cli/reactjs_jsx_ppx.ml | 122 ++++++++++++++++++++--------------------- 1 file changed, 61 insertions(+), 61 deletions(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index 13b6586d..074fe643 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -11,6 +11,65 @@ type jsxConfig = { mutable nestedModules: string list; } +let getPayloadFields payload = + match payload with + | PStr + ({ + pstr_desc = + Pstr_eval ({pexp_desc = Pexp_record (recordFields, None)}, _); + } + :: _rest) -> + recordFields + | _ -> [] + +type configKey = Int | String + +let getJsxConfigByKey ~key ~type_ recordFields = + let values = + List.filter_map + (fun ((lid, expr) : Longident.t Location.loc * expression) -> + match (type_, lid, expr) with + | ( Int, + {txt = Lident k}, + {pexp_desc = Pexp_constant (Pconst_integer (value, None))} ) + when k = key -> + Some value + | ( String, + {txt = Lident k}, + {pexp_desc = Pexp_constant (Pconst_string (value, None))} ) + when k = key -> + Some value + | _ -> None) + recordFields + in + match values with + | [] -> None + | [v] | v :: _ -> Some v + +let getInt ~key fields = + match fields |> getJsxConfigByKey ~key ~type_:Int with + | None -> None + | Some s -> int_of_string_opt s + +let getString ~key fields = fields |> getJsxConfigByKey ~key ~type_:String + +let updateConfig config payload = + let fields = getPayloadFields payload in + (match getInt ~key:"version" fields with + | None -> () + | Some i -> config.version <- i); + (match getString ~key:"module" fields with + | None -> () + | Some s -> config.module_ <- s); + match getString ~key:"mode" fields with + | None -> () + | Some s -> config.mode <- s + +let isJsxConfigAttr ((loc, _) : attribute) = loc.txt = "jsxConfig" + +let processConfigAttribute attribute config = + if isJsxConfigAttr attribute then updateConfig config (snd attribute) + module V3 = struct let rec find_opt p = function | [] -> None @@ -1273,65 +1332,6 @@ module V3 = struct end module V4 = struct - let getJsxConfig payload = - match payload with - | PStr - ({ - pstr_desc = - Pstr_eval ({pexp_desc = Pexp_record (recordFields, None)}, _); - } - :: _rest) -> - recordFields - | _ -> [] - - type configKey = Int | String - - let getJsxConfigByKey ~key ~type_ recordFields = - let values = - List.filter_map - (fun ((lid, expr) : Longident.t Location.loc * expression) -> - match (type_, lid, expr) with - | ( Int, - {txt = Lident k}, - {pexp_desc = Pexp_constant (Pconst_integer (value, None))} ) - when k = key -> - Some value - | ( String, - {txt = Lident k}, - {pexp_desc = Pexp_constant (Pconst_string (value, None))} ) - when k = key -> - Some value - | _ -> None) - recordFields - in - match values with - | [] -> None - | [v] | v :: _ -> Some v - - let getInt ~key fields = - match fields |> getJsxConfigByKey ~key ~type_:Int with - | None -> None - | Some s -> int_of_string_opt s - - let getString ~key fields = fields |> getJsxConfigByKey ~key ~type_:String - - let updateConfig config payload = - let fields = getJsxConfig payload in - (match getInt ~key:"version" fields with - | None -> () - | Some i -> config.version <- i); - (match getString ~key:"module" fields with - | None -> () - | Some s -> config.module_ <- s); - match getString ~key:"mode" fields with - | None -> () - | Some s -> config.mode <- s - - let isJsxConfigAttr ((loc, _) : attribute) = loc.txt = "jsxConfig" - - let processConfigAttribute attribute config = - if isJsxConfigAttr attribute then updateConfig config (snd attribute) - let rec find_opt p = function | [] -> None | x :: l -> if p x then Some x else find_opt p l @@ -2730,7 +2730,7 @@ let getMapper ~config = List.map (fun item -> (match item.psig_desc with - | Psig_attribute attr -> V4.processConfigAttribute attr config + | Psig_attribute attr -> processConfigAttribute attr config | _ -> ()); let item = default_mapper.signature_item mapper item in if config.version = 3 then transformSignatureItem3 mapper item @@ -2744,7 +2744,7 @@ let getMapper ~config = List.map (fun item -> (match item.pstr_desc with - | Pstr_attribute attr -> V4.processConfigAttribute attr config + | Pstr_attribute attr -> processConfigAttribute attr config | _ -> ()); let item = default_mapper.structure_item mapper item in if config.version = 3 then transformStructureItem3 mapper item From b6ee6746066e110de59ca0255c0bf867e7e41cf5 Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Fri, 8 Jul 2022 01:33:44 +0200 Subject: [PATCH 66/94] Restore config when getting out of a submodule. --- cli/reactjs_jsx_ppx.ml | 69 ++++++++++++++++++++++++++++-------------- 1 file changed, 46 insertions(+), 23 deletions(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index 074fe643..a04cda60 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -2726,33 +2726,56 @@ let getMapper ~config = | 4 -> module_binding4 mapper mb | _ -> default_mapper.module_binding mapper mb in + let saveConfig () = + { + config with + version = config.version; + module_ = config.module_; + mode = config.mode; + } + in + let restoreConfig oldConfig = + config.version <- oldConfig.version; + config.module_ <- oldConfig.module_; + config.mode <- oldConfig.mode + in let signature mapper items = - List.map - (fun item -> - (match item.psig_desc with - | Psig_attribute attr -> processConfigAttribute attr config - | _ -> ()); - let item = default_mapper.signature_item mapper item in - if config.version = 3 then transformSignatureItem3 mapper item - else if config.version = 4 then transformSignatureItem4 mapper item - else [item]) - items - |> List.flatten + let oldConfig = saveConfig () in + let result = + List.map + (fun item -> + (match item.psig_desc with + | Psig_attribute attr -> processConfigAttribute attr config + | _ -> ()); + let item = default_mapper.signature_item mapper item in + if config.version = 3 then transformSignatureItem3 mapper item + else if config.version = 4 then transformSignatureItem4 mapper item + else [item]) + items + |> List.flatten + in + restoreConfig oldConfig; + result [@@raises Invalid_argument] in let structure mapper items = - List.map - (fun item -> - (match item.pstr_desc with - | Pstr_attribute attr -> processConfigAttribute attr config - | _ -> ()); - let item = default_mapper.structure_item mapper item in - if config.version = 3 then transformStructureItem3 mapper item - else if config.version = 4 then - transformStructureItem4 ~config mapper item - else [item]) - items - |> List.flatten + let oldConfig = saveConfig () in + let result = + List.map + (fun item -> + (match item.pstr_desc with + | Pstr_attribute attr -> processConfigAttribute attr config + | _ -> ()); + let item = default_mapper.structure_item mapper item in + if config.version = 3 then transformStructureItem3 mapper item + else if config.version = 4 then + transformStructureItem4 ~config mapper item + else [item]) + items + |> List.flatten + in + restoreConfig oldConfig; + result [@@raises Invalid_argument] in From 095106224bdbf93536405c03697925e260c2d3c5 Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Fri, 8 Jul 2022 01:38:14 +0200 Subject: [PATCH 67/94] cleanup tranformStructureItem4 --- cli/reactjs_jsx_ppx.ml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index a04cda60..590a770e 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -2700,8 +2700,9 @@ module V4 = struct (* TODO: some line number might still be wrong *) let jsxMapper ~config = - let module_binding = module_binding ~config in let expr = expr ~config in + let module_binding = module_binding ~config in + let transformStructureItem = transformStructureItem ~config in (expr, module_binding, transformSignatureItem, transformStructureItem) [@@raises Invalid_argument, Failure] end @@ -2768,8 +2769,7 @@ let getMapper ~config = | _ -> ()); let item = default_mapper.structure_item mapper item in if config.version = 3 then transformStructureItem3 mapper item - else if config.version = 4 then - transformStructureItem4 ~config mapper item + else if config.version = 4 then transformStructureItem4 mapper item else [item]) items |> List.flatten From 4b42894ef07f0f7725951a03881a0a27a2621c4b Mon Sep 17 00:00:00 2001 From: Woonki Moon Date: Sat, 9 Jul 2022 18:13:29 +0900 Subject: [PATCH 68/94] fix props type params for external definition --- cli/reactjs_jsx_ppx.ml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index 590a770e..8138340e 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -1594,6 +1594,13 @@ module V4 = struct if label = "key" || label = "ref" then None else Some (Typ.var label, Invariant)) + (* make props type for external *) + (* external make: React.componentLike, React.element> = "default" *) + let makePropsTypeExternal namedTypeList = + namedTypeList + |> List.filter_map (fun (_isOptional, label, _, interiorType) -> + if label = "key" || label = "ref" then None else Some interiorType) + (* make type params for make fn arguments *) (* let make = ({id, name, children}: props<'id, 'name, 'children>) *) let makePropsTypeParams namedTypeList = @@ -2054,7 +2061,7 @@ module V4 = struct let retPropsType = Typ.constr ~loc:pstr_loc (Location.mkloc (Lident "props") pstr_loc) - (makePropsTypeParams namedTypeList) + (makePropsTypeExternal namedTypeList) in (* type props<'id, 'name> = { @optional key: string, @optional id: 'id, ... } *) let propsRecordType = From 97df3e9390eb3fa93b711e2d81ddf050b6312f75 Mon Sep 17 00:00:00 2001 From: Woonki Moon Date: Sat, 9 Jul 2022 18:47:39 +0900 Subject: [PATCH 69/94] clean up make props type params --- cli/reactjs_jsx_ppx.ml | 35 ++++++++----------- .../externalWithCustomName.res_v4_auto.txt | 2 +- .../externalWithCustomName.res_v4_cls.txt | 2 +- .../react/expected/forwardRef.res_v4_auto.txt | 2 +- .../react/expected/forwardRef.res_v4_cls.txt | 2 +- .../react/expected/newtype.res_v4_auto.txt | 10 ++++-- .../ppx/react/expected/newtype.res_v4_cls.txt | 10 ++++-- 7 files changed, 34 insertions(+), 29 deletions(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index 8138340e..8a437e42 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -1587,30 +1587,17 @@ module V4 = struct pexp_attributes = []; } - (* make type params for type props<'id, 'name, ...> *) - let makePropsTypeParamsTvar namedTypeList = - namedTypeList - |> List.filter_map (fun (_, label, _, _) -> - if label = "key" || label = "ref" then None - else Some (Typ.var label, Invariant)) - - (* make props type for external *) - (* external make: React.componentLike, React.element> = "default" *) - let makePropsTypeExternal namedTypeList = - namedTypeList - |> List.filter_map (fun (_isOptional, label, _, interiorType) -> - if label = "key" || label = "ref" then None else Some interiorType) - (* make type params for make fn arguments *) (* let make = ({id, name, children}: props<'id, 'name, 'children>) *) - let makePropsTypeParams namedTypeList = + let makePropsTypeParamsTvar namedTypeList = namedTypeList |> List.filter_map (fun (_isOptional, label, _, _interiorType) -> if label = "key" || label = "ref" then None else Some (Typ.var label)) - (* make type params for make sig arguments *) + (* make type params for make sig arguments and for external *) (* let make: React.componentLike>, React.element> *) - let makePropsTypeParamsSig namedTypeList = + (* external make: React.componentLike, React.element> = "default" *) + let makePropsTypeParams namedTypeList = namedTypeList |> List.filter_map (fun (_isOptional, label, _, interiorType) -> if label = "key" || label = "ref" then None else Some interiorType) @@ -1633,7 +1620,10 @@ module V4 = struct else Type.field ~loc {txt = label; loc} (Typ.var label)) in (* 'id, 'className, ... *) - let params = makePropsTypeParamsTvar namedTypeList in + let params = + makePropsTypeParamsTvar namedTypeList + |> List.map (fun coreType -> (coreType, Invariant)) + in Str.type_ Nonrecursive [ Type.mk ~loc ~params {txt = propsName; loc} @@ -1653,7 +1643,10 @@ module V4 = struct (Typ.var label) else Type.field ~loc {txt = label; loc} (Typ.var label)) in - let params = makePropsTypeParamsTvar namedTypeList in + let params = + makePropsTypeParamsTvar namedTypeList + |> List.map (fun coreType -> (coreType, Invariant)) + in Sig.type_ Nonrecursive [ Type.mk ~loc ~params {txt = propsName; loc} @@ -2061,7 +2054,7 @@ module V4 = struct let retPropsType = Typ.constr ~loc:pstr_loc (Location.mkloc (Lident "props") pstr_loc) - (makePropsTypeExternal namedTypeList) + (makePropsTypeParams namedTypeList) in (* type props<'id, 'name> = { @optional key: string, @optional id: 'id, ... } *) let propsRecordType = @@ -2524,7 +2517,7 @@ module V4 = struct let retPropsType = Typ.constr (Location.mkloc (Lident "props") psig_loc) - (makePropsTypeParamsSig namedTypeList) + (makePropsTypeParams namedTypeList) in let propsRecordType = makePropsRecordTypeSig "props" Location.none diff --git a/tests/ppx/react/expected/externalWithCustomName.res_v4_auto.txt b/tests/ppx/react/expected/externalWithCustomName.res_v4_auto.txt index 49e3e620..1f974fbc 100644 --- a/tests/ppx/react/expected/externalWithCustomName.res_v4_auto.txt +++ b/tests/ppx/react/expected/externalWithCustomName.res_v4_auto.txt @@ -1,7 +1,7 @@ module Foo = { type props<'a, 'b> = {key?: string, a: 'a, b: 'b} @module("Foo") - external component: React.componentLike, React.element> = "component" + external component: React.componentLike, React.element> = "component" } let t = React.jsx(Foo.component, {a: 1, b: "1"}) diff --git a/tests/ppx/react/expected/externalWithCustomName.res_v4_cls.txt b/tests/ppx/react/expected/externalWithCustomName.res_v4_cls.txt index 11d1b752..ad7abf41 100644 --- a/tests/ppx/react/expected/externalWithCustomName.res_v4_cls.txt +++ b/tests/ppx/react/expected/externalWithCustomName.res_v4_cls.txt @@ -1,7 +1,7 @@ module Foo = { type props<'a, 'b> = {key?: string, a: 'a, b: 'b} @module("Foo") - external component: React.componentLike, React.element> = "component" + external component: React.componentLike, React.element> = "component" } let t = React.createElement(Foo.component, {a: 1, b: "1"}) diff --git a/tests/ppx/react/expected/forwardRef.res_v4_auto.txt b/tests/ppx/react/expected/forwardRef.res_v4_auto.txt index 6c257892..811764c4 100644 --- a/tests/ppx/react/expected/forwardRef.res_v4_auto.txt +++ b/tests/ppx/react/expected/forwardRef.res_v4_auto.txt @@ -5,7 +5,7 @@ module FancyInput = { children: 'children, ref?: ReactDOM.Ref.currentDomRef, } - let make = ({?className, children, ?ref, _}: props<'className, 'children>) => { + let make = ({?className, children, ?ref, _}: props, 'children>) => { let ref = Js.Nullable.fromOption(ref) let _ = ref diff --git a/tests/ppx/react/expected/forwardRef.res_v4_cls.txt b/tests/ppx/react/expected/forwardRef.res_v4_cls.txt index 3998fda1..70078553 100644 --- a/tests/ppx/react/expected/forwardRef.res_v4_cls.txt +++ b/tests/ppx/react/expected/forwardRef.res_v4_cls.txt @@ -5,7 +5,7 @@ module FancyInput = { children: 'children, ref?: ReactDOM.Ref.currentDomRef, } - let make = ({?className, children, ?ref, _}: props<'className, 'children>) => { + let make = ({?className, children, ?ref, _}: props, 'children>) => { let ref = Js.Nullable.fromOption(ref) let _ = ref diff --git a/tests/ppx/react/expected/newtype.res_v4_auto.txt b/tests/ppx/react/expected/newtype.res_v4_auto.txt index 914b80af..c7b8c8af 100644 --- a/tests/ppx/react/expected/newtype.res_v4_auto.txt +++ b/tests/ppx/react/expected/newtype.res_v4_auto.txt @@ -1,6 +1,12 @@ type props<'a, 'b, 'c> = {key?: string, a: 'a, b: 'b, c: 'c} -let make = (_: props<'a, 'b, 'c>, type a, ~a: a, ~b: array>, ~c: 'a, _) => - ReactDOM.jsx("div", {key: ?None}) +let make = ( + _: props>, 'a>, + type a, + ~a: a, + ~b: array>, + ~c: 'a, + _, +) => ReactDOM.jsx("div", {key: ?None}) let make = { let \"Newtype" = (props: props<_>) => make(props) \"Newtype" diff --git a/tests/ppx/react/expected/newtype.res_v4_cls.txt b/tests/ppx/react/expected/newtype.res_v4_cls.txt index 460c8382..38391b2a 100644 --- a/tests/ppx/react/expected/newtype.res_v4_cls.txt +++ b/tests/ppx/react/expected/newtype.res_v4_cls.txt @@ -1,6 +1,12 @@ type props<'a, 'b, 'c> = {key?: string, a: 'a, b: 'b, c: 'c} -let make = (_: props<'a, 'b, 'c>, type a, ~a: a, ~b: array>, ~c: 'a, _) => - ReactDOMRe.createDOMElementVariadic("div", []) +let make = ( + _: props>, 'a>, + type a, + ~a: a, + ~b: array>, + ~c: 'a, + _, +) => ReactDOMRe.createDOMElementVariadic("div", []) let make = { let \"Newtype" = (props: props<_>) => make(props) \"Newtype" From 0babb8c520ed255eae9a771caa463ec2db824980 Mon Sep 17 00:00:00 2001 From: Woonki Moon Date: Sat, 9 Jul 2022 23:33:23 +0900 Subject: [PATCH 70/94] test with jsx config update --- scripts/test.sh | 19 +- ...p.res_v4_auto.txt => commentAtTop.res.txt} | 0 .../react/expected/commentAtTop.res_v3.txt | 10 - .../expected/commentAtTop.res_v4_cls.txt | 9 - .../expected/externalWithCustomName.res.txt | 33 ++++ .../externalWithCustomName.res_v3.txt | 9 - .../externalWithCustomName.res_v4_auto.txt | 7 - .../externalWithCustomName.res_v4_cls.txt | 7 - ...fig.res_v3.txt => fileLevelConfig.res.txt} | 26 +-- .../expected/fileLevelConfig.res_v4_auto.txt | 33 ---- .../expected/fileLevelConfig.res_v4_cls.txt | 33 ---- tests/ppx/react/expected/forwardRef.res.txt | 173 ++++++++++++++++++ .../ppx/react/expected/forwardRef.res_v3.txt | 56 ------ .../react/expected/forwardRef.res_v4_auto.txt | 53 ------ .../react/expected/forwardRef.res_v4_cls.txt | 53 ------ tests/ppx/react/expected/innerModule.res.txt | 77 ++++++++ .../ppx/react/expected/innerModule.res_v3.txt | 25 --- .../expected/innerModule.res_v4_auto.txt | 21 --- .../react/expected/innerModule.res_v4_cls.txt | 21 --- tests/ppx/react/expected/newtype.res.txt | 49 +++++ tests/ppx/react/expected/newtype.res_v3.txt | 15 -- .../react/expected/newtype.res_v4_auto.txt | 13 -- .../ppx/react/expected/newtype.res_v4_cls.txt | 13 -- tests/ppx/react/expected/topLevel.res.txt | 36 ++++ tests/ppx/react/expected/topLevel.res_v3.txt | 10 - .../react/expected/topLevel.res_v4_auto.txt | 9 - .../react/expected/topLevel.res_v4_cls.txt | 9 - .../ppx/react/expected/typeConstraint.res.txt | 32 ++++ .../react/expected/typeConstraint.res_v3.txt | 8 - .../expected/typeConstraint.res_v4_auto.txt | 8 - .../expected/typeConstraint.res_v4_cls.txt | 8 - tests/ppx/react/externalWithCustomName.res | 20 ++ tests/ppx/react/fileLevelConfig.res | 4 +- tests/ppx/react/forwardRef.res | 48 +++++ tests/ppx/react/innerModule.res | 39 +++- tests/ppx/react/newtype.res | 12 ++ tests/ppx/react/topLevel.res | 18 ++ tests/ppx/react/typeConstraint.res | 20 +- 38 files changed, 567 insertions(+), 469 deletions(-) rename tests/ppx/react/expected/{commentAtTop.res_v4_auto.txt => commentAtTop.res.txt} (100%) delete mode 100644 tests/ppx/react/expected/commentAtTop.res_v3.txt delete mode 100644 tests/ppx/react/expected/commentAtTop.res_v4_cls.txt create mode 100644 tests/ppx/react/expected/externalWithCustomName.res.txt delete mode 100644 tests/ppx/react/expected/externalWithCustomName.res_v3.txt delete mode 100644 tests/ppx/react/expected/externalWithCustomName.res_v4_auto.txt delete mode 100644 tests/ppx/react/expected/externalWithCustomName.res_v4_cls.txt rename tests/ppx/react/expected/{fileLevelConfig.res_v3.txt => fileLevelConfig.res.txt} (100%) delete mode 100644 tests/ppx/react/expected/fileLevelConfig.res_v4_auto.txt delete mode 100644 tests/ppx/react/expected/fileLevelConfig.res_v4_cls.txt create mode 100644 tests/ppx/react/expected/forwardRef.res.txt delete mode 100644 tests/ppx/react/expected/forwardRef.res_v3.txt delete mode 100644 tests/ppx/react/expected/forwardRef.res_v4_auto.txt delete mode 100644 tests/ppx/react/expected/forwardRef.res_v4_cls.txt create mode 100644 tests/ppx/react/expected/innerModule.res.txt delete mode 100644 tests/ppx/react/expected/innerModule.res_v3.txt delete mode 100644 tests/ppx/react/expected/innerModule.res_v4_auto.txt delete mode 100644 tests/ppx/react/expected/innerModule.res_v4_cls.txt create mode 100644 tests/ppx/react/expected/newtype.res.txt delete mode 100644 tests/ppx/react/expected/newtype.res_v3.txt delete mode 100644 tests/ppx/react/expected/newtype.res_v4_auto.txt delete mode 100644 tests/ppx/react/expected/newtype.res_v4_cls.txt create mode 100644 tests/ppx/react/expected/topLevel.res.txt delete mode 100644 tests/ppx/react/expected/topLevel.res_v3.txt delete mode 100644 tests/ppx/react/expected/topLevel.res_v4_auto.txt delete mode 100644 tests/ppx/react/expected/topLevel.res_v4_cls.txt create mode 100644 tests/ppx/react/expected/typeConstraint.res.txt delete mode 100644 tests/ppx/react/expected/typeConstraint.res_v3.txt delete mode 100644 tests/ppx/react/expected/typeConstraint.res_v4_auto.txt delete mode 100644 tests/ppx/react/expected/typeConstraint.res_v4_cls.txt diff --git a/scripts/test.sh b/scripts/test.sh index 7be84526..54d1c07f 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -8,9 +8,6 @@ function exp { echo "$(dirname $1)/expected/$(basename $1).txt" } -function exp2 { - echo "$(dirname $1)/expected/$(basename $1)$2.txt" -} taskCount=0 function maybeWait { @@ -38,22 +35,10 @@ while read file; do rescript $file &> $(exp $file) & maybeWait done temp/files.txt -while read file; do - rescript -jsx-version 3 $file &> $(exp2 $file "_v3") & maybeWait -done temp/files.txt -while read file; do - rescript -jsx-version 4 -jsx-mode classic $file &> $(exp2 $file "_v4_cls") & maybeWait -done temp/files.txt while read file; do - rescript -jsx-version 4 -jsx-mode automatic $file &> $(exp2 $file "_v4_auto") & maybeWait + rescript -jsx-version 4 -jsx-mode "automatic" $file &> $(exp $file) & maybeWait done {"msg": 'msg} = "" // test React JSX file - -let make = - (@warning("-16") ~msg) => { - ReactDOMRe.createDOMElementVariadic("div", [{msg->React.string}]) - } -let make = { - let \"CommentAtTop" = (\"Props": {"msg": 'msg}) => make(~msg=\"Props"["msg"]) - \"CommentAtTop" -} diff --git a/tests/ppx/react/expected/commentAtTop.res_v4_cls.txt b/tests/ppx/react/expected/commentAtTop.res_v4_cls.txt deleted file mode 100644 index 65c67e50..00000000 --- a/tests/ppx/react/expected/commentAtTop.res_v4_cls.txt +++ /dev/null @@ -1,9 +0,0 @@ -type props<'msg> = {key?: string, msg: 'msg} // test React JSX file - -let make = ({msg, _}: props<'msg>) => { - ReactDOMRe.createDOMElementVariadic("div", [{msg->React.string}]) -} -let make = { - let \"CommentAtTop" = (props: props<_>) => make(props) - \"CommentAtTop" -} diff --git a/tests/ppx/react/expected/externalWithCustomName.res.txt b/tests/ppx/react/expected/externalWithCustomName.res.txt new file mode 100644 index 00000000..cdcf46cc --- /dev/null +++ b/tests/ppx/react/expected/externalWithCustomName.res.txt @@ -0,0 +1,33 @@ +@@jsxConfig({version: 3}) + +module Foo = { + @obj + external componentProps: (~a: int, ~b: string, ~key: string=?, unit) => {"a": int, "b": string} = + "" + @module("Foo") + external component: React.componentLike<{"a": int, "b": string}, React.element> = "component" +} + +let t = React.createElement(Foo.component, Foo.componentProps(~a=1, ~b={"1"}, ())) + +@@jsxConfig({version: 4, mode: "classic"}) + +module Foo = { + type props<'a, 'b> = {key?: string, a: 'a, b: 'b} + + @module("Foo") + external component: React.componentLike, React.element> = "component" +} + +let t = React.createElement(Foo.component, {a: 1, b: "1"}) + +@@jsxConfig({version: 4, mode: "automatic"}) + +module Foo = { + type props<'a, 'b> = {key?: string, a: 'a, b: 'b} + + @module("Foo") + external component: React.componentLike, React.element> = "component" +} + +let t = React.jsx(Foo.component, {a: 1, b: "1"}) diff --git a/tests/ppx/react/expected/externalWithCustomName.res_v3.txt b/tests/ppx/react/expected/externalWithCustomName.res_v3.txt deleted file mode 100644 index 083aeead..00000000 --- a/tests/ppx/react/expected/externalWithCustomName.res_v3.txt +++ /dev/null @@ -1,9 +0,0 @@ -module Foo = { - @obj - external componentProps: (~a: int, ~b: string, ~key: string=?, unit) => {"a": int, "b": string} = - "" - @module("Foo") - external component: React.componentLike<{"a": int, "b": string}, React.element> = "component" -} - -let t = React.createElement(Foo.component, Foo.componentProps(~a=1, ~b={"1"}, ())) diff --git a/tests/ppx/react/expected/externalWithCustomName.res_v4_auto.txt b/tests/ppx/react/expected/externalWithCustomName.res_v4_auto.txt deleted file mode 100644 index 1f974fbc..00000000 --- a/tests/ppx/react/expected/externalWithCustomName.res_v4_auto.txt +++ /dev/null @@ -1,7 +0,0 @@ -module Foo = { - type props<'a, 'b> = {key?: string, a: 'a, b: 'b} - @module("Foo") - external component: React.componentLike, React.element> = "component" -} - -let t = React.jsx(Foo.component, {a: 1, b: "1"}) diff --git a/tests/ppx/react/expected/externalWithCustomName.res_v4_cls.txt b/tests/ppx/react/expected/externalWithCustomName.res_v4_cls.txt deleted file mode 100644 index ad7abf41..00000000 --- a/tests/ppx/react/expected/externalWithCustomName.res_v4_cls.txt +++ /dev/null @@ -1,7 +0,0 @@ -module Foo = { - type props<'a, 'b> = {key?: string, a: 'a, b: 'b} - @module("Foo") - external component: React.componentLike, React.element> = "component" -} - -let t = React.createElement(Foo.component, {a: 1, b: "1"}) diff --git a/tests/ppx/react/expected/fileLevelConfig.res_v3.txt b/tests/ppx/react/expected/fileLevelConfig.res.txt similarity index 100% rename from tests/ppx/react/expected/fileLevelConfig.res_v3.txt rename to tests/ppx/react/expected/fileLevelConfig.res.txt index 810292e5..ce94b54b 100644 --- a/tests/ppx/react/expected/fileLevelConfig.res_v3.txt +++ b/tests/ppx/react/expected/fileLevelConfig.res.txt @@ -1,11 +1,12 @@ -@@jsxConfig({version: 4, mode: "automatic"}) -type props<'msg> = {key?: string, msg: 'msg} +@@jsxConfig({version: 3}) +@obj external makeProps: (~msg: 'msg, ~key: string=?, unit) => {"msg": 'msg} = "" -let make = ({msg, _}: props<'msg>) => { - ReactDOM.jsx("div", {children: {msg->React.string}}) -} +let make = + (@warning("-16") ~msg) => { + ReactDOMRe.createDOMElementVariadic("div", [{msg->React.string}]) + } let make = { - let \"FileLevelConfig" = (props: props<_>) => make(props) + let \"FileLevelConfig" = (\"Props": {"msg": 'msg}) => make(~msg=\"Props"["msg"]) \"FileLevelConfig" } @@ -20,14 +21,13 @@ let make = { \"FileLevelConfig" } -@@jsxConfig({version: 3}) -@obj external makeProps: (~msg: 'msg, ~key: string=?, unit) => {"msg": 'msg} = "" +@@jsxConfig({version: 4, mode: "automatic"}) +type props<'msg> = {key?: string, msg: 'msg} -let make = - (@warning("-16") ~msg) => { - ReactDOMRe.createDOMElementVariadic("div", [{msg->React.string}]) - } +let make = ({msg, _}: props<'msg>) => { + ReactDOM.jsx("div", {children: {msg->React.string}}) +} let make = { - let \"FileLevelConfig" = (\"Props": {"msg": 'msg}) => make(~msg=\"Props"["msg"]) + let \"FileLevelConfig" = (props: props<_>) => make(props) \"FileLevelConfig" } diff --git a/tests/ppx/react/expected/fileLevelConfig.res_v4_auto.txt b/tests/ppx/react/expected/fileLevelConfig.res_v4_auto.txt deleted file mode 100644 index 810292e5..00000000 --- a/tests/ppx/react/expected/fileLevelConfig.res_v4_auto.txt +++ /dev/null @@ -1,33 +0,0 @@ -@@jsxConfig({version: 4, mode: "automatic"}) -type props<'msg> = {key?: string, msg: 'msg} - -let make = ({msg, _}: props<'msg>) => { - ReactDOM.jsx("div", {children: {msg->React.string}}) -} -let make = { - let \"FileLevelConfig" = (props: props<_>) => make(props) - \"FileLevelConfig" -} - -@@jsxConfig({version: 4, mode: "classic"}) -type props<'msg> = {key?: string, msg: 'msg} - -let make = ({msg, _}: props<'msg>) => { - ReactDOMRe.createDOMElementVariadic("div", [{msg->React.string}]) -} -let make = { - let \"FileLevelConfig" = (props: props<_>) => make(props) - \"FileLevelConfig" -} - -@@jsxConfig({version: 3}) -@obj external makeProps: (~msg: 'msg, ~key: string=?, unit) => {"msg": 'msg} = "" - -let make = - (@warning("-16") ~msg) => { - ReactDOMRe.createDOMElementVariadic("div", [{msg->React.string}]) - } -let make = { - let \"FileLevelConfig" = (\"Props": {"msg": 'msg}) => make(~msg=\"Props"["msg"]) - \"FileLevelConfig" -} diff --git a/tests/ppx/react/expected/fileLevelConfig.res_v4_cls.txt b/tests/ppx/react/expected/fileLevelConfig.res_v4_cls.txt deleted file mode 100644 index 810292e5..00000000 --- a/tests/ppx/react/expected/fileLevelConfig.res_v4_cls.txt +++ /dev/null @@ -1,33 +0,0 @@ -@@jsxConfig({version: 4, mode: "automatic"}) -type props<'msg> = {key?: string, msg: 'msg} - -let make = ({msg, _}: props<'msg>) => { - ReactDOM.jsx("div", {children: {msg->React.string}}) -} -let make = { - let \"FileLevelConfig" = (props: props<_>) => make(props) - \"FileLevelConfig" -} - -@@jsxConfig({version: 4, mode: "classic"}) -type props<'msg> = {key?: string, msg: 'msg} - -let make = ({msg, _}: props<'msg>) => { - ReactDOMRe.createDOMElementVariadic("div", [{msg->React.string}]) -} -let make = { - let \"FileLevelConfig" = (props: props<_>) => make(props) - \"FileLevelConfig" -} - -@@jsxConfig({version: 3}) -@obj external makeProps: (~msg: 'msg, ~key: string=?, unit) => {"msg": 'msg} = "" - -let make = - (@warning("-16") ~msg) => { - ReactDOMRe.createDOMElementVariadic("div", [{msg->React.string}]) - } -let make = { - let \"FileLevelConfig" = (\"Props": {"msg": 'msg}) => make(~msg=\"Props"["msg"]) - \"FileLevelConfig" -} diff --git a/tests/ppx/react/expected/forwardRef.res.txt b/tests/ppx/react/expected/forwardRef.res.txt new file mode 100644 index 00000000..7cbea802 --- /dev/null +++ b/tests/ppx/react/expected/forwardRef.res.txt @@ -0,0 +1,173 @@ +@@jsxConfig({version: 3}) + +module FancyInput = { + @obj + external makeProps: ( + ~className: 'className=?, + ~children: 'children, + ~key: string=?, + ~ref: 'ref=?, + unit, + ) => {"className": option<'className>, "children": 'children} = "" + + let make = + (@warning("-16") ~className=?, @warning("-16") ~children) => + @warning("-16") + ref => + ReactDOMRe.createDOMElementVariadic( + "div", + [ + ReactDOMRe.createDOMElementVariadic( + "input", + ~props=ReactDOMRe.domProps( + ~type_="text", + ~className?, + ~ref=?{Js.Nullable.toOption(ref)->Belt.Option.map(ReactDOM.Ref.domRef)}, + (), + ), + [], + ), + children, + ], + ) + let make = React.forwardRef({ + let \"ForwardRef$FancyInput" = ( + \"Props": {"className": option<'className>, "children": 'children}, + ref, + ) => make(~children=\"Props"["children"], ~className=?\"Props"["className"], ref) + \"ForwardRef$FancyInput" + }) +} +@obj external makeProps: (~key: string=?, unit) => {.} = "" + +let make = () => { + let input = React.useRef(Js.Nullable.null) + + ReactDOMRe.createDOMElementVariadic( + "div", + [ + React.createElement( + FancyInput.make, + FancyInput.makeProps(~ref=input, ~children={React.string("Click to focus")}, ()), + ), + ], + ) +} +let make = { + let \"ForwardRef" = (\"Props": {.}) => make() + \"ForwardRef" +} + +@@jsxConfig({version: 4, mode: "classic"}) + +module FancyInput = { + type props<'className, 'children> = { + key?: string, + className?: 'className, + children: 'children, + ref?: ReactDOM.Ref.currentDomRef, + } + + let make = ({?className, children, ?ref, _}: props, 'children>) => { + let ref = Js.Nullable.fromOption(ref) + let _ = ref + + ReactDOMRe.createDOMElementVariadic( + "div", + [ + ReactDOMRe.createDOMElementVariadic( + "input", + ~props=ReactDOMRe.domProps( + ~type_="text", + ~className?, + ~ref=?{Js.Nullable.toOption(ref)->Belt.Option.map(ReactDOM.Ref.domRef)}, + (), + ), + [], + ), + children, + ], + ) + } + let make = React.forwardRef({ + let \"ForwardRef$FancyInput" = (props: props<_>, ref) => + make({...props, ref: ?Js.Nullable.toOption(ref)}) + \"ForwardRef$FancyInput" + }) +} +type props = {key?: string} + +let make = (_: props) => { + let input = React.useRef(Js.Nullable.null) + + ReactDOMRe.createDOMElementVariadic( + "div", + [ + React.createElement( + FancyInput.make, + {ref: input, children: {React.string("Click to focus")}}, + ), + ], + ) +} +let make = { + let \"ForwardRef" = props => make(props) + \"ForwardRef" +} + +@@jsxConfig({version: 4, mode: "automatic"}) + +module FancyInput = { + type props<'className, 'children> = { + key?: string, + className?: 'className, + children: 'children, + ref?: ReactDOM.Ref.currentDomRef, + } + + let make = ({?className, children, ?ref, _}: props, 'children>) => { + let ref = Js.Nullable.fromOption(ref) + let _ = ref + + ReactDOM.jsxs( + "div", + { + children: React.array([ + ReactDOM.jsx( + "input", + { + type_: "text", + ?className, + ref: ?Js.Nullable.toOption(ref)->Belt.Option.map(ReactDOM.Ref.domRef), + }, + ), + children, + ]), + }, + ) + } + let make = React.forwardRef({ + let \"ForwardRef$FancyInput" = (props: props<_>, ref) => + make({...props, ref: ?Js.Nullable.toOption(ref)}) + \"ForwardRef$FancyInput" + }) +} +type props = {key?: string} + +let make = (_: props) => { + let input = React.useRef(Js.Nullable.null) + + ReactDOM.jsx( + "div", + { + children: React.jsx( + FancyInput.make, + {ref: input, children: {React.string("Click to focus")}}, + ), + }, + ) +} +let make = { + let \"ForwardRef" = props => make(props) + \"ForwardRef" +} diff --git a/tests/ppx/react/expected/forwardRef.res_v3.txt b/tests/ppx/react/expected/forwardRef.res_v3.txt deleted file mode 100644 index 7ad6177f..00000000 --- a/tests/ppx/react/expected/forwardRef.res_v3.txt +++ /dev/null @@ -1,56 +0,0 @@ -module FancyInput = { - @obj - external makeProps: ( - ~className: 'className=?, - ~children: 'children, - ~key: string=?, - ~ref: 'ref=?, - unit, - ) => {"className": option<'className>, "children": 'children} = "" - let make = - (@warning("-16") ~className=?, @warning("-16") ~children) => - @warning("-16") - ref => - ReactDOMRe.createDOMElementVariadic( - "div", - [ - ReactDOMRe.createDOMElementVariadic( - "input", - ~props=ReactDOMRe.domProps( - ~type_="text", - ~className?, - ~ref=?{Js.Nullable.toOption(ref)->Belt.Option.map(ReactDOM.Ref.domRef)}, - (), - ), - [], - ), - children, - ], - ) - let make = React.forwardRef({ - let \"ForwardRef$FancyInput" = ( - \"Props": {"className": option<'className>, "children": 'children}, - ref, - ) => make(~children=\"Props"["children"], ~className=?\"Props"["className"], ref) - \"ForwardRef$FancyInput" - }) -} -@obj external makeProps: (~key: string=?, unit) => {.} = "" - -let make = () => { - let input = React.useRef(Js.Nullable.null) - - ReactDOMRe.createDOMElementVariadic( - "div", - [ - React.createElement( - FancyInput.make, - FancyInput.makeProps(~ref=input, ~children={React.string("Click to focus")}, ()), - ), - ], - ) -} -let make = { - let \"ForwardRef" = (\"Props": {.}) => make() - \"ForwardRef" -} diff --git a/tests/ppx/react/expected/forwardRef.res_v4_auto.txt b/tests/ppx/react/expected/forwardRef.res_v4_auto.txt deleted file mode 100644 index 811764c4..00000000 --- a/tests/ppx/react/expected/forwardRef.res_v4_auto.txt +++ /dev/null @@ -1,53 +0,0 @@ -module FancyInput = { - type props<'className, 'children> = { - key?: string, - className?: 'className, - children: 'children, - ref?: ReactDOM.Ref.currentDomRef, - } - let make = ({?className, children, ?ref, _}: props, 'children>) => { - let ref = Js.Nullable.fromOption(ref) - let _ = ref - - ReactDOM.jsxs( - "div", - { - children: React.array([ - ReactDOM.jsx( - "input", - { - type_: "text", - ?className, - ref: ?Js.Nullable.toOption(ref)->Belt.Option.map(ReactDOM.Ref.domRef), - }, - ), - children, - ]), - }, - ) - } - let make = React.forwardRef({ - let \"ForwardRef$FancyInput" = (props: props<_>, ref) => - make({...props, ref: ?Js.Nullable.toOption(ref)}) - \"ForwardRef$FancyInput" - }) -} -type props = {key?: string} - -let make = (_: props) => { - let input = React.useRef(Js.Nullable.null) - - ReactDOM.jsx( - "div", - { - children: React.jsx( - FancyInput.make, - {ref: input, children: {React.string("Click to focus")}}, - ), - }, - ) -} -let make = { - let \"ForwardRef" = props => make(props) - \"ForwardRef" -} diff --git a/tests/ppx/react/expected/forwardRef.res_v4_cls.txt b/tests/ppx/react/expected/forwardRef.res_v4_cls.txt deleted file mode 100644 index 70078553..00000000 --- a/tests/ppx/react/expected/forwardRef.res_v4_cls.txt +++ /dev/null @@ -1,53 +0,0 @@ -module FancyInput = { - type props<'className, 'children> = { - key?: string, - className?: 'className, - children: 'children, - ref?: ReactDOM.Ref.currentDomRef, - } - let make = ({?className, children, ?ref, _}: props, 'children>) => { - let ref = Js.Nullable.fromOption(ref) - let _ = ref - - ReactDOMRe.createDOMElementVariadic( - "div", - [ - ReactDOMRe.createDOMElementVariadic( - "input", - ~props=ReactDOMRe.domProps( - ~type_="text", - ~className?, - ~ref=?{Js.Nullable.toOption(ref)->Belt.Option.map(ReactDOM.Ref.domRef)}, - (), - ), - [], - ), - children, - ], - ) - } - let make = React.forwardRef({ - let \"ForwardRef$FancyInput" = (props: props<_>, ref) => - make({...props, ref: ?Js.Nullable.toOption(ref)}) - \"ForwardRef$FancyInput" - }) -} -type props = {key?: string} - -let make = (_: props) => { - let input = React.useRef(Js.Nullable.null) - - ReactDOMRe.createDOMElementVariadic( - "div", - [ - React.createElement( - FancyInput.make, - {ref: input, children: {React.string("Click to focus")}}, - ), - ], - ) -} -let make = { - let \"ForwardRef" = props => make(props) - \"ForwardRef" -} diff --git a/tests/ppx/react/expected/innerModule.res.txt b/tests/ppx/react/expected/innerModule.res.txt new file mode 100644 index 00000000..83e8a6ce --- /dev/null +++ b/tests/ppx/react/expected/innerModule.res.txt @@ -0,0 +1,77 @@ +@@jsxConfig({version: 3}) +module Bar = { + @obj external makeProps: (~a: 'a, ~b: 'b, ~key: string=?, unit) => {"a": 'a, "b": 'b} = "" + + let make = + (@warning("-16") ~a, @warning("-16") ~b, _) => { + Js.log("This function should be named `InnerModule.react$Bar`") + ReactDOMRe.createDOMElementVariadic("div", []) + } + let make = { + let \"InnerModule$Bar" = (\"Props": {"a": 'a, "b": 'b}) => + make(~b=\"Props"["b"], ~a=\"Props"["a"], ()) + \"InnerModule$Bar" + } + @obj external componentProps: (~a: 'a, ~b: 'b, ~key: string=?, unit) => {"a": 'a, "b": 'b} = "" + + let component = + (@warning("-16") ~a, @warning("-16") ~b, _) => { + Js.log("This function should be named `InnerModule.react$Bar$component`") + ReactDOMRe.createDOMElementVariadic("div", []) + } + let component = { + let \"InnerModule$Bar$component" = (\"Props": {"a": 'a, "b": 'b}) => + component(~b=\"Props"["b"], ~a=\"Props"["a"], ()) + \"InnerModule$Bar$component" + } +} + +@@jsxConfig({version: 4, mode: "classic"}) + +module Bar = { + type props<'a, 'b> = {key?: string, a: 'a, b: 'b} + + let make = ({a, b, _}: props<'a, 'b>) => { + Js.log("This function should be named `InnerModule.react$Bar`") + ReactDOMRe.createDOMElementVariadic("div", []) + } + let make = { + let \"InnerModule$Bar" = (props: props<_>) => make(props) + \"InnerModule$Bar" + } + type props<'a, 'b> = {key?: string, a: 'a, b: 'b} + + let component = ({a, b, _}: props<'a, 'b>) => { + Js.log("This function should be named `InnerModule.react$Bar$component`") + ReactDOMRe.createDOMElementVariadic("div", []) + } + let component = { + let \"InnerModule$Bar$component" = (props: props<_>) => make(props) + \"InnerModule$Bar$component" + } +} + +@@jsxConfig({version: 4, mode: "automatic"}) + +module Bar = { + type props<'a, 'b> = {key?: string, a: 'a, b: 'b} + + let make = ({a, b, _}: props<'a, 'b>) => { + Js.log("This function should be named `InnerModule.react$Bar`") + ReactDOM.jsx("div", {key: ?None}) + } + let make = { + let \"InnerModule$Bar" = (props: props<_>) => make(props) + \"InnerModule$Bar" + } + type props<'a, 'b> = {key?: string, a: 'a, b: 'b} + + let component = ({a, b, _}: props<'a, 'b>) => { + Js.log("This function should be named `InnerModule.react$Bar$component`") + ReactDOM.jsx("div", {key: ?None}) + } + let component = { + let \"InnerModule$Bar$component" = (props: props<_>) => make(props) + \"InnerModule$Bar$component" + } +} diff --git a/tests/ppx/react/expected/innerModule.res_v3.txt b/tests/ppx/react/expected/innerModule.res_v3.txt deleted file mode 100644 index 6c0e7369..00000000 --- a/tests/ppx/react/expected/innerModule.res_v3.txt +++ /dev/null @@ -1,25 +0,0 @@ -module Bar = { - @obj external makeProps: (~a: 'a, ~b: 'b, ~key: string=?, unit) => {"a": 'a, "b": 'b} = "" - let make = - (@warning("-16") ~a, @warning("-16") ~b, _) => { - Js.log("This function should be named `InnerModule.react$Bar`") - ReactDOMRe.createDOMElementVariadic("div", []) - } - let make = { - let \"InnerModule$Bar" = (\"Props": {"a": 'a, "b": 'b}) => - make(~b=\"Props"["b"], ~a=\"Props"["a"], ()) - \"InnerModule$Bar" - } - @obj external componentProps: (~a: 'a, ~b: 'b, ~key: string=?, unit) => {"a": 'a, "b": 'b} = "" - - let component = - (@warning("-16") ~a, @warning("-16") ~b, _) => { - Js.log("This function should be named `InnerModule.react$Bar$component`") - ReactDOMRe.createDOMElementVariadic("div", []) - } - let component = { - let \"InnerModule$Bar$component" = (\"Props": {"a": 'a, "b": 'b}) => - component(~b=\"Props"["b"], ~a=\"Props"["a"], ()) - \"InnerModule$Bar$component" - } -} diff --git a/tests/ppx/react/expected/innerModule.res_v4_auto.txt b/tests/ppx/react/expected/innerModule.res_v4_auto.txt deleted file mode 100644 index 16a8f994..00000000 --- a/tests/ppx/react/expected/innerModule.res_v4_auto.txt +++ /dev/null @@ -1,21 +0,0 @@ -module Bar = { - type props<'a, 'b> = {key?: string, a: 'a, b: 'b} - let make = ({a, b, _}: props<'a, 'b>) => { - Js.log("This function should be named `InnerModule.react$Bar`") - ReactDOM.jsx("div", {key: ?None}) - } - let make = { - let \"InnerModule$Bar" = (props: props<_>) => make(props) - \"InnerModule$Bar" - } - type props<'a, 'b> = {key?: string, a: 'a, b: 'b} - - let component = ({a, b, _}: props<'a, 'b>) => { - Js.log("This function should be named `InnerModule.react$Bar$component`") - ReactDOM.jsx("div", {key: ?None}) - } - let component = { - let \"InnerModule$Bar$component" = (props: props<_>) => make(props) - \"InnerModule$Bar$component" - } -} diff --git a/tests/ppx/react/expected/innerModule.res_v4_cls.txt b/tests/ppx/react/expected/innerModule.res_v4_cls.txt deleted file mode 100644 index acbf4822..00000000 --- a/tests/ppx/react/expected/innerModule.res_v4_cls.txt +++ /dev/null @@ -1,21 +0,0 @@ -module Bar = { - type props<'a, 'b> = {key?: string, a: 'a, b: 'b} - let make = ({a, b, _}: props<'a, 'b>) => { - Js.log("This function should be named `InnerModule.react$Bar`") - ReactDOMRe.createDOMElementVariadic("div", []) - } - let make = { - let \"InnerModule$Bar" = (props: props<_>) => make(props) - \"InnerModule$Bar" - } - type props<'a, 'b> = {key?: string, a: 'a, b: 'b} - - let component = ({a, b, _}: props<'a, 'b>) => { - Js.log("This function should be named `InnerModule.react$Bar$component`") - ReactDOMRe.createDOMElementVariadic("div", []) - } - let component = { - let \"InnerModule$Bar$component" = (props: props<_>) => make(props) - \"InnerModule$Bar$component" - } -} diff --git a/tests/ppx/react/expected/newtype.res.txt b/tests/ppx/react/expected/newtype.res.txt new file mode 100644 index 00000000..b254e737 --- /dev/null +++ b/tests/ppx/react/expected/newtype.res.txt @@ -0,0 +1,49 @@ +@@jsxConfig({version: 3}) +@obj +external makeProps: ( + ~a: '\"type-a", + ~b: array>, + ~c: 'a, + ~key: string=?, + unit, +) => {"a": '\"type-a", "b": array>, "c": 'a} = "" + +let make = (type a, ~a: a, ~b: array>, ~c: 'a, _) => + ReactDOMRe.createDOMElementVariadic("div", []) +let make = { + let \"Newtype" = (\"Props": {"a": '\"type-a", "b": array>, "c": 'a}) => + make(~c=\"Props"["c"], ~b=\"Props"["b"], ~a=\"Props"["a"]) + \"Newtype" +} + +@@jsxConfig({version: 4, mode: "classic"}) +type props<'a, 'b, 'c> = {key?: string, a: 'a, b: 'b, c: 'c} + +let make = ( + _: props>, 'a>, + type a, + ~a: a, + ~b: array>, + ~c: 'a, + _, +) => ReactDOMRe.createDOMElementVariadic("div", []) +let make = { + let \"Newtype" = (props: props<_>) => make(props) + \"Newtype" +} + +@@jsxConfig({version: 4, mode: "automatic"}) +type props<'a, 'b, 'c> = {key?: string, a: 'a, b: 'b, c: 'c} + +let make = ( + _: props>, 'a>, + type a, + ~a: a, + ~b: array>, + ~c: 'a, + _, +) => ReactDOM.jsx("div", {key: ?None}) +let make = { + let \"Newtype" = (props: props<_>) => make(props) + \"Newtype" +} diff --git a/tests/ppx/react/expected/newtype.res_v3.txt b/tests/ppx/react/expected/newtype.res_v3.txt deleted file mode 100644 index ace5106c..00000000 --- a/tests/ppx/react/expected/newtype.res_v3.txt +++ /dev/null @@ -1,15 +0,0 @@ -@obj -external makeProps: ( - ~a: '\"type-a", - ~b: array>, - ~c: 'a, - ~key: string=?, - unit, -) => {"a": '\"type-a", "b": array>, "c": 'a} = "" -let make = (type a, ~a: a, ~b: array>, ~c: 'a, _) => - ReactDOMRe.createDOMElementVariadic("div", []) -let make = { - let \"Newtype" = (\"Props": {"a": '\"type-a", "b": array>, "c": 'a}) => - make(~c=\"Props"["c"], ~b=\"Props"["b"], ~a=\"Props"["a"]) - \"Newtype" -} diff --git a/tests/ppx/react/expected/newtype.res_v4_auto.txt b/tests/ppx/react/expected/newtype.res_v4_auto.txt deleted file mode 100644 index c7b8c8af..00000000 --- a/tests/ppx/react/expected/newtype.res_v4_auto.txt +++ /dev/null @@ -1,13 +0,0 @@ -type props<'a, 'b, 'c> = {key?: string, a: 'a, b: 'b, c: 'c} -let make = ( - _: props>, 'a>, - type a, - ~a: a, - ~b: array>, - ~c: 'a, - _, -) => ReactDOM.jsx("div", {key: ?None}) -let make = { - let \"Newtype" = (props: props<_>) => make(props) - \"Newtype" -} diff --git a/tests/ppx/react/expected/newtype.res_v4_cls.txt b/tests/ppx/react/expected/newtype.res_v4_cls.txt deleted file mode 100644 index 38391b2a..00000000 --- a/tests/ppx/react/expected/newtype.res_v4_cls.txt +++ /dev/null @@ -1,13 +0,0 @@ -type props<'a, 'b, 'c> = {key?: string, a: 'a, b: 'b, c: 'c} -let make = ( - _: props>, 'a>, - type a, - ~a: a, - ~b: array>, - ~c: 'a, - _, -) => ReactDOMRe.createDOMElementVariadic("div", []) -let make = { - let \"Newtype" = (props: props<_>) => make(props) - \"Newtype" -} diff --git a/tests/ppx/react/expected/topLevel.res.txt b/tests/ppx/react/expected/topLevel.res.txt new file mode 100644 index 00000000..3a9529d9 --- /dev/null +++ b/tests/ppx/react/expected/topLevel.res.txt @@ -0,0 +1,36 @@ +@@jsxConfig({version: 3}) +@obj external makeProps: (~a: 'a, ~b: 'b, ~key: string=?, unit) => {"a": 'a, "b": 'b} = "" + +let make = + (@warning("-16") ~a, @warning("-16") ~b, _) => { + Js.log("This function should be named 'TopLevel.react'") + ReactDOMRe.createDOMElementVariadic("div", []) + } +let make = { + let \"TopLevel" = (\"Props": {"a": 'a, "b": 'b}) => make(~b=\"Props"["b"], ~a=\"Props"["a"], ()) + \"TopLevel" +} + +@@jsxConfig({version: 4, mode: "classic"}) +type props<'a, 'b> = {key?: string, a: 'a, b: 'b} + +let make = ({a, b, _}: props<'a, 'b>) => { + Js.log("This function should be named 'TopLevel.react'") + ReactDOMRe.createDOMElementVariadic("div", []) +} +let make = { + let \"TopLevel" = (props: props<_>) => make(props) + \"TopLevel" +} + +@@jsxConfig({version: 4, mode: "automatic"}) +type props<'a, 'b> = {key?: string, a: 'a, b: 'b} + +let make = ({a, b, _}: props<'a, 'b>) => { + Js.log("This function should be named 'TopLevel.react'") + ReactDOM.jsx("div", {key: ?None}) +} +let make = { + let \"TopLevel" = (props: props<_>) => make(props) + \"TopLevel" +} diff --git a/tests/ppx/react/expected/topLevel.res_v3.txt b/tests/ppx/react/expected/topLevel.res_v3.txt deleted file mode 100644 index b14eee2a..00000000 --- a/tests/ppx/react/expected/topLevel.res_v3.txt +++ /dev/null @@ -1,10 +0,0 @@ -@obj external makeProps: (~a: 'a, ~b: 'b, ~key: string=?, unit) => {"a": 'a, "b": 'b} = "" -let make = - (@warning("-16") ~a, @warning("-16") ~b, _) => { - Js.log("This function should be named 'TopLevel.react'") - ReactDOMRe.createDOMElementVariadic("div", []) - } -let make = { - let \"TopLevel" = (\"Props": {"a": 'a, "b": 'b}) => make(~b=\"Props"["b"], ~a=\"Props"["a"], ()) - \"TopLevel" -} diff --git a/tests/ppx/react/expected/topLevel.res_v4_auto.txt b/tests/ppx/react/expected/topLevel.res_v4_auto.txt deleted file mode 100644 index d732f6b3..00000000 --- a/tests/ppx/react/expected/topLevel.res_v4_auto.txt +++ /dev/null @@ -1,9 +0,0 @@ -type props<'a, 'b> = {key?: string, a: 'a, b: 'b} -let make = ({a, b, _}: props<'a, 'b>) => { - Js.log("This function should be named 'TopLevel.react'") - ReactDOM.jsx("div", {key: ?None}) -} -let make = { - let \"TopLevel" = (props: props<_>) => make(props) - \"TopLevel" -} diff --git a/tests/ppx/react/expected/topLevel.res_v4_cls.txt b/tests/ppx/react/expected/topLevel.res_v4_cls.txt deleted file mode 100644 index e4cc45c7..00000000 --- a/tests/ppx/react/expected/topLevel.res_v4_cls.txt +++ /dev/null @@ -1,9 +0,0 @@ -type props<'a, 'b> = {key?: string, a: 'a, b: 'b} -let make = ({a, b, _}: props<'a, 'b>) => { - Js.log("This function should be named 'TopLevel.react'") - ReactDOMRe.createDOMElementVariadic("div", []) -} -let make = { - let \"TopLevel" = (props: props<_>) => make(props) - \"TopLevel" -} diff --git a/tests/ppx/react/expected/typeConstraint.res.txt b/tests/ppx/react/expected/typeConstraint.res.txt new file mode 100644 index 00000000..433e2a53 --- /dev/null +++ b/tests/ppx/react/expected/typeConstraint.res.txt @@ -0,0 +1,32 @@ +@@jsxConfig({version: 3}) +@obj external makeProps: (~a: 'a, ~b: 'b, ~key: string=?, unit) => {"a": 'a, "b": 'b} = "" + +let make: + type a. (~a: a, ~b: a, a) => React.element = + (~a, ~b, _) => ReactDOMRe.createDOMElementVariadic("div", []) +let make = { + let \"TypeConstraint" = (\"Props": {"a": 'a, "b": 'b}) => make(~b=\"Props"["b"], ~a=\"Props"["a"]) + \"TypeConstraint" +} + +@@jsxConfig({version: 4, mode: "classic"}) +type props<'a, 'b> = {key?: string, a: 'a, b: 'b} + +let make: 'a. (~a: 'a, ~b: 'a, 'a) => React.element = (_: props<'a, 'b>, type a): ( + (~a: a, ~b: a, a) => React.element +) => (~a, ~b, _) => ReactDOMRe.createDOMElementVariadic("div", []) +let make = { + let \"TypeConstraint" = (props: props<_>) => make(props) + \"TypeConstraint" +} + +@@jsxConfig({version: 4, mode: "automatic"}) +type props<'a, 'b> = {key?: string, a: 'a, b: 'b} + +let make: 'a. (~a: 'a, ~b: 'a, 'a) => React.element = (_: props<'a, 'b>, type a): ( + (~a: a, ~b: a, a) => React.element +) => (~a, ~b, _) => ReactDOM.jsx("div", {key: ?None}) +let make = { + let \"TypeConstraint" = (props: props<_>) => make(props) + \"TypeConstraint" +} diff --git a/tests/ppx/react/expected/typeConstraint.res_v3.txt b/tests/ppx/react/expected/typeConstraint.res_v3.txt deleted file mode 100644 index 8940b164..00000000 --- a/tests/ppx/react/expected/typeConstraint.res_v3.txt +++ /dev/null @@ -1,8 +0,0 @@ -@obj external makeProps: (~a: 'a, ~b: 'b, ~key: string=?, unit) => {"a": 'a, "b": 'b} = "" -let make: - type a. (~a: a, ~b: a, a) => React.element = - (~a, ~b, _) => ReactDOMRe.createDOMElementVariadic("div", []) -let make = { - let \"TypeConstraint" = (\"Props": {"a": 'a, "b": 'b}) => make(~b=\"Props"["b"], ~a=\"Props"["a"]) - \"TypeConstraint" -} diff --git a/tests/ppx/react/expected/typeConstraint.res_v4_auto.txt b/tests/ppx/react/expected/typeConstraint.res_v4_auto.txt deleted file mode 100644 index a83d2519..00000000 --- a/tests/ppx/react/expected/typeConstraint.res_v4_auto.txt +++ /dev/null @@ -1,8 +0,0 @@ -type props<'a, 'b> = {key?: string, a: 'a, b: 'b} -let make: 'a. (~a: 'a, ~b: 'a, 'a) => React.element = (_: props<'a, 'b>, type a): ( - (~a: a, ~b: a, a) => React.element -) => (~a, ~b, _) => ReactDOM.jsx("div", {key: ?None}) -let make = { - let \"TypeConstraint" = (props: props<_>) => make(props) - \"TypeConstraint" -} diff --git a/tests/ppx/react/expected/typeConstraint.res_v4_cls.txt b/tests/ppx/react/expected/typeConstraint.res_v4_cls.txt deleted file mode 100644 index 50156ade..00000000 --- a/tests/ppx/react/expected/typeConstraint.res_v4_cls.txt +++ /dev/null @@ -1,8 +0,0 @@ -type props<'a, 'b> = {key?: string, a: 'a, b: 'b} -let make: 'a. (~a: 'a, ~b: 'a, 'a) => React.element = (_: props<'a, 'b>, type a): ( - (~a: a, ~b: a, a) => React.element -) => (~a, ~b, _) => ReactDOMRe.createDOMElementVariadic("div", []) -let make = { - let \"TypeConstraint" = (props: props<_>) => make(props) - \"TypeConstraint" -} diff --git a/tests/ppx/react/externalWithCustomName.res b/tests/ppx/react/externalWithCustomName.res index 3a4a3e71..fc3602fb 100644 --- a/tests/ppx/react/externalWithCustomName.res +++ b/tests/ppx/react/externalWithCustomName.res @@ -1,3 +1,23 @@ +@@jsxConfig({version: 3}) + +module Foo = { + @react.component @module("Foo") + external component: (~a: int, ~b: string, _) => React.element = "component" +} + +let t = + +@@jsxConfig({version: 4, mode: "classic"}) + +module Foo = { + @react.component @module("Foo") + external component: (~a: int, ~b: string, _) => React.element = "component" +} + +let t = + +@@jsxConfig({version: 4, mode: "automatic"}) + module Foo = { @react.component @module("Foo") external component: (~a: int, ~b: string, _) => React.element = "component" diff --git a/tests/ppx/react/fileLevelConfig.res b/tests/ppx/react/fileLevelConfig.res index 5880183b..a8eafd95 100644 --- a/tests/ppx/react/fileLevelConfig.res +++ b/tests/ppx/react/fileLevelConfig.res @@ -1,4 +1,4 @@ -@@jsxConfig({version: 4, mode: "automatic"}) +@@jsxConfig({version: 3}) @react.component let make = (~msg) => { @@ -12,7 +12,7 @@ let make = (~msg) => {
{msg->React.string}
} -@@jsxConfig({version: 3}) +@@jsxConfig({version: 4, mode: "automatic"}) @react.component let make = (~msg) => { diff --git a/tests/ppx/react/forwardRef.res b/tests/ppx/react/forwardRef.res index a34d1ea9..8010499b 100644 --- a/tests/ppx/react/forwardRef.res +++ b/tests/ppx/react/forwardRef.res @@ -1,3 +1,51 @@ +@@jsxConfig({version: 3}) + +module FancyInput = { + @react.component + let make = React.forwardRef((~className=?, ~children, ref) => +
+ Belt.Option.map(ReactDOM.Ref.domRef)} + /> + children +
+ ) +} + +@react.component +let make = () => { + let input = React.useRef(Js.Nullable.null) + +
{React.string("Click to focus")}
+} + +@@jsxConfig({version: 4, mode: "classic"}) + +module FancyInput = { + @react.component + let make = React.forwardRef((~className=?, ~children, ref) => +
+ Belt.Option.map(ReactDOM.Ref.domRef)} + /> + children +
+ ) +} + +@react.component +let make = () => { + let input = React.useRef(Js.Nullable.null) + +
{React.string("Click to focus")}
+} + +@@jsxConfig({version: 4, mode: "automatic"}) + module FancyInput = { @react.component let make = React.forwardRef((~className=?, ~children, ref) => diff --git a/tests/ppx/react/innerModule.res b/tests/ppx/react/innerModule.res index 08b7c482..796690f6 100644 --- a/tests/ppx/react/innerModule.res +++ b/tests/ppx/react/innerModule.res @@ -1,16 +1,43 @@ +@@jsxConfig({version: 3}) module Bar = { @react.component let make = (~a, ~b, _) => { - Js.log( - "This function should be named `InnerModule.react$Bar`", - ) + Js.log("This function should be named `InnerModule.react$Bar`")
} @react.component let component = (~a, ~b, _) => { - Js.log( - "This function should be named `InnerModule.react$Bar$component`", - ) + Js.log("This function should be named `InnerModule.react$Bar$component`") +
+ } +} + +@@jsxConfig({version: 4, mode: "classic"}) + +module Bar = { + @react.component + let make = (~a, ~b, _) => { + Js.log("This function should be named `InnerModule.react$Bar`") +
+ } + @react.component + let component = (~a, ~b, _) => { + Js.log("This function should be named `InnerModule.react$Bar$component`") +
+ } +} + +@@jsxConfig({version: 4, mode: "automatic"}) + +module Bar = { + @react.component + let make = (~a, ~b, _) => { + Js.log("This function should be named `InnerModule.react$Bar`") +
+ } + @react.component + let component = (~a, ~b, _) => { + Js.log("This function should be named `InnerModule.react$Bar$component`")
} } diff --git a/tests/ppx/react/newtype.res b/tests/ppx/react/newtype.res index 1e6f2daa..e25e9b6b 100644 --- a/tests/ppx/react/newtype.res +++ b/tests/ppx/react/newtype.res @@ -1,2 +1,14 @@ +@@jsxConfig({version: 3}) + +@react.component +let make = (type a, ~a: a, ~b: array>, ~c: 'a, _) =>
+ +@@jsxConfig({version: 4, mode: "classic"}) + +@react.component +let make = (type a, ~a: a, ~b: array>, ~c: 'a, _) =>
+ +@@jsxConfig({version: 4, mode: "automatic"}) + @react.component let make = (type a, ~a: a, ~b: array>, ~c: 'a, _) =>
diff --git a/tests/ppx/react/topLevel.res b/tests/ppx/react/topLevel.res index 3c00888e..8ea77292 100644 --- a/tests/ppx/react/topLevel.res +++ b/tests/ppx/react/topLevel.res @@ -1,3 +1,21 @@ +@@jsxConfig({version: 3}) + +@react.component +let make = (~a, ~b, _) => { + Js.log("This function should be named 'TopLevel.react'") +
+} + +@@jsxConfig({version: 4, mode: "classic"}) + +@react.component +let make = (~a, ~b, _) => { + Js.log("This function should be named 'TopLevel.react'") +
+} + +@@jsxConfig({version: 4, mode: "automatic"}) + @react.component let make = (~a, ~b, _) => { Js.log("This function should be named 'TopLevel.react'") diff --git a/tests/ppx/react/typeConstraint.res b/tests/ppx/react/typeConstraint.res index cbe88490..28b9c218 100644 --- a/tests/ppx/react/typeConstraint.res +++ b/tests/ppx/react/typeConstraint.res @@ -1,2 +1,20 @@ +@@jsxConfig({version: 3}) + @react.component -let make: type a. (~a: a, ~b: a, a) => React.element = (~a, ~b, _) =>
+let make: + type a. (~a: a, ~b: a, a) => React.element = + (~a, ~b, _) =>
+ +@@jsxConfig({version: 4, mode: "classic"}) + +@react.component +let make: + type a. (~a: a, ~b: a, a) => React.element = + (~a, ~b, _) =>
+ +@@jsxConfig({version: 4, mode: "automatic"}) + +@react.component +let make: + type a. (~a: a, ~b: a, a) => React.element = + (~a, ~b, _) =>
From 47beff4aa6eca0dd8357f6888ae628757ab21837 Mon Sep 17 00:00:00 2001 From: Woonki Moon Date: Sun, 10 Jul 2022 22:02:14 +0900 Subject: [PATCH 71/94] udpate docs --- cli/JSXV4.md | 21 ++++++++++++++++++--- cli/JSXV4UPGRADE.md | 6 ------ 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/cli/JSXV4.md b/cli/JSXV4.md index 3f7430b5..a280f2ee 100644 --- a/cli/JSXV4.md +++ b/cli/JSXV4.md @@ -132,9 +132,24 @@ ReactDOMRe.createElement(ReasonReact.fragment, [comp1, comp2, comp3]) The top-level attribute `@@jsxConfig` is used to update the jsx config for the rest of the file (or until the next config update). Only the values mentioned are updated, the others are left unchanged. ```rescript -@@jsxConfig({version: 4, mode: "automatic"}) -// The jsx config is updated for the rest of the file. +@@jsxConfig({ version: 4, mode: "automatic" }) -@react.component +module Wrapper = { + module R1 = { + @react.component // V4 & new jsx transform + let make = () => body + } + + @@jsxConfig({ version: 4, mode: "classic" }) + + module R2 = { + @react.component // V4 with `React.createElement` + let make = () => body + } +} + +@@jsxConfig({ versino: 3 }) + +@react.component // V3 let make = () => body ``` diff --git a/cli/JSXV4UPGRADE.md b/cli/JSXV4UPGRADE.md index a5a7fb09..97c96acf 100644 --- a/cli/JSXV4UPGRADE.md +++ b/cli/JSXV4UPGRADE.md @@ -11,19 +11,14 @@ ```json "jsx": { "version": 3, - "module": "react", - "mode": "classic" } ``` - **Note:** When using `jsx` v3, the `runtime` option will be ignored. - b. JSX V4 with classic mode (generate calls to `React.createElement` just as with V3) ```json "jsx": { "version": 4, - "module": "react", "mode": "classic" } ``` @@ -33,7 +28,6 @@ ```json "jsx": { "version": 4, - "module": "react", "mode": "automatic" } ``` From b755fabad85389855108b09bb66623ae642b4780 Mon Sep 17 00:00:00 2001 From: Woonki Moon Date: Mon, 11 Jul 2022 09:02:20 +0900 Subject: [PATCH 72/94] fix typo --- cli/JSXV4.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/JSXV4.md b/cli/JSXV4.md index a280f2ee..55ee6045 100644 --- a/cli/JSXV4.md +++ b/cli/JSXV4.md @@ -148,7 +148,7 @@ module Wrapper = { } } -@@jsxConfig({ versino: 3 }) +@@jsxConfig({ version: 3 }) @react.component // V3 let make = () => body From 6e90507c4330f7baa90d4ce36322c1c2a7caf54c Mon Sep 17 00:00:00 2001 From: Woonki Moon Date: Tue, 12 Jul 2022 02:16:04 +0900 Subject: [PATCH 73/94] more concrete type for arg --- cli/reactjs_jsx_ppx.ml | 47 ++++-------------------------------------- 1 file changed, 4 insertions(+), 43 deletions(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index 8a437e42..779f2c2a 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -1961,57 +1961,18 @@ module V4 = struct let argToType types (name, default, _noLabelName, _alias, loc, type_) = match (type_, name, default) with - | Some {ptyp_desc = Ptyp_constr ({txt = Lident "option"}, [type_])}, name, _ - when isOptional name -> - ( true, - getLabel name, - [], - { - type_ with - ptyp_desc = - Ptyp_constr ({loc = type_.ptyp_loc; txt = optionIdent}, [type_]); - } ) - :: types - | Some type_, name, Some _default -> - ( false, - getLabel name, - [], - { - ptyp_desc = Ptyp_constr ({loc; txt = optionIdent}, [type_]); - ptyp_loc = loc; - ptyp_attributes = []; - } ) + | Some type_, name, _ when isOptional name -> + (true, getLabel name, [], {type_ with ptyp_attributes = optionalAttr}) :: types | Some type_, name, _ -> (false, getLabel name, [], type_) :: types | None, name, _ when isOptional name -> ( true, getLabel name, [], - { - ptyp_desc = - Ptyp_constr - ( {loc; txt = optionIdent}, - [ - { - ptyp_desc = Ptyp_var (safeTypeFromValue name); - ptyp_loc = loc; - ptyp_attributes = []; - }; - ] ); - ptyp_loc = loc; - ptyp_attributes = []; - } ) + Typ.var ~loc ~attrs:optionalAttr (safeTypeFromValue name) ) :: types | None, name, _ when isLabelled name -> - ( false, - getLabel name, - [], - { - ptyp_desc = Ptyp_var (safeTypeFromValue name); - ptyp_loc = loc; - ptyp_attributes = []; - } ) - :: types + (false, getLabel name, [], Typ.var ~loc (safeTypeFromValue name)) :: types | _ -> types [@@raises Invalid_argument] From 247ad0ce607bb2841c8edd5c053258af0e7c06f2 Mon Sep 17 00:00:00 2001 From: Woonki Moon Date: Tue, 12 Jul 2022 02:17:18 +0900 Subject: [PATCH 74/94] fix test --- tests/ppx/react/expected/forwardRef.res.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ppx/react/expected/forwardRef.res.txt b/tests/ppx/react/expected/forwardRef.res.txt index 7cbea802..ab0d41d6 100644 --- a/tests/ppx/react/expected/forwardRef.res.txt +++ b/tests/ppx/react/expected/forwardRef.res.txt @@ -68,7 +68,7 @@ module FancyInput = { ref?: ReactDOM.Ref.currentDomRef, } - let make = ({?className, children, ?ref, _}: props, 'children>) => { + let make = ({?className, children, ?ref, _}: props<'className, 'children>) => { let ref = Js.Nullable.fromOption(ref) let _ = ref @@ -125,7 +125,7 @@ module FancyInput = { ref?: ReactDOM.Ref.currentDomRef, } - let make = ({?className, children, ?ref, _}: props, 'children>) => { + let make = ({?className, children, ?ref, _}: props<'className, 'children>) => { let ref = Js.Nullable.fromOption(ref) let _ = ref From 589bdccdbedcf71bb43ddfacfdd53de496fd30a0 Mon Sep 17 00:00:00 2001 From: Woonki Moon Date: Tue, 12 Jul 2022 03:32:04 +0900 Subject: [PATCH 75/94] fix newtype --- cli/reactjs_jsx_ppx.ml | 29 +++++++++++++++++-- tests/ppx/react/expected/newtype.res.txt | 20 +++---------- .../ppx/react/expected/typeConstraint.res.txt | 4 +-- 3 files changed, 32 insertions(+), 21 deletions(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index 779f2c2a..1b0216ff 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -1959,7 +1959,27 @@ module V4 = struct | _ -> (args, newtypes, None) [@@raises Invalid_argument] - let argToType types (name, default, _noLabelName, _alias, loc, type_) = + let newtypeToVar newtype type_ = + let var_desc = Ptyp_var ("type-" ^ newtype) in + let typ (mapper : Ast_mapper.mapper) typ = + match typ.ptyp_desc with + | Ptyp_constr ({txt = Lident name}, _) when name = newtype -> + {typ with ptyp_desc = var_desc} + | _ -> Ast_mapper.default_mapper.typ mapper typ + in + let mapper = {Ast_mapper.default_mapper with typ} in + mapper.typ mapper type_ + + let argToType ~newtypes types (name, default, _noLabelName, _alias, loc, type_) + = + let type_ = + List.fold_left + (fun type_ newtype -> + match type_ with + | Some typ -> Some (newtypeToVar newtype.txt typ) + | None -> None) + type_ newtypes + in match (type_, name, default) with | Some type_, name, _ when isOptional name -> (true, getLabel name, [], {type_ with ptyp_attributes = optionalAttr}) @@ -2230,12 +2250,14 @@ module V4 = struct modifiedBinding binding in (* do stuff here! *) - let namedArgList, _newtypes, _forwardRef = + let namedArgList, newtypes, _forwardRef = recursivelyTransformNamedArgsForMake mapper (modifiedBindingOld binding) [] [] in - let namedTypeList = List.fold_left argToType [] namedArgList in + let namedTypeList = + List.fold_left (argToType ~newtypes) [] namedArgList + in (* let _ = ref *) let vbIgnoreUnusedRef = Vb.mk (Pat.any ()) (Exp.ident (Location.mknoloc (Lident "ref"))) @@ -2337,6 +2359,7 @@ module V4 = struct in let rec returnedExpression patterns ({pexp_desc} as expr) = match pexp_desc with + | Pexp_newtype ({txt}, expr) -> returnedExpression patterns expr | Pexp_fun ( _arg_label, _default, diff --git a/tests/ppx/react/expected/newtype.res.txt b/tests/ppx/react/expected/newtype.res.txt index b254e737..b8ead74b 100644 --- a/tests/ppx/react/expected/newtype.res.txt +++ b/tests/ppx/react/expected/newtype.res.txt @@ -19,14 +19,8 @@ let make = { @@jsxConfig({version: 4, mode: "classic"}) type props<'a, 'b, 'c> = {key?: string, a: 'a, b: 'b, c: 'c} -let make = ( - _: props>, 'a>, - type a, - ~a: a, - ~b: array>, - ~c: 'a, - _, -) => ReactDOMRe.createDOMElementVariadic("div", []) +let make = ({a, b, c, _}: props<'\"type-a", array>, 'a>) => + ReactDOMRe.createDOMElementVariadic("div", []) let make = { let \"Newtype" = (props: props<_>) => make(props) \"Newtype" @@ -35,14 +29,8 @@ let make = { @@jsxConfig({version: 4, mode: "automatic"}) type props<'a, 'b, 'c> = {key?: string, a: 'a, b: 'b, c: 'c} -let make = ( - _: props>, 'a>, - type a, - ~a: a, - ~b: array>, - ~c: 'a, - _, -) => ReactDOM.jsx("div", {key: ?None}) +let make = ({a, b, c, _}: props<'\"type-a", array>, 'a>) => + ReactDOM.jsx("div", {key: ?None}) let make = { let \"Newtype" = (props: props<_>) => make(props) \"Newtype" diff --git a/tests/ppx/react/expected/typeConstraint.res.txt b/tests/ppx/react/expected/typeConstraint.res.txt index 433e2a53..a1573d0e 100644 --- a/tests/ppx/react/expected/typeConstraint.res.txt +++ b/tests/ppx/react/expected/typeConstraint.res.txt @@ -12,7 +12,7 @@ let make = { @@jsxConfig({version: 4, mode: "classic"}) type props<'a, 'b> = {key?: string, a: 'a, b: 'b} -let make: 'a. (~a: 'a, ~b: 'a, 'a) => React.element = (_: props<'a, 'b>, type a): ( +let make: 'a. (~a: 'a, ~b: 'a, 'a) => React.element = (_: props<'a, 'b>): ( (~a: a, ~b: a, a) => React.element ) => (~a, ~b, _) => ReactDOMRe.createDOMElementVariadic("div", []) let make = { @@ -23,7 +23,7 @@ let make = { @@jsxConfig({version: 4, mode: "automatic"}) type props<'a, 'b> = {key?: string, a: 'a, b: 'b} -let make: 'a. (~a: 'a, ~b: 'a, 'a) => React.element = (_: props<'a, 'b>, type a): ( +let make: 'a. (~a: 'a, ~b: 'a, 'a) => React.element = (_: props<'a, 'b>): ( (~a: a, ~b: a, a) => React.element ) => (~a, ~b, _) => ReactDOM.jsx("div", {key: ?None}) let make = { From af1d52daea83eae6041c6c9efc6686ee86562007 Mon Sep 17 00:00:00 2001 From: Woonki Moon Date: Tue, 12 Jul 2022 14:06:37 +0900 Subject: [PATCH 76/94] fix typeConstraints --- cli/reactjs_jsx_ppx.ml | 54 +++++++++++++------ .../ppx/react/expected/typeConstraint.res.txt | 9 ++-- 2 files changed, 40 insertions(+), 23 deletions(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index 1b0216ff..ca00efbc 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -1879,7 +1879,8 @@ module V4 = struct args [@@raises Invalid_argument] - let rec recursivelyTransformNamedArgsForMake mapper expr args newtypes = + let rec recursivelyTransformNamedArgsForMake mapper expr args newtypes + coreType = let expr = mapper.expr mapper expr in match expr.pexp_desc with (* TODO: make this show up with a loc. *) @@ -1931,32 +1932,33 @@ module V4 = struct recursivelyTransformNamedArgsForMake mapper expression ((arg, default, pattern, alias, pattern.ppat_loc, type_) :: args) - newtypes + newtypes coreType | Pexp_fun ( Nolabel, _, {ppat_desc = Ppat_construct ({txt = Lident "()"}, _) | Ppat_any}, _expression ) -> - (args, newtypes, None) + (args, newtypes, coreType) | Pexp_fun ( Nolabel, _, { ppat_desc = - Ppat_var {txt} | Ppat_constraint ({ppat_desc = Ppat_var {txt}}, _); + Ppat_var _ | Ppat_constraint ({ppat_desc = Ppat_var _}, _); }, _expression ) -> - (args, newtypes, Some txt) + (args, newtypes, coreType) | Pexp_fun (Nolabel, _, pattern, _expression) -> Location.raise_errorf ~loc:pattern.ppat_loc "React: react.component refs only support plain arguments and type \ annotations." | Pexp_newtype (label, expression) -> recursivelyTransformNamedArgsForMake mapper expression args - (label :: newtypes) - | Pexp_constraint (expression, _typ) -> + (label :: newtypes) coreType + | Pexp_constraint (expression, coreType) -> recursivelyTransformNamedArgsForMake mapper expression args newtypes - | _ -> (args, newtypes, None) + (Some coreType) + | _ -> (args, newtypes, coreType) [@@raises Invalid_argument] let newtypeToVar newtype type_ = @@ -1970,14 +1972,21 @@ module V4 = struct let mapper = {Ast_mapper.default_mapper with typ} in mapper.typ mapper type_ - let argToType ~newtypes types (name, default, _noLabelName, _alias, loc, type_) - = + let argToType ~newtypes ~(typeConstraints : core_type option) types + (name, default, _noLabelName, _alias, loc, type_) = + let rec getType name coreType = + match coreType with + | {ptyp_desc = Ptyp_arrow (arg, c1, c2)} -> + if name = arg then Some c1 else getType name c2 + | _ -> None + in + let typeConst = Option.bind typeConstraints (getType name) in let type_ = List.fold_left (fun type_ newtype -> - match type_ with - | Some typ -> Some (newtypeToVar newtype.txt typ) - | None -> None) + match (type_, typeConst) with + | _, Some typ | Some typ, None -> Some (newtypeToVar newtype.txt typ) + | _ -> None) type_ newtypes in match (type_, name, default) with @@ -2250,13 +2259,15 @@ module V4 = struct modifiedBinding binding in (* do stuff here! *) - let namedArgList, newtypes, _forwardRef = + let namedArgList, newtypes, typeConstraints = recursivelyTransformNamedArgsForMake mapper (modifiedBindingOld binding) - [] [] + [] [] None in let namedTypeList = - List.fold_left (argToType ~newtypes) [] namedArgList + List.fold_left + (argToType ~newtypes ~typeConstraints) + [] namedArgList in (* let _ = ref *) let vbIgnoreUnusedRef = @@ -2360,6 +2371,8 @@ module V4 = struct let rec returnedExpression patterns ({pexp_desc} as expr) = match pexp_desc with | Pexp_newtype ({txt}, expr) -> returnedExpression patterns expr + | Pexp_constraint (expr, coreType) -> + returnedExpression patterns expr | Pexp_fun ( _arg_label, _default, @@ -2440,7 +2453,14 @@ module V4 = struct ], None ) | Nonrecursive -> - ( [{binding with pvb_expr = expression; pvb_attributes = []}], + ( [ + { + binding with + pvb_expr = expression; + pvb_attributes = []; + pvb_pat = Pat.var {txt = fnName; loc = Location.none}; + }; + ], Some (bindingWrapper fullExpression) ) in (Some propsRecordType, bindings, newBinding) diff --git a/tests/ppx/react/expected/typeConstraint.res.txt b/tests/ppx/react/expected/typeConstraint.res.txt index a1573d0e..e1f9f021 100644 --- a/tests/ppx/react/expected/typeConstraint.res.txt +++ b/tests/ppx/react/expected/typeConstraint.res.txt @@ -12,9 +12,8 @@ let make = { @@jsxConfig({version: 4, mode: "classic"}) type props<'a, 'b> = {key?: string, a: 'a, b: 'b} -let make: 'a. (~a: 'a, ~b: 'a, 'a) => React.element = (_: props<'a, 'b>): ( - (~a: a, ~b: a, a) => React.element -) => (~a, ~b, _) => ReactDOMRe.createDOMElementVariadic("div", []) +let make = ({a, b, _}: props<'\"type-a", '\"type-a">) => + ReactDOMRe.createDOMElementVariadic("div", []) let make = { let \"TypeConstraint" = (props: props<_>) => make(props) \"TypeConstraint" @@ -23,9 +22,7 @@ let make = { @@jsxConfig({version: 4, mode: "automatic"}) type props<'a, 'b> = {key?: string, a: 'a, b: 'b} -let make: 'a. (~a: 'a, ~b: 'a, 'a) => React.element = (_: props<'a, 'b>): ( - (~a: a, ~b: a, a) => React.element -) => (~a, ~b, _) => ReactDOM.jsx("div", {key: ?None}) +let make = ({a, b, _}: props<'\"type-a", '\"type-a">) => ReactDOM.jsx("div", {key: ?None}) let make = { let \"TypeConstraint" = (props: props<_>) => make(props) \"TypeConstraint" From fb0899209fe1045877e62168be4a6efc64036cd5 Mon Sep 17 00:00:00 2001 From: Woonki Moon Date: Wed, 13 Jul 2022 23:54:31 +0900 Subject: [PATCH 77/94] keep pvb_attributes --- cli/reactjs_jsx_ppx.ml | 3 +-- tests/ppx/react/expected/commentAtTop.res.txt | 1 + tests/ppx/react/expected/fileLevelConfig.res.txt | 3 +++ tests/ppx/react/expected/forwardRef.res.txt | 6 ++++++ tests/ppx/react/expected/innerModule.res.txt | 6 ++++++ tests/ppx/react/expected/newtype.res.txt | 3 +++ tests/ppx/react/expected/topLevel.res.txt | 3 +++ tests/ppx/react/expected/typeConstraint.res.txt | 3 +++ 8 files changed, 26 insertions(+), 2 deletions(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index ca00efbc..ed76f901 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -1118,7 +1118,7 @@ module V3 = struct ], None ) | Nonrecursive -> - ( [{binding with pvb_expr = expression; pvb_attributes = []}], + ( [{binding with pvb_expr = expression}], Some (bindingWrapper fullExpression) ) in (Some externalDecl, bindings, newBinding) @@ -2457,7 +2457,6 @@ module V4 = struct { binding with pvb_expr = expression; - pvb_attributes = []; pvb_pat = Pat.var {txt = fnName; loc = Location.none}; }; ], diff --git a/tests/ppx/react/expected/commentAtTop.res.txt b/tests/ppx/react/expected/commentAtTop.res.txt index 99eaf029..2367e3d0 100644 --- a/tests/ppx/react/expected/commentAtTop.res.txt +++ b/tests/ppx/react/expected/commentAtTop.res.txt @@ -1,5 +1,6 @@ type props<'msg> = {key?: string, msg: 'msg} // test React JSX file +@react.component let make = ({msg, _}: props<'msg>) => { ReactDOM.jsx("div", {children: {msg->React.string}}) } diff --git a/tests/ppx/react/expected/fileLevelConfig.res.txt b/tests/ppx/react/expected/fileLevelConfig.res.txt index ce94b54b..373902f5 100644 --- a/tests/ppx/react/expected/fileLevelConfig.res.txt +++ b/tests/ppx/react/expected/fileLevelConfig.res.txt @@ -1,6 +1,7 @@ @@jsxConfig({version: 3}) @obj external makeProps: (~msg: 'msg, ~key: string=?, unit) => {"msg": 'msg} = "" +@react.component let make = (@warning("-16") ~msg) => { ReactDOMRe.createDOMElementVariadic("div", [{msg->React.string}]) @@ -13,6 +14,7 @@ let make = { @@jsxConfig({version: 4, mode: "classic"}) type props<'msg> = {key?: string, msg: 'msg} +@react.component let make = ({msg, _}: props<'msg>) => { ReactDOMRe.createDOMElementVariadic("div", [{msg->React.string}]) } @@ -24,6 +26,7 @@ let make = { @@jsxConfig({version: 4, mode: "automatic"}) type props<'msg> = {key?: string, msg: 'msg} +@react.component let make = ({msg, _}: props<'msg>) => { ReactDOM.jsx("div", {children: {msg->React.string}}) } diff --git a/tests/ppx/react/expected/forwardRef.res.txt b/tests/ppx/react/expected/forwardRef.res.txt index ab0d41d6..d54b268a 100644 --- a/tests/ppx/react/expected/forwardRef.res.txt +++ b/tests/ppx/react/expected/forwardRef.res.txt @@ -10,6 +10,7 @@ module FancyInput = { unit, ) => {"className": option<'className>, "children": 'children} = "" + @react.component let make = (@warning("-16") ~className=?, @warning("-16") ~children) => @warning("-16") @@ -40,6 +41,7 @@ module FancyInput = { } @obj external makeProps: (~key: string=?, unit) => {.} = "" +@react.component let make = () => { let input = React.useRef(Js.Nullable.null) @@ -68,6 +70,7 @@ module FancyInput = { ref?: ReactDOM.Ref.currentDomRef, } + @react.component let make = ({?className, children, ?ref, _}: props<'className, 'children>) => { let ref = Js.Nullable.fromOption(ref) let _ = ref @@ -97,6 +100,7 @@ module FancyInput = { } type props = {key?: string} +@react.component let make = (_: props) => { let input = React.useRef(Js.Nullable.null) @@ -125,6 +129,7 @@ module FancyInput = { ref?: ReactDOM.Ref.currentDomRef, } + @react.component let make = ({?className, children, ?ref, _}: props<'className, 'children>) => { let ref = Js.Nullable.fromOption(ref) let _ = ref @@ -154,6 +159,7 @@ module FancyInput = { } type props = {key?: string} +@react.component let make = (_: props) => { let input = React.useRef(Js.Nullable.null) diff --git a/tests/ppx/react/expected/innerModule.res.txt b/tests/ppx/react/expected/innerModule.res.txt index 83e8a6ce..8cc10ce3 100644 --- a/tests/ppx/react/expected/innerModule.res.txt +++ b/tests/ppx/react/expected/innerModule.res.txt @@ -2,6 +2,7 @@ module Bar = { @obj external makeProps: (~a: 'a, ~b: 'b, ~key: string=?, unit) => {"a": 'a, "b": 'b} = "" + @react.component let make = (@warning("-16") ~a, @warning("-16") ~b, _) => { Js.log("This function should be named `InnerModule.react$Bar`") @@ -14,6 +15,7 @@ module Bar = { } @obj external componentProps: (~a: 'a, ~b: 'b, ~key: string=?, unit) => {"a": 'a, "b": 'b} = "" + @react.component let component = (@warning("-16") ~a, @warning("-16") ~b, _) => { Js.log("This function should be named `InnerModule.react$Bar$component`") @@ -31,6 +33,7 @@ module Bar = { module Bar = { type props<'a, 'b> = {key?: string, a: 'a, b: 'b} + @react.component let make = ({a, b, _}: props<'a, 'b>) => { Js.log("This function should be named `InnerModule.react$Bar`") ReactDOMRe.createDOMElementVariadic("div", []) @@ -41,6 +44,7 @@ module Bar = { } type props<'a, 'b> = {key?: string, a: 'a, b: 'b} + @react.component let component = ({a, b, _}: props<'a, 'b>) => { Js.log("This function should be named `InnerModule.react$Bar$component`") ReactDOMRe.createDOMElementVariadic("div", []) @@ -56,6 +60,7 @@ module Bar = { module Bar = { type props<'a, 'b> = {key?: string, a: 'a, b: 'b} + @react.component let make = ({a, b, _}: props<'a, 'b>) => { Js.log("This function should be named `InnerModule.react$Bar`") ReactDOM.jsx("div", {key: ?None}) @@ -66,6 +71,7 @@ module Bar = { } type props<'a, 'b> = {key?: string, a: 'a, b: 'b} + @react.component let component = ({a, b, _}: props<'a, 'b>) => { Js.log("This function should be named `InnerModule.react$Bar$component`") ReactDOM.jsx("div", {key: ?None}) diff --git a/tests/ppx/react/expected/newtype.res.txt b/tests/ppx/react/expected/newtype.res.txt index b8ead74b..f8b85a25 100644 --- a/tests/ppx/react/expected/newtype.res.txt +++ b/tests/ppx/react/expected/newtype.res.txt @@ -8,6 +8,7 @@ external makeProps: ( unit, ) => {"a": '\"type-a", "b": array>, "c": 'a} = "" +@react.component let make = (type a, ~a: a, ~b: array>, ~c: 'a, _) => ReactDOMRe.createDOMElementVariadic("div", []) let make = { @@ -19,6 +20,7 @@ let make = { @@jsxConfig({version: 4, mode: "classic"}) type props<'a, 'b, 'c> = {key?: string, a: 'a, b: 'b, c: 'c} +@react.component let make = ({a, b, c, _}: props<'\"type-a", array>, 'a>) => ReactDOMRe.createDOMElementVariadic("div", []) let make = { @@ -29,6 +31,7 @@ let make = { @@jsxConfig({version: 4, mode: "automatic"}) type props<'a, 'b, 'c> = {key?: string, a: 'a, b: 'b, c: 'c} +@react.component let make = ({a, b, c, _}: props<'\"type-a", array>, 'a>) => ReactDOM.jsx("div", {key: ?None}) let make = { diff --git a/tests/ppx/react/expected/topLevel.res.txt b/tests/ppx/react/expected/topLevel.res.txt index 3a9529d9..c2188eb5 100644 --- a/tests/ppx/react/expected/topLevel.res.txt +++ b/tests/ppx/react/expected/topLevel.res.txt @@ -1,6 +1,7 @@ @@jsxConfig({version: 3}) @obj external makeProps: (~a: 'a, ~b: 'b, ~key: string=?, unit) => {"a": 'a, "b": 'b} = "" +@react.component let make = (@warning("-16") ~a, @warning("-16") ~b, _) => { Js.log("This function should be named 'TopLevel.react'") @@ -14,6 +15,7 @@ let make = { @@jsxConfig({version: 4, mode: "classic"}) type props<'a, 'b> = {key?: string, a: 'a, b: 'b} +@react.component let make = ({a, b, _}: props<'a, 'b>) => { Js.log("This function should be named 'TopLevel.react'") ReactDOMRe.createDOMElementVariadic("div", []) @@ -26,6 +28,7 @@ let make = { @@jsxConfig({version: 4, mode: "automatic"}) type props<'a, 'b> = {key?: string, a: 'a, b: 'b} +@react.component let make = ({a, b, _}: props<'a, 'b>) => { Js.log("This function should be named 'TopLevel.react'") ReactDOM.jsx("div", {key: ?None}) diff --git a/tests/ppx/react/expected/typeConstraint.res.txt b/tests/ppx/react/expected/typeConstraint.res.txt index e1f9f021..0a7a1d72 100644 --- a/tests/ppx/react/expected/typeConstraint.res.txt +++ b/tests/ppx/react/expected/typeConstraint.res.txt @@ -1,6 +1,7 @@ @@jsxConfig({version: 3}) @obj external makeProps: (~a: 'a, ~b: 'b, ~key: string=?, unit) => {"a": 'a, "b": 'b} = "" +@react.component let make: type a. (~a: a, ~b: a, a) => React.element = (~a, ~b, _) => ReactDOMRe.createDOMElementVariadic("div", []) @@ -12,6 +13,7 @@ let make = { @@jsxConfig({version: 4, mode: "classic"}) type props<'a, 'b> = {key?: string, a: 'a, b: 'b} +@react.component let make = ({a, b, _}: props<'\"type-a", '\"type-a">) => ReactDOMRe.createDOMElementVariadic("div", []) let make = { @@ -22,6 +24,7 @@ let make = { @@jsxConfig({version: 4, mode: "automatic"}) type props<'a, 'b> = {key?: string, a: 'a, b: 'b} +@react.component let make = ({a, b, _}: props<'\"type-a", '\"type-a">) => ReactDOM.jsx("div", {key: ?None}) let make = { let \"TypeConstraint" = (props: props<_>) => make(props) From dbad398ff19ae91ae921c22660dd70b030734c40 Mon Sep 17 00:00:00 2001 From: Woonki Moon Date: Thu, 14 Jul 2022 11:38:49 +0900 Subject: [PATCH 78/94] remove unused variables, values --- cli/reactjs_jsx_ppx.ml | 27 ++------------------------- 1 file changed, 2 insertions(+), 25 deletions(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index ed76f901..3cbe1a34 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -1360,8 +1360,6 @@ module V4 = struct | Optional str | Labelled str -> str | Nolabel -> "" - let optionIdent = Lident "option" - let optionalAttr = [({txt = "ns.optional"; loc = Location.none}, PStr [])] let constantString ~loc str = @@ -2370,9 +2368,8 @@ module V4 = struct in let rec returnedExpression patterns ({pexp_desc} as expr) = match pexp_desc with - | Pexp_newtype ({txt}, expr) -> returnedExpression patterns expr - | Pexp_constraint (expr, coreType) -> - returnedExpression patterns expr + | Pexp_newtype (_, expr) -> returnedExpression patterns expr + | Pexp_constraint (expr, _) -> returnedExpression patterns expr | Pexp_fun ( _arg_label, _default, @@ -2590,26 +2587,6 @@ module V4 = struct module name.") [@@raises Invalid_argument] - let signature ~config mapper items = - let items = default_mapper.signature mapper items in - List.map - (fun item -> - if config.version = 4 then transformSignatureItem mapper item - else [item]) - items - |> List.flatten - [@@raises Invalid_argument] - - let structure ~config mapper items = - let items = default_mapper.structure mapper items in - List.map - (fun item -> - if config.version = 4 then transformStructureItem ~config mapper item - else [item]) - items - |> List.flatten - [@@raises Invalid_argument] - let expr ~config mapper expression = match expression with (* Does the function application have the @JSX attribute? *) From 4cc70a0d590b854ae648f2d36b6c043b645eff8a Mon Sep 17 00:00:00 2001 From: Woonki Moon Date: Fri, 15 Jul 2022 01:46:43 +0900 Subject: [PATCH 79/94] uppercase loc to definition --- cli/reactjs_jsx_ppx.ml | 64 ++++++++++++++++++++++++------------------ 1 file changed, 37 insertions(+), 27 deletions(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index 3cbe1a34..ab9a19f7 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -1535,7 +1535,7 @@ module V4 = struct *) (* make record from props and spread props if exists *) - let recordFromProps ?(removeKey = false) {pexp_loc} callArguments = + let recordFromProps ?(removeKey = false) callArguments = let rec removeLastPositionUnitAux props acc = match props with | [] -> acc @@ -1571,17 +1571,21 @@ module V4 = struct in match spreadFields with | [] -> - {pexp_desc = Pexp_record (fields, None); pexp_loc; pexp_attributes = []} + { + pexp_desc = Pexp_record (fields, None); + pexp_loc = Location.none; + pexp_attributes = []; + } | [spreadProps] -> { pexp_desc = Pexp_record (fields, Some spreadProps); - pexp_loc; + pexp_loc = Location.none; pexp_attributes = []; } | spreadProps :: _ -> { pexp_desc = Pexp_record (fields, Some spreadProps); - pexp_loc; + pexp_loc = Location.none; pexp_attributes = []; } @@ -1651,8 +1655,8 @@ module V4 = struct ~kind:(Ptype_record labelDeclList); ] - let transformUppercaseCall3 ~config modulePath mapper loc attrs callExpression - callArguments = + let transformUppercaseCall3 ~config modulePath mapper loc attrs callArguments + = let children, argsWithLabels = extractChildren ~loc ~removeLastPositionUnit:true callArguments in @@ -1713,7 +1717,7 @@ module V4 = struct match config.mode with (* The new jsx transform *) | "automatic" -> - let record = recordFromProps ~removeKey:true callExpression args in + let record = recordFromProps ~removeKey:true args in let props = if isEmptyRecord record then recordWithOnlyKey ~loc else record in @@ -1723,20 +1727,24 @@ module V4 = struct let jsxExpr, key = match (!childrenArg, keyProp) with | None, (_, keyExpr) :: _ -> - ( Exp.ident ~loc {loc; txt = Ldot (Lident "React", "jsxKeyed")}, + ( Exp.ident + {loc = Location.none; txt = Ldot (Lident "React", "jsxKeyed")}, [(nolabel, keyExpr)] ) | None, [] -> - (Exp.ident ~loc {loc; txt = Ldot (Lident "React", "jsx")}, []) + ( Exp.ident {loc = Location.none; txt = Ldot (Lident "React", "jsx")}, + [] ) | Some _, (_, keyExpr) :: _ -> - ( Exp.ident ~loc {loc; txt = Ldot (Lident "React", "jsxsKeyed")}, + ( Exp.ident + {loc = Location.none; txt = Ldot (Lident "React", "jsxsKeyed")}, [(nolabel, keyExpr)] ) | Some _, [] -> - (Exp.ident ~loc {loc; txt = Ldot (Lident "React", "jsxs")}, []) + ( Exp.ident {loc = Location.none; txt = Ldot (Lident "React", "jsxs")}, + [] ) in - Exp.apply ~loc ~attrs jsxExpr - ([(nolabel, Exp.ident ~loc {txt = ident; loc}); (nolabel, props)] @ key) + Exp.apply ~attrs jsxExpr + ([(nolabel, Exp.ident {txt = ident; loc}); (nolabel, props)] @ key) | _ -> ( - let record = recordFromProps callExpression args in + let record = recordFromProps args in (* check if record which goes to Foo.make({ ... } as record) empty or not if empty then change it to {key: @optional None} only for upper case jsx This would be redundant regarding PR progress https://github.com/rescript-lang/syntax/pull/299 @@ -1746,22 +1754,25 @@ module V4 = struct in match !childrenArg with | None -> - Exp.apply ~loc ~attrs - (Exp.ident ~loc {loc; txt = Ldot (Lident "React", "createElement")}) - [(nolabel, Exp.ident ~loc {txt = ident; loc}); (nolabel, props)] + Exp.apply ~attrs + (Exp.ident + {loc = Location.none; txt = Ldot (Lident "React", "createElement")}) + [(nolabel, Exp.ident {txt = ident; loc}); (nolabel, props)] | Some children -> - Exp.apply ~loc ~attrs - (Exp.ident ~loc - {loc; txt = Ldot (Lident "React", "createElementVariadic")}) + Exp.apply ~attrs + (Exp.ident + { + loc = Location.none; + txt = Ldot (Lident "React", "createElementVariadic"); + }) [ - (nolabel, Exp.ident ~loc {txt = ident; loc}); + (nolabel, Exp.ident {txt = ident; loc}); (nolabel, props); (nolabel, children); ]) [@@raises Invalid_argument] - let transformLowercaseCall3 ~config mapper loc attrs callExpression - callArguments id = + let transformLowercaseCall3 ~config mapper loc attrs callArguments id = let componentNameExpr = constantString ~loc id in match config.mode with (* the new jsx transform *) @@ -1799,7 +1810,7 @@ module V4 = struct | Pexp_record (labelDecls, _) when List.length labelDecls = 0 -> true | _ -> false in - let record = recordFromProps ~removeKey:true callExpression args in + let record = recordFromProps ~removeKey:true args in let props = if isEmptyRecord record then recordWithOnlyKey ~loc else record in @@ -2561,13 +2572,12 @@ module V4 = struct (* Foo.createElement(~prop1=foo, ~prop2=bar, ~children=[], ()) *) | {loc; txt = Ldot (modulePath, ("createElement" | "make"))} -> transformUppercaseCall3 ~config modulePath mapper loc attrs - callExpression callArguments + callArguments (* div(~prop1=foo, ~prop2=bar, ~children=[bla], ()) *) (* turn that into ReactDOMRe.createElement(~props=ReactDOMRe.props(~props1=foo, ~props2=bar, ()), [|bla|]) *) | {loc; txt = Lident id} -> - transformLowercaseCall3 ~config mapper loc attrs callExpression - callArguments id + transformLowercaseCall3 ~config mapper loc attrs callArguments id | {txt = Ldot (_, anythingNotCreateElementOrMake)} -> raise (Invalid_argument From b128cbe4ac514e4df0edbfe6259eb665ff86c69e Mon Sep 17 00:00:00 2001 From: Woonki Moon Date: Sun, 17 Jul 2022 01:49:20 +0900 Subject: [PATCH 80/94] clean up --- cli/reactjs_jsx_ppx.ml | 52 ++++++++++++------------------------------ 1 file changed, 15 insertions(+), 37 deletions(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index ab9a19f7..232f50f1 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -1461,10 +1461,6 @@ module V4 = struct (Invalid_argument "JSX: somehow there's more than one `children` label") [@@raises Invalid_argument] - let unerasableIgnore loc = - ( {loc; txt = "warning"}, - PStr [Str.eval (Exp.constant (Pconst_string ("-16", None)))] ) - let merlinFocus = ({loc = Location.none; txt = "merlin.focus"}, PStr []) (* Helper method to look up the [@react.component] attribute *) @@ -2148,13 +2144,6 @@ module V4 = struct (expressionFn expression) in let expression = binding.pvb_expr in - let unerasableIgnoreExp exp = - { - exp with - pexp_attributes = - unerasableIgnore emptyLoc :: exp.pexp_attributes; - } - in (* TODO: there is a long-tail of unsupported features inside of blocks - Pexp_letmodule , Pexp_letexception , Pexp_ifthenelse *) let rec spelunkForFunExpression expression = match expression with @@ -2167,17 +2156,15 @@ module V4 = struct pattern, ({pexp_desc = Pexp_fun _} as internalExpression) ); } -> - let wrap, hasUnit, hasForwardRef, exp = + let wrap, hasForwardRef, exp = spelunkForFunExpression internalExpression in ( wrap, - hasUnit, hasForwardRef, - unerasableIgnoreExp - { - expression with - pexp_desc = Pexp_fun (label, default, pattern, exp); - } ) + { + expression with + pexp_desc = Pexp_fun (label, default, pattern, exp); + } ) (* let make = (()) => ... *) (* let make = (_) => ... *) | { @@ -2191,7 +2178,7 @@ module V4 = struct }, _internalExpression ); } -> - ((fun a -> a), true, false, expression) + ((fun a -> a), false, expression) (* let make = (~prop) => ... *) | { pexp_desc = @@ -2201,14 +2188,13 @@ module V4 = struct _pattern, _internalExpression ); } -> - ((fun a -> a), false, false, unerasableIgnoreExp expression) + ((fun a -> a), false, expression) (* let make = (prop) => ... *) | { pexp_desc = Pexp_fun (_nolabel, _default, pattern, _internalExpression); } -> - if !hasApplication then - ((fun a -> a), false, false, unerasableIgnoreExp expression) + if !hasApplication then ((fun a -> a), false, expression) else Location.raise_errorf ~loc:pattern.ppat_loc "React: props need to be labelled arguments.\n\ @@ -2219,11 +2205,10 @@ module V4 = struct (* let make = {let foo = bar in (~prop) => ...} *) | {pexp_desc = Pexp_let (recursive, vbs, internalExpression)} -> (* here's where we spelunk! *) - let wrap, hasUnit, hasForwardRef, exp = + let wrap, hasForwardRef, exp = spelunkForFunExpression internalExpression in ( wrap, - hasUnit, hasForwardRef, {expression with pexp_desc = Pexp_let (recursive, vbs, exp)} ) @@ -2233,38 +2218,31 @@ module V4 = struct Pexp_apply (wrapperExpression, [(Nolabel, internalExpression)]); } -> let () = hasApplication := true in - let _, hasUnit, _, exp = - spelunkForFunExpression internalExpression - in + let _, _, exp = spelunkForFunExpression internalExpression in let hasForwardRef = isForwardRef wrapperExpression in ( (fun exp -> Exp.apply wrapperExpression [(nolabel, exp)]), - hasUnit, hasForwardRef, exp ) | { pexp_desc = Pexp_sequence (wrapperExpression, internalExpression); } -> - let wrap, hasUnit, hasForwardRef, exp = + let wrap, hasForwardRef, exp = spelunkForFunExpression internalExpression in ( wrap, - hasUnit, hasForwardRef, { expression with pexp_desc = Pexp_sequence (wrapperExpression, exp); } ) - | e -> ((fun a -> a), false, false, e) + | e -> ((fun a -> a), false, e) in - let wrapExpression, hasUnit, hasForwardRef, expression = + let wrapExpression, hasForwardRef, expression = spelunkForFunExpression expression in - ( wrapExpressionWithBinding wrapExpression, - hasUnit, - hasForwardRef, - expression ) + (wrapExpressionWithBinding wrapExpression, hasForwardRef, expression) in - let bindingWrapper, _hasUnit, hasForwardRef, expression = + let bindingWrapper, hasForwardRef, expression = modifiedBinding binding in (* do stuff here! *) From 2bbf89dbdb8bb705a2a4b3907e8d760059fc9a22 Mon Sep 17 00:00:00 2001 From: Woonki Moon Date: Sun, 17 Jul 2022 17:01:06 +0900 Subject: [PATCH 81/94] clean up transforming forwardRef --- cli/reactjs_jsx_ppx.ml | 95 +++++++-------------- tests/ppx/react/expected/forwardRef.res.txt | 18 +--- 2 files changed, 36 insertions(+), 77 deletions(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index 232f50f1..15b957f3 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -2256,20 +2256,6 @@ module V4 = struct (argToType ~newtypes ~typeConstraints) [] namedArgList in - (* let _ = ref *) - let vbIgnoreUnusedRef = - Vb.mk (Pat.any ()) (Exp.ident (Location.mknoloc (Lident "ref"))) - in - (* let ref = ref->Js.Nullable.fromOption *) - let vbRefFromOption = - Vb.mk - (Pat.var @@ Location.mknoloc "ref") - (Exp.apply - (Exp.ident - (Location.mknoloc - (Ldot (Ldot (Lident "Js", "Nullable"), "fromOption")))) - [(Nolabel, Exp.ident (Location.mknoloc @@ Lident "ref"))]) - in let namedArgWithDefaultValueList = List.filter_map argWithDefaultValue namedArgList in @@ -2299,30 +2285,14 @@ module V4 = struct else []) in let innerExpression = - if hasForwardRef then - Exp.apply - (Exp.ident @@ Location.mknoloc @@ Lident "make") - [ - ( Nolabel, - Exp.record - [ - ( Location.mknoloc @@ Lident "ref", - Exp.apply ~attrs:optionalAttr - (Exp.ident - (Location.mknoloc - (Ldot - (Ldot (Lident "Js", "Nullable"), "toOption")))) - [ - ( Nolabel, - Exp.ident (Location.mknoloc @@ Lident "ref") ); - ] ); - ] - (Some (Exp.ident (Location.mknoloc @@ Lident "props"))) ); - ] - else - Exp.apply - (Exp.ident (Location.mknoloc @@ Lident "make")) - [(Nolabel, Exp.ident (Location.mknoloc @@ Lident "props"))] + Exp.apply + (Exp.ident (Location.mknoloc @@ Lident "make")) + ([(Nolabel, Exp.ident (Location.mknoloc @@ Lident "props"))] + @ + match hasForwardRef with + | true -> + [(Nolabel, Exp.ident (Location.mknoloc @@ Lident "ref"))] + | false -> []) in let fullExpression = (* React component name should start with uppercase letter *) @@ -2355,10 +2325,13 @@ module V4 = struct ] (Exp.ident ~loc:emptyLoc {loc = emptyLoc; txt = Lident txt}) in - let rec returnedExpression patterns ({pexp_desc} as expr) = + let rec returnedExpression patternsWithLabel patternsWithNolabel + ({pexp_desc} as expr) = match pexp_desc with - | Pexp_newtype (_, expr) -> returnedExpression patterns expr - | Pexp_constraint (expr, _) -> returnedExpression patterns expr + | Pexp_newtype (_, expr) -> + returnedExpression patternsWithLabel patternsWithNolabel expr + | Pexp_constraint (expr, _) -> + returnedExpression patternsWithLabel patternsWithNolabel expr | Pexp_fun ( _arg_label, _default, @@ -2367,7 +2340,7 @@ module V4 = struct Ppat_construct ({txt = Lident "()"}, _) | Ppat_any; }, expr ) -> - (patterns, expr) + (patternsWithLabel, patternsWithNolabel, expr) | Pexp_fun (arg_label, _default, {ppat_loc; ppat_desc}, expr) -> ( if isLabelled arg_label || isOptional arg_label then returnedExpression @@ -2376,43 +2349,39 @@ module V4 = struct ~attrs: (if isOptional arg_label then optionalAttr else []) {txt = getLabel arg_label; loc = ppat_loc} ) - :: patterns) - expr + :: patternsWithLabel) + patternsWithNolabel expr else (* Special case of nolabel arg "ref" in forwardRef fn *) (* let make = React.forwardRef(ref => body) *) match ppat_desc with - | Ppat_var {txt} - | Ppat_constraint ({ppat_desc = Ppat_var {txt}}, _) - when txt = "ref" -> - returnedExpression + | Ppat_var {txt} -> + returnedExpression patternsWithLabel (( {loc = ppat_loc; txt = Lident txt}, Pat.var ~attrs:optionalAttr {txt; loc = ppat_loc} ) - :: patterns) + :: patternsWithNolabel) expr - | _ -> returnedExpression patterns expr) - | _ -> (patterns, expr) + | _ -> + returnedExpression patternsWithLabel patternsWithNolabel expr) + | _ -> (patternsWithLabel, patternsWithNolabel, expr) + in + let patternsWithLabel, patternsWithNolabel, expression = + returnedExpression [] [] expression in - let patternsWithLid, expression = returnedExpression [] expression in let pattern = - match patternsWithLid with + match patternsWithLabel with | [] -> Pat.any () - | _ -> Pat.record (List.rev patternsWithLid) Open + | _ -> Pat.record (List.rev patternsWithLabel) Open in - (* add patttern matching for optional prop value *) + (* add pattern matching for optional prop value *) let expression = if List.length vbMatchList = 0 then expression else Exp.let_ Nonrecursive vbMatchList expression in - (* add let _ = ref to ignore unused warning *) let expression = - match hasForwardRef with - | true -> - let expression = - Exp.let_ Nonrecursive [vbIgnoreUnusedRef] expression - in - Exp.let_ Nonrecursive [vbRefFromOption] expression - | false -> expression + List.fold_left + (fun expr (_, pattern) -> Exp.fun_ Nolabel None pattern expr) + expression patternsWithNolabel in let expression = Exp.fun_ Nolabel None diff --git a/tests/ppx/react/expected/forwardRef.res.txt b/tests/ppx/react/expected/forwardRef.res.txt index d54b268a..0f2f7b65 100644 --- a/tests/ppx/react/expected/forwardRef.res.txt +++ b/tests/ppx/react/expected/forwardRef.res.txt @@ -71,10 +71,7 @@ module FancyInput = { } @react.component - let make = ({?className, children, ?ref, _}: props<'className, 'children>) => { - let ref = Js.Nullable.fromOption(ref) - let _ = ref - + let make = ({?className, children, _}: props<'className, 'children>, ref) => ReactDOMRe.createDOMElementVariadic( "div", [ @@ -91,10 +88,8 @@ module FancyInput = { children, ], ) - } let make = React.forwardRef({ - let \"ForwardRef$FancyInput" = (props: props<_>, ref) => - make({...props, ref: ?Js.Nullable.toOption(ref)}) + let \"ForwardRef$FancyInput" = (props: props<_>, ref) => make(props, ref) \"ForwardRef$FancyInput" }) } @@ -130,10 +125,7 @@ module FancyInput = { } @react.component - let make = ({?className, children, ?ref, _}: props<'className, 'children>) => { - let ref = Js.Nullable.fromOption(ref) - let _ = ref - + let make = ({?className, children, _}: props<'className, 'children>, ref) => ReactDOM.jsxs( "div", { @@ -150,10 +142,8 @@ module FancyInput = { ]), }, ) - } let make = React.forwardRef({ - let \"ForwardRef$FancyInput" = (props: props<_>, ref) => - make({...props, ref: ?Js.Nullable.toOption(ref)}) + let \"ForwardRef$FancyInput" = (props: props<_>, ref) => make(props, ref) \"ForwardRef$FancyInput" }) } From f4cb119b3c83ef74c0b96644c9f7d51c0b241223 Mon Sep 17 00:00:00 2001 From: Woonki Moon Date: Sun, 17 Jul 2022 20:24:56 +0900 Subject: [PATCH 82/94] update V4 spec doc --- cli/JSXV4.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/cli/JSXV4.md b/cli/JSXV4.md index 55ee6045..ab29a282 100644 --- a/cli/JSXV4.md +++ b/cli/JSXV4.md @@ -23,11 +23,8 @@ let make = React.forwardRef((~x, ~y, ref) => body) let make = React.forwardRef({ let fn = - @react.component (~x, ~y, ~ref=?) => { - let ref = ref->Js.Nullable.fromOption - body - } - (props, ref) => fn({...props, ref: {ref->Js.Nullable.toOption}}) + @react.component (~x, ~y) => ref => body + (props, ref) => fn(props, ref) }) ``` From 8c62db048f354aca52e8a90553afc6228c17cf25 Mon Sep 17 00:00:00 2001 From: Woonki Moon Date: Sun, 17 Jul 2022 23:02:48 +0900 Subject: [PATCH 83/94] change order of fields in props type record more clean up for forwardRef --- cli/reactjs_jsx_ppx.ml | 92 ++++++++++----------- tests/ppx/react/expected/forwardRef.res.txt | 4 +- 2 files changed, 46 insertions(+), 50 deletions(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index 15b957f3..6fac2375 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -1600,56 +1600,39 @@ module V4 = struct |> List.filter_map (fun (_isOptional, label, _, interiorType) -> if label = "key" || label = "ref" then None else Some interiorType) - (* type props<'id, 'name, ...> = { @optional key: string, @optional id: 'id, ... } *) - let makePropsRecordType propsName loc namedTypeList = - let labelDeclList = - namedTypeList - |> List.map (fun (isOptional, label, _, _interiorType) -> - if label = "key" then - Type.field ~loc ~attrs:optionalAttr {txt = label; loc} - (keyType Location.none) - else if label = "ref" then - Type.field ~loc - ~attrs:(if isOptional then optionalAttr else []) - {txt = label; loc} (refType Location.none) - else if isOptional then - Type.field ~loc ~attrs:optionalAttr {txt = label; loc} - (Typ.var label) - else Type.field ~loc {txt = label; loc} (Typ.var label)) - in + let makeLabelDecls ~loc namedTypeList = + namedTypeList + |> List.map (fun (isOptional, label, _, interiorType) -> + if label = "key" then + Type.field ~loc ~attrs:optionalAttr {txt = label; loc} interiorType + else if label = "ref" then + Type.field ~loc + ~attrs:(if isOptional then optionalAttr else []) + {txt = label; loc} interiorType + else if isOptional then + Type.field ~loc ~attrs:optionalAttr {txt = label; loc} + (Typ.var label) + else Type.field ~loc {txt = label; loc} (Typ.var label)) + + let makeTypeDecls propsName loc namedTypeList = + let labelDeclList = makeLabelDecls ~loc namedTypeList in (* 'id, 'className, ... *) let params = makePropsTypeParamsTvar namedTypeList |> List.map (fun coreType -> (coreType, Invariant)) in - Str.type_ Nonrecursive - [ - Type.mk ~loc ~params {txt = propsName; loc} - ~kind:(Ptype_record labelDeclList); - ] + [ + Type.mk ~loc ~params {txt = propsName; loc} + ~kind:(Ptype_record labelDeclList); + ] + + (* type props<'id, 'name, ...> = { @optional key: string, @optional id: 'id, ... } *) + let makePropsRecordType propsName loc namedTypeList = + Str.type_ Nonrecursive (makeTypeDecls propsName loc namedTypeList) (* type props<'id, 'name, ...> = { @optional key: string, @optional id: 'id, ... } *) let makePropsRecordTypeSig propsName loc namedTypeList = - let labelDeclList = - namedTypeList - |> List.map (fun (isOptional, label, _, _interiorType) -> - if label = "key" then - Type.field ~loc ~attrs:optionalAttr {txt = label; loc} - (keyType Location.none) - else if isOptional then - Type.field ~loc ~attrs:optionalAttr {txt = label; loc} - (Typ.var label) - else Type.field ~loc {txt = label; loc} (Typ.var label)) - in - let params = - makePropsTypeParamsTvar namedTypeList - |> List.map (fun coreType -> (coreType, Invariant)) - in - Sig.type_ Nonrecursive - [ - Type.mk ~loc ~params {txt = propsName; loc} - ~kind:(Ptype_record labelDeclList); - ] + Sig.type_ Nonrecursive (makeTypeDecls propsName loc namedTypeList) let transformUppercaseCall3 ~config modulePath mapper loc attrs callArguments = @@ -2279,10 +2262,11 @@ module V4 = struct (* type props = { ... } *) let propsRecordType = makePropsRecordType "props" emptyLoc - (((true, "key", [], keyType emptyLoc) :: namedTypeList) - @ - if hasForwardRef then [(true, "ref", [], refType Location.none)] - else []) + ([(true, "key", [], keyType emptyLoc)] + @ (if hasForwardRef then + [(true, "ref", [], refType Location.none)] + else []) + @ namedTypeList) in let innerExpression = Exp.apply @@ -2459,12 +2443,20 @@ module V4 = struct match List.filter hasAttr pval_attributes with | [] -> [item] | [_] -> + let hasForwardRef = ref false in let rec getPropTypes types ({ptyp_loc; ptyp_desc} as fullType) = match ptyp_desc with | Ptyp_arrow (name, type_, ({ptyp_desc = Ptyp_arrow _} as rest)) when isOptional name || isLabelled name -> getPropTypes ((name, ptyp_loc, type_) :: types) rest - | Ptyp_arrow (Nolabel, _type, rest) -> getPropTypes types rest + | Ptyp_arrow + ( Nolabel, + {ptyp_desc = Ptyp_constr ({txt = Lident "unit"}, _)}, + rest ) -> + getPropTypes types rest + | Ptyp_arrow (Nolabel, _type, rest) -> + hasForwardRef := true; + getPropTypes types rest | Ptyp_arrow (name, type_, returnValue) when isOptional name || isLabelled name -> (returnValue, (name, returnValue.ptyp_loc, type_) :: types) @@ -2479,7 +2471,11 @@ module V4 = struct in let propsRecordType = makePropsRecordTypeSig "props" Location.none - ((true, "key", [], keyType Location.none) :: namedTypeList) + ([(true, "key", [], keyType Location.none)] + (* If there is Nolabel arg, regard the type as ref in forwardRef *) + @ (if !hasForwardRef then [(true, "ref", [], refType Location.none)] + else []) + @ namedTypeList) in (* can't be an arrow because it will defensively uncurry *) let newExternalType = diff --git a/tests/ppx/react/expected/forwardRef.res.txt b/tests/ppx/react/expected/forwardRef.res.txt index 0f2f7b65..8086cec7 100644 --- a/tests/ppx/react/expected/forwardRef.res.txt +++ b/tests/ppx/react/expected/forwardRef.res.txt @@ -65,9 +65,9 @@ let make = { module FancyInput = { type props<'className, 'children> = { key?: string, + ref?: ReactDOM.Ref.currentDomRef, className?: 'className, children: 'children, - ref?: ReactDOM.Ref.currentDomRef, } @react.component @@ -119,9 +119,9 @@ let make = { module FancyInput = { type props<'className, 'children> = { key?: string, + ref?: ReactDOM.Ref.currentDomRef, className?: 'className, children: 'children, - ref?: ReactDOM.Ref.currentDomRef, } @react.component From bbb6398e293c08aa10f4857507f1ea2b7ec2479a Mon Sep 17 00:00:00 2001 From: Woonki Moon Date: Wed, 20 Jul 2022 00:19:59 +0900 Subject: [PATCH 84/94] strip explicit option from arg in implementation --- cli/reactjs_jsx_ppx.ml | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index 6fac2375..d59edfac 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -1592,13 +1592,24 @@ module V4 = struct |> List.filter_map (fun (_isOptional, label, _, _interiorType) -> if label = "key" || label = "ref" then None else Some (Typ.var label)) + let stripOption coreType = + match coreType with + | {ptyp_desc = Ptyp_constr ({txt = Lident "option"}, coreTypes)} -> + List.nth_opt coreTypes 0 + | _ -> Some coreType + (* make type params for make sig arguments and for external *) (* let make: React.componentLike>, React.element> *) (* external make: React.componentLike, React.element> = "default" *) - let makePropsTypeParams namedTypeList = + let makePropsTypeParams ?(stripExplicitOption = false) namedTypeList = namedTypeList - |> List.filter_map (fun (_isOptional, label, _, interiorType) -> - if label = "key" || label = "ref" then None else Some interiorType) + |> List.filter_map (fun (isOptional, label, _, interiorType) -> + if label = "key" || label = "ref" then None + (* Strip the explicit option type in implementation *) + (* let make = (~x: option=?) => ... *) + else if isOptional && stripExplicitOption then + stripOption interiorType + else Some interiorType) let makeLabelDecls ~loc namedTypeList = namedTypeList @@ -2372,7 +2383,7 @@ module V4 = struct (Pat.constraint_ pattern (Typ.constr ~loc:emptyLoc {txt = Lident "props"; loc = emptyLoc} - (makePropsTypeParams namedTypeList))) + (makePropsTypeParams ~stripExplicitOption:true namedTypeList))) expression in (* let make = ({id, name, ...}: props<'id, 'name, ...>) => { ... } *) From 8bc3e5d7b0dff0a05bf209a178eeb272baca61a2 Mon Sep 17 00:00:00 2001 From: Woonki Moon Date: Wed, 20 Jul 2022 00:58:49 +0900 Subject: [PATCH 85/94] make type param label safe --- cli/reactjs_jsx_ppx.ml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index d59edfac..cc03fc71 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -1590,7 +1590,8 @@ module V4 = struct let makePropsTypeParamsTvar namedTypeList = namedTypeList |> List.filter_map (fun (_isOptional, label, _, _interiorType) -> - if label = "key" || label = "ref" then None else Some (Typ.var label)) + if label = "key" || label = "ref" then None + else Some (Typ.var @@ safeTypeFromValue (Labelled label))) let stripOption coreType = match coreType with @@ -1622,8 +1623,10 @@ module V4 = struct {txt = label; loc} interiorType else if isOptional then Type.field ~loc ~attrs:optionalAttr {txt = label; loc} - (Typ.var label) - else Type.field ~loc {txt = label; loc} (Typ.var label)) + (Typ.var @@ safeTypeFromValue @@ Labelled label) + else + Type.field ~loc {txt = label; loc} + (Typ.var @@ safeTypeFromValue @@ Labelled label)) let makeTypeDecls propsName loc namedTypeList = let labelDeclList = makeLabelDecls ~loc namedTypeList in From d3b5505cb3028355c9c846aeef5a94b9248606c3 Mon Sep 17 00:00:00 2001 From: Woonki Moon Date: Thu, 21 Jul 2022 00:37:51 +0900 Subject: [PATCH 86/94] check each module has one react comp at most --- cli/reactjs_jsx_ppx.ml | 720 +++++++++--------- .../react/expected/fileLevelConfig.res.txt | 57 +- tests/ppx/react/expected/forwardRef.res.txt | 282 +++---- tests/ppx/react/expected/innerModule.res.txt | 54 -- tests/ppx/react/expected/newtype.res.txt | 74 +- tests/ppx/react/expected/topLevel.res.txt | 64 +- .../ppx/react/expected/typeConstraint.res.txt | 56 +- tests/ppx/react/fileLevelConfig.res | 24 +- tests/ppx/react/forwardRef.res | 108 +-- tests/ppx/react/innerModule.res | 30 - tests/ppx/react/newtype.res | 18 +- tests/ppx/react/topLevel.res | 30 +- tests/ppx/react/typeConstraint.res | 30 +- 13 files changed, 778 insertions(+), 769 deletions(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index cc03fc71..1ff539b7 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -70,11 +70,14 @@ let isJsxConfigAttr ((loc, _) : attribute) = loc.txt = "jsxConfig" let processConfigAttribute attribute config = if isJsxConfigAttr attribute then updateConfig config (snd attribute) -module V3 = struct - let rec find_opt p = function - | [] -> None - | x :: l -> if p x then Some x else find_opt p l +(* Helper method to look up the [@react.component] attribute *) +let hasAttr (loc, _) = loc.txt = "react.component" + +(* Iterate over the attributes and try to find the [@react.component] attribute *) +let hasAttrOnBinding {pvb_attributes} = + List.find_opt hasAttr pvb_attributes <> None +module V3 = struct let nolabel = Nolabel let labelled str = Labelled str @@ -192,16 +195,9 @@ module V3 = struct let merlinFocus = ({loc = Location.none; txt = "merlin.focus"}, PStr []) - (* Helper method to look up the [@react.component] attribute *) - let hasAttr (loc, _) = loc.txt = "react.component" - (* Helper method to filter out any attribute that isn't [@react.component] *) let otherAttrsPure (loc, _) = loc.txt <> "react.component" - (* Iterate over the attributes and try to find the [@react.component] attribute *) - let hasAttrOnBinding {pvb_attributes} = - find_opt hasAttr pvb_attributes <> None - (* Finds the name of the variable the binding is assigned to, otherwise raises Invalid_argument *) let rec getFnName binding = match binding with @@ -1332,10 +1328,6 @@ module V3 = struct end module V4 = struct - let rec find_opt p = function - | [] -> None - | x :: l -> if p x then Some x else find_opt p l - let nolabel = Nolabel let labelled str = Labelled str @@ -1463,16 +1455,9 @@ module V4 = struct let merlinFocus = ({loc = Location.none; txt = "merlin.focus"}, PStr []) - (* Helper method to look up the [@react.component] attribute *) - let hasAttr (loc, _) = loc.txt = "react.component" - (* Helper method to filter out any attribute that isn't [@react.component] *) let otherAttrsPure (loc, _) = loc.txt <> "react.component" - (* Iterate over the attributes and try to find the [@react.component] attribute *) - let hasAttrOnBinding {pvb_attributes} = - find_opt hasAttr pvb_attributes <> None - (* Finds the name of the variable the binding is assigned to, otherwise raises Invalid_argument *) let rec getFnName binding = match binding with @@ -2019,7 +2004,7 @@ module V4 = struct | name when isOptional name -> (true, getLabel name, [], type_) :: types | _ -> types - let transformStructureItem ~config mapper item = + let transformStructureItem ~hasReactComponent ~config mapper item = match item with (* external *) | { @@ -2030,6 +2015,11 @@ module V4 = struct match List.filter hasAttr pval_attributes with | [] -> [item] | [_] -> + (* If there is another @react.component, throw error *) + if !hasReactComponent then + Location.raise_errorf ~loc:pstr_loc + "Each module should have one react component at most" + else hasReactComponent := true; let rec getPropTypes types ({ptyp_loc; ptyp_desc} as fullType) = match ptyp_desc with | Ptyp_arrow (name, type_, ({ptyp_desc = Ptyp_arrow _} as rest)) @@ -2083,339 +2073,354 @@ module V4 = struct let emptyLoc = Location.in_file fileName in let mapBinding binding = if hasAttrOnBinding binding then - let bindingLoc = binding.pvb_loc in - let bindingPatLoc = binding.pvb_pat.ppat_loc in - let binding = - { - binding with - pvb_pat = {binding.pvb_pat with ppat_loc = emptyLoc}; - pvb_loc = emptyLoc; - } - in - let fnName = getFnName binding.pvb_pat in - let internalFnName = fnName ^ "$Internal" in - let fullModuleName = - makeModuleName fileName config.nestedModules fnName - in - let modifiedBindingOld binding = - let expression = binding.pvb_expr in - (* TODO: there is a long-tail of unsupported features inside of blocks - Pexp_letmodule , Pexp_letexception , Pexp_ifthenelse *) - let rec spelunkForFunExpression expression = - match expression with - (* let make = (~prop) => ... *) - | {pexp_desc = Pexp_fun _} | {pexp_desc = Pexp_newtype _} -> - expression - (* let make = {let foo = bar in (~prop) => ...} *) - | {pexp_desc = Pexp_let (_recursive, _vbs, returnExpression)} -> - (* here's where we spelunk! *) - spelunkForFunExpression returnExpression - (* let make = React.forwardRef((~prop) => ...) *) - | { - pexp_desc = - Pexp_apply - (_wrapperExpression, [(Nolabel, innerFunctionExpression)]); - } -> - spelunkForFunExpression innerFunctionExpression - | { - pexp_desc = - Pexp_sequence (_wrapperExpression, innerFunctionExpression); - } -> - spelunkForFunExpression innerFunctionExpression - | {pexp_desc = Pexp_constraint (innerFunctionExpression, _typ)} -> - spelunkForFunExpression innerFunctionExpression - | _ -> - raise - (Invalid_argument - "react.component calls can only be on function \ - definitions or component wrappers (forwardRef, memo).") - [@@raises Invalid_argument] - in - spelunkForFunExpression expression - in - let modifiedBinding binding = - let hasApplication = ref false in - let wrapExpressionWithBinding expressionFn expression = - Vb.mk ~loc:bindingLoc - ~attrs:(List.filter otherAttrsPure binding.pvb_attributes) - (Pat.var ~loc:bindingPatLoc {loc = bindingPatLoc; txt = fnName}) - (expressionFn expression) + if !hasReactComponent then + Location.raise_errorf ~loc:pstr_loc + "Each module should have one react component at most" + else ( + hasReactComponent := true; + let bindingLoc = binding.pvb_loc in + let bindingPatLoc = binding.pvb_pat.ppat_loc in + let binding = + { + binding with + pvb_pat = {binding.pvb_pat with ppat_loc = emptyLoc}; + pvb_loc = emptyLoc; + } in - let expression = binding.pvb_expr in - (* TODO: there is a long-tail of unsupported features inside of blocks - Pexp_letmodule , Pexp_letexception , Pexp_ifthenelse *) - let rec spelunkForFunExpression expression = - match expression with - (* let make = (~prop) => ... with no final unit *) - | { - pexp_desc = - Pexp_fun - ( ((Labelled _ | Optional _) as label), - default, - pattern, - ({pexp_desc = Pexp_fun _} as internalExpression) ); - } -> - let wrap, hasForwardRef, exp = - spelunkForFunExpression internalExpression - in - ( wrap, - hasForwardRef, - { - expression with - pexp_desc = Pexp_fun (label, default, pattern, exp); - } ) - (* let make = (()) => ... *) - (* let make = (_) => ... *) - | { - pexp_desc = - Pexp_fun - ( Nolabel, - _default, - { - ppat_desc = - Ppat_construct ({txt = Lident "()"}, _) | Ppat_any; - }, - _internalExpression ); - } -> - ((fun a -> a), false, expression) - (* let make = (~prop) => ... *) - | { - pexp_desc = - Pexp_fun - ( (Labelled _ | Optional _), - _default, - _pattern, - _internalExpression ); - } -> - ((fun a -> a), false, expression) - (* let make = (prop) => ... *) - | { - pexp_desc = - Pexp_fun (_nolabel, _default, pattern, _internalExpression); - } -> - if !hasApplication then ((fun a -> a), false, expression) - else - Location.raise_errorf ~loc:pattern.ppat_loc - "React: props need to be labelled arguments.\n\ - \ If you are working with refs be sure to wrap with \ - React.forwardRef.\n\ - \ If your component doesn't have any props use () or _ \ - instead of a name." - (* let make = {let foo = bar in (~prop) => ...} *) - | {pexp_desc = Pexp_let (recursive, vbs, internalExpression)} -> - (* here's where we spelunk! *) - let wrap, hasForwardRef, exp = - spelunkForFunExpression internalExpression - in - ( wrap, - hasForwardRef, - {expression with pexp_desc = Pexp_let (recursive, vbs, exp)} - ) - (* let make = React.forwardRef((~prop) => ...) *) - | { - pexp_desc = - Pexp_apply (wrapperExpression, [(Nolabel, internalExpression)]); - } -> - let () = hasApplication := true in - let _, _, exp = spelunkForFunExpression internalExpression in - let hasForwardRef = isForwardRef wrapperExpression in - ( (fun exp -> Exp.apply wrapperExpression [(nolabel, exp)]), - hasForwardRef, - exp ) - | { - pexp_desc = Pexp_sequence (wrapperExpression, internalExpression); - } -> - let wrap, hasForwardRef, exp = - spelunkForFunExpression internalExpression - in - ( wrap, - hasForwardRef, - { - expression with - pexp_desc = Pexp_sequence (wrapperExpression, exp); - } ) - | e -> ((fun a -> a), false, e) + let fnName = getFnName binding.pvb_pat in + let internalFnName = fnName ^ "$Internal" in + let fullModuleName = + makeModuleName fileName config.nestedModules fnName in - let wrapExpression, hasForwardRef, expression = + let modifiedBindingOld binding = + let expression = binding.pvb_expr in + (* TODO: there is a long-tail of unsupported features inside of blocks - Pexp_letmodule , Pexp_letexception , Pexp_ifthenelse *) + let rec spelunkForFunExpression expression = + match expression with + (* let make = (~prop) => ... *) + | {pexp_desc = Pexp_fun _} | {pexp_desc = Pexp_newtype _} -> + expression + (* let make = {let foo = bar in (~prop) => ...} *) + | {pexp_desc = Pexp_let (_recursive, _vbs, returnExpression)} -> + (* here's where we spelunk! *) + spelunkForFunExpression returnExpression + (* let make = React.forwardRef((~prop) => ...) *) + | { + pexp_desc = + Pexp_apply + (_wrapperExpression, [(Nolabel, innerFunctionExpression)]); + } -> + spelunkForFunExpression innerFunctionExpression + | { + pexp_desc = + Pexp_sequence (_wrapperExpression, innerFunctionExpression); + } -> + spelunkForFunExpression innerFunctionExpression + | {pexp_desc = Pexp_constraint (innerFunctionExpression, _typ)} + -> + spelunkForFunExpression innerFunctionExpression + | _ -> + raise + (Invalid_argument + "react.component calls can only be on function \ + definitions or component wrappers (forwardRef, memo).") + [@@raises Invalid_argument] + in spelunkForFunExpression expression in - (wrapExpressionWithBinding wrapExpression, hasForwardRef, expression) - in - let bindingWrapper, hasForwardRef, expression = - modifiedBinding binding - in - (* do stuff here! *) - let namedArgList, newtypes, typeConstraints = - recursivelyTransformNamedArgsForMake mapper - (modifiedBindingOld binding) - [] [] None - in - let namedTypeList = - List.fold_left - (argToType ~newtypes ~typeConstraints) - [] namedArgList - in - let namedArgWithDefaultValueList = - List.filter_map argWithDefaultValue namedArgList - in - let vbMatch (label, default) = - Vb.mk - (Pat.var (Location.mknoloc label)) - (Exp.match_ - (Exp.ident {txt = Lident label; loc = Location.none}) - [ - Exp.case - (Pat.construct - (Location.mknoloc @@ Lident "Some") - (Some (Pat.var (Location.mknoloc label)))) - (Exp.ident (Location.mknoloc @@ Lident label)); - Exp.case - (Pat.construct (Location.mknoloc @@ Lident "None") None) - default; - ]) - in - let vbMatchList = List.map vbMatch namedArgWithDefaultValueList in - (* type props = { ... } *) - let propsRecordType = - makePropsRecordType "props" emptyLoc - ([(true, "key", [], keyType emptyLoc)] - @ (if hasForwardRef then - [(true, "ref", [], refType Location.none)] - else []) - @ namedTypeList) - in - let innerExpression = - Exp.apply - (Exp.ident (Location.mknoloc @@ Lident "make")) - ([(Nolabel, Exp.ident (Location.mknoloc @@ Lident "props"))] - @ - match hasForwardRef with - | true -> - [(Nolabel, Exp.ident (Location.mknoloc @@ Lident "ref"))] - | false -> []) - in - let fullExpression = - (* React component name should start with uppercase letter *) - (* let make = { let \"App" = props => make(props); \"App" } *) - (* let make = React.forwardRef({ - let \"App" = (props, ref) => make({...props, ref: @optional (Js.Nullabel.toOption(ref))}) - })*) - Exp.fun_ nolabel None - (match namedTypeList with - | [] -> Pat.var @@ Location.mknoloc "props" - | _ -> - Pat.constraint_ - (Pat.var @@ Location.mknoloc "props") - (Typ.constr (Location.mknoloc @@ Lident "props") [Typ.any ()])) - (if hasForwardRef then - Exp.fun_ nolabel None - (Pat.var @@ Location.mknoloc "ref") - innerExpression - else innerExpression) - in - let fullExpression = - match fullModuleName with - | "" -> fullExpression - | txt -> - Exp.let_ Nonrecursive - [ - Vb.mk ~loc:emptyLoc - (Pat.var ~loc:emptyLoc {loc = emptyLoc; txt}) - fullExpression; - ] - (Exp.ident ~loc:emptyLoc {loc = emptyLoc; txt = Lident txt}) - in - let rec returnedExpression patternsWithLabel patternsWithNolabel - ({pexp_desc} as expr) = - match pexp_desc with - | Pexp_newtype (_, expr) -> - returnedExpression patternsWithLabel patternsWithNolabel expr - | Pexp_constraint (expr, _) -> - returnedExpression patternsWithLabel patternsWithNolabel expr - | Pexp_fun - ( _arg_label, - _default, - { - ppat_desc = - Ppat_construct ({txt = Lident "()"}, _) | Ppat_any; - }, - expr ) -> - (patternsWithLabel, patternsWithNolabel, expr) - | Pexp_fun (arg_label, _default, {ppat_loc; ppat_desc}, expr) -> ( - if isLabelled arg_label || isOptional arg_label then - returnedExpression - (( {loc = ppat_loc; txt = Lident (getLabel arg_label)}, - Pat.var - ~attrs: - (if isOptional arg_label then optionalAttr else []) - {txt = getLabel arg_label; loc = ppat_loc} ) - :: patternsWithLabel) - patternsWithNolabel expr - else - (* Special case of nolabel arg "ref" in forwardRef fn *) - (* let make = React.forwardRef(ref => body) *) - match ppat_desc with - | Ppat_var {txt} -> - returnedExpression patternsWithLabel - (( {loc = ppat_loc; txt = Lident txt}, - Pat.var ~attrs:optionalAttr {txt; loc = ppat_loc} ) - :: patternsWithNolabel) - expr + let modifiedBinding binding = + let hasApplication = ref false in + let wrapExpressionWithBinding expressionFn expression = + Vb.mk ~loc:bindingLoc + ~attrs:(List.filter otherAttrsPure binding.pvb_attributes) + (Pat.var ~loc:bindingPatLoc + {loc = bindingPatLoc; txt = fnName}) + (expressionFn expression) + in + let expression = binding.pvb_expr in + (* TODO: there is a long-tail of unsupported features inside of blocks - Pexp_letmodule , Pexp_letexception , Pexp_ifthenelse *) + let rec spelunkForFunExpression expression = + match expression with + (* let make = (~prop) => ... with no final unit *) + | { + pexp_desc = + Pexp_fun + ( ((Labelled _ | Optional _) as label), + default, + pattern, + ({pexp_desc = Pexp_fun _} as internalExpression) ); + } -> + let wrap, hasForwardRef, exp = + spelunkForFunExpression internalExpression + in + ( wrap, + hasForwardRef, + { + expression with + pexp_desc = Pexp_fun (label, default, pattern, exp); + } ) + (* let make = (()) => ... *) + (* let make = (_) => ... *) + | { + pexp_desc = + Pexp_fun + ( Nolabel, + _default, + { + ppat_desc = + Ppat_construct ({txt = Lident "()"}, _) | Ppat_any; + }, + _internalExpression ); + } -> + ((fun a -> a), false, expression) + (* let make = (~prop) => ... *) + | { + pexp_desc = + Pexp_fun + ( (Labelled _ | Optional _), + _default, + _pattern, + _internalExpression ); + } -> + ((fun a -> a), false, expression) + (* let make = (prop) => ... *) + | { + pexp_desc = + Pexp_fun (_nolabel, _default, pattern, _internalExpression); + } -> + if !hasApplication then ((fun a -> a), false, expression) + else + Location.raise_errorf ~loc:pattern.ppat_loc + "React: props need to be labelled arguments.\n\ + \ If you are working with refs be sure to wrap with \ + React.forwardRef.\n\ + \ If your component doesn't have any props use () or _ \ + instead of a name." + (* let make = {let foo = bar in (~prop) => ...} *) + | {pexp_desc = Pexp_let (recursive, vbs, internalExpression)} -> + (* here's where we spelunk! *) + let wrap, hasForwardRef, exp = + spelunkForFunExpression internalExpression + in + ( wrap, + hasForwardRef, + {expression with pexp_desc = Pexp_let (recursive, vbs, exp)} + ) + (* let make = React.forwardRef((~prop) => ...) *) + | { + pexp_desc = + Pexp_apply + (wrapperExpression, [(Nolabel, internalExpression)]); + } -> + let () = hasApplication := true in + let _, _, exp = spelunkForFunExpression internalExpression in + let hasForwardRef = isForwardRef wrapperExpression in + ( (fun exp -> Exp.apply wrapperExpression [(nolabel, exp)]), + hasForwardRef, + exp ) + | { + pexp_desc = + Pexp_sequence (wrapperExpression, internalExpression); + } -> + let wrap, hasForwardRef, exp = + spelunkForFunExpression internalExpression + in + ( wrap, + hasForwardRef, + { + expression with + pexp_desc = Pexp_sequence (wrapperExpression, exp); + } ) + | e -> ((fun a -> a), false, e) + in + let wrapExpression, hasForwardRef, expression = + spelunkForFunExpression expression + in + ( wrapExpressionWithBinding wrapExpression, + hasForwardRef, + expression ) + in + let bindingWrapper, hasForwardRef, expression = + modifiedBinding binding + in + (* do stuff here! *) + let namedArgList, newtypes, typeConstraints = + recursivelyTransformNamedArgsForMake mapper + (modifiedBindingOld binding) + [] [] None + in + let namedTypeList = + List.fold_left + (argToType ~newtypes ~typeConstraints) + [] namedArgList + in + let namedArgWithDefaultValueList = + List.filter_map argWithDefaultValue namedArgList + in + let vbMatch (label, default) = + Vb.mk + (Pat.var (Location.mknoloc label)) + (Exp.match_ + (Exp.ident {txt = Lident label; loc = Location.none}) + [ + Exp.case + (Pat.construct + (Location.mknoloc @@ Lident "Some") + (Some (Pat.var (Location.mknoloc label)))) + (Exp.ident (Location.mknoloc @@ Lident label)); + Exp.case + (Pat.construct (Location.mknoloc @@ Lident "None") None) + default; + ]) + in + let vbMatchList = List.map vbMatch namedArgWithDefaultValueList in + (* type props = { ... } *) + let propsRecordType = + makePropsRecordType "props" emptyLoc + ([(true, "key", [], keyType emptyLoc)] + @ (if hasForwardRef then + [(true, "ref", [], refType Location.none)] + else []) + @ namedTypeList) + in + let innerExpression = + Exp.apply + (Exp.ident (Location.mknoloc @@ Lident "make")) + ([(Nolabel, Exp.ident (Location.mknoloc @@ Lident "props"))] + @ + match hasForwardRef with + | true -> + [(Nolabel, Exp.ident (Location.mknoloc @@ Lident "ref"))] + | false -> []) + in + let fullExpression = + (* React component name should start with uppercase letter *) + (* let make = { let \"App" = props => make(props); \"App" } *) + (* let make = React.forwardRef({ + let \"App" = (props, ref) => make({...props, ref: @optional (Js.Nullabel.toOption(ref))}) + })*) + Exp.fun_ nolabel None + (match namedTypeList with + | [] -> Pat.var @@ Location.mknoloc "props" | _ -> - returnedExpression patternsWithLabel patternsWithNolabel expr) - | _ -> (patternsWithLabel, patternsWithNolabel, expr) - in - let patternsWithLabel, patternsWithNolabel, expression = - returnedExpression [] [] expression - in - let pattern = - match patternsWithLabel with - | [] -> Pat.any () - | _ -> Pat.record (List.rev patternsWithLabel) Open - in - (* add pattern matching for optional prop value *) - let expression = - if List.length vbMatchList = 0 then expression - else Exp.let_ Nonrecursive vbMatchList expression - in - let expression = - List.fold_left - (fun expr (_, pattern) -> Exp.fun_ Nolabel None pattern expr) - expression patternsWithNolabel - in - let expression = - Exp.fun_ Nolabel None - (Pat.constraint_ pattern - (Typ.constr ~loc:emptyLoc - {txt = Lident "props"; loc = emptyLoc} - (makePropsTypeParams ~stripExplicitOption:true namedTypeList))) - expression - in - (* let make = ({id, name, ...}: props<'id, 'name, ...>) => { ... } *) - let bindings, newBinding = - match recFlag with - | Recursive -> - ( [ - bindingWrapper - (Exp.let_ ~loc:emptyLoc Recursive - [ - makeNewBinding binding expression internalFnName; - Vb.mk - (Pat.var {loc = emptyLoc; txt = fnName}) - fullExpression; - ] - (Exp.ident {loc = emptyLoc; txt = Lident fnName})); - ], - None ) - | Nonrecursive -> - ( [ - { - binding with - pvb_expr = expression; - pvb_pat = Pat.var {txt = fnName; loc = Location.none}; - }; - ], - Some (bindingWrapper fullExpression) ) - in - (Some propsRecordType, bindings, newBinding) + Pat.constraint_ + (Pat.var @@ Location.mknoloc "props") + (Typ.constr + (Location.mknoloc @@ Lident "props") + [Typ.any ()])) + (if hasForwardRef then + Exp.fun_ nolabel None + (Pat.var @@ Location.mknoloc "ref") + innerExpression + else innerExpression) + in + let fullExpression = + match fullModuleName with + | "" -> fullExpression + | txt -> + Exp.let_ Nonrecursive + [ + Vb.mk ~loc:emptyLoc + (Pat.var ~loc:emptyLoc {loc = emptyLoc; txt}) + fullExpression; + ] + (Exp.ident ~loc:emptyLoc {loc = emptyLoc; txt = Lident txt}) + in + let rec returnedExpression patternsWithLabel patternsWithNolabel + ({pexp_desc} as expr) = + match pexp_desc with + | Pexp_newtype (_, expr) -> + returnedExpression patternsWithLabel patternsWithNolabel expr + | Pexp_constraint (expr, _) -> + returnedExpression patternsWithLabel patternsWithNolabel expr + | Pexp_fun + ( _arg_label, + _default, + { + ppat_desc = + Ppat_construct ({txt = Lident "()"}, _) | Ppat_any; + }, + expr ) -> + (patternsWithLabel, patternsWithNolabel, expr) + | Pexp_fun (arg_label, _default, {ppat_loc; ppat_desc}, expr) -> ( + if isLabelled arg_label || isOptional arg_label then + returnedExpression + (( {loc = ppat_loc; txt = Lident (getLabel arg_label)}, + Pat.var + ~attrs: + (if isOptional arg_label then optionalAttr else []) + {txt = getLabel arg_label; loc = ppat_loc} ) + :: patternsWithLabel) + patternsWithNolabel expr + else + (* Special case of nolabel arg "ref" in forwardRef fn *) + (* let make = React.forwardRef(ref => body) *) + match ppat_desc with + | Ppat_var {txt} -> + returnedExpression patternsWithLabel + (( {loc = ppat_loc; txt = Lident txt}, + Pat.var ~attrs:optionalAttr {txt; loc = ppat_loc} ) + :: patternsWithNolabel) + expr + | _ -> + returnedExpression patternsWithLabel patternsWithNolabel + expr) + | _ -> (patternsWithLabel, patternsWithNolabel, expr) + in + let patternsWithLabel, patternsWithNolabel, expression = + returnedExpression [] [] expression + in + let pattern = + match patternsWithLabel with + | [] -> Pat.any () + | _ -> Pat.record (List.rev patternsWithLabel) Open + in + (* add pattern matching for optional prop value *) + let expression = + if List.length vbMatchList = 0 then expression + else Exp.let_ Nonrecursive vbMatchList expression + in + let expression = + List.fold_left + (fun expr (_, pattern) -> Exp.fun_ Nolabel None pattern expr) + expression patternsWithNolabel + in + let expression = + Exp.fun_ Nolabel None + (Pat.constraint_ pattern + (Typ.constr ~loc:emptyLoc + {txt = Lident "props"; loc = emptyLoc} + (makePropsTypeParams ~stripExplicitOption:true + namedTypeList))) + expression + in + (* let make = ({id, name, ...}: props<'id, 'name, ...>) => { ... } *) + let bindings, newBinding = + match recFlag with + | Recursive -> + ( [ + bindingWrapper + (Exp.let_ ~loc:emptyLoc Recursive + [ + makeNewBinding binding expression internalFnName; + Vb.mk + (Pat.var {loc = emptyLoc; txt = fnName}) + fullExpression; + ] + (Exp.ident {loc = emptyLoc; txt = Lident fnName})); + ], + None ) + | Nonrecursive -> + ( [ + { + binding with + pvb_expr = expression; + pvb_pat = Pat.var {txt = fnName; loc = Location.none}; + }; + ], + Some (bindingWrapper fullExpression) ) + in + (Some propsRecordType, bindings, newBinding)) else (None, [binding], None) [@@raises Invalid_argument] in @@ -2448,7 +2453,7 @@ module V4 = struct | _ -> [item] [@@raises Invalid_argument] - let transformSignatureItem _mapper item = + let transformSignatureItem ~hasReactComponent _mapper item = match item with | { psig_loc; @@ -2457,6 +2462,9 @@ module V4 = struct match List.filter hasAttr pval_attributes with | [] -> [item] | [_] -> + (* If there is another @react.component, throw error *) + if !hasReactComponent then raise (Invalid_argument "2") + else hasReactComponent := true; let hasForwardRef = ref false in let rec getPropTypes types ({ptyp_loc; ptyp_desc} as fullType) = match ptyp_desc with @@ -2689,6 +2697,7 @@ let getMapper ~config = in let signature mapper items = let oldConfig = saveConfig () in + let hasReactComponent = ref false in let result = List.map (fun item -> @@ -2697,7 +2706,8 @@ let getMapper ~config = | _ -> ()); let item = default_mapper.signature_item mapper item in if config.version = 3 then transformSignatureItem3 mapper item - else if config.version = 4 then transformSignatureItem4 mapper item + else if config.version = 4 then + transformSignatureItem4 ~hasReactComponent mapper item else [item]) items |> List.flatten @@ -2708,6 +2718,7 @@ let getMapper ~config = in let structure mapper items = let oldConfig = saveConfig () in + let hasReactComponent = ref false in let result = List.map (fun item -> @@ -2716,7 +2727,8 @@ let getMapper ~config = | _ -> ()); let item = default_mapper.structure_item mapper item in if config.version = 3 then transformStructureItem3 mapper item - else if config.version = 4 then transformStructureItem4 mapper item + else if config.version = 4 then + transformStructureItem4 ~hasReactComponent mapper item else [item]) items |> List.flatten diff --git a/tests/ppx/react/expected/fileLevelConfig.res.txt b/tests/ppx/react/expected/fileLevelConfig.res.txt index 373902f5..b208f661 100644 --- a/tests/ppx/react/expected/fileLevelConfig.res.txt +++ b/tests/ppx/react/expected/fileLevelConfig.res.txt @@ -1,36 +1,45 @@ @@jsxConfig({version: 3}) -@obj external makeProps: (~msg: 'msg, ~key: string=?, unit) => {"msg": 'msg} = "" -@react.component -let make = - (@warning("-16") ~msg) => { - ReactDOMRe.createDOMElementVariadic("div", [{msg->React.string}]) +module V3 = { + @obj external makeProps: (~msg: 'msg, ~key: string=?, unit) => {"msg": 'msg} = "" + + @react.component + let make = + (@warning("-16") ~msg) => { + ReactDOMRe.createDOMElementVariadic("div", [{msg->React.string}]) + } + let make = { + let \"FileLevelConfig$V3" = (\"Props": {"msg": 'msg}) => make(~msg=\"Props"["msg"]) + \"FileLevelConfig$V3" } -let make = { - let \"FileLevelConfig" = (\"Props": {"msg": 'msg}) => make(~msg=\"Props"["msg"]) - \"FileLevelConfig" } @@jsxConfig({version: 4, mode: "classic"}) -type props<'msg> = {key?: string, msg: 'msg} -@react.component -let make = ({msg, _}: props<'msg>) => { - ReactDOMRe.createDOMElementVariadic("div", [{msg->React.string}]) -} -let make = { - let \"FileLevelConfig" = (props: props<_>) => make(props) - \"FileLevelConfig" +module V4C = { + type props<'msg> = {key?: string, msg: 'msg} + + @react.component + let make = ({msg, _}: props<'msg>) => { + ReactDOMRe.createDOMElementVariadic("div", [{msg->React.string}]) + } + let make = { + let \"FileLevelConfig$V4C" = (props: props<_>) => make(props) + \"FileLevelConfig$V4C" + } } @@jsxConfig({version: 4, mode: "automatic"}) -type props<'msg> = {key?: string, msg: 'msg} -@react.component -let make = ({msg, _}: props<'msg>) => { - ReactDOM.jsx("div", {children: {msg->React.string}}) -} -let make = { - let \"FileLevelConfig" = (props: props<_>) => make(props) - \"FileLevelConfig" +module V4A = { + type props<'msg> = {key?: string, msg: 'msg} + + @react.component + let make = ({msg, _}: props<'msg>) => { + ReactDOM.jsx("div", {children: {msg->React.string}}) + } + let make = { + let \"FileLevelConfig$V4A" = (props: props<_>) => make(props) + \"FileLevelConfig$V4A" + } } diff --git a/tests/ppx/react/expected/forwardRef.res.txt b/tests/ppx/react/expected/forwardRef.res.txt index 8086cec7..3bd5e497 100644 --- a/tests/ppx/react/expected/forwardRef.res.txt +++ b/tests/ppx/react/expected/forwardRef.res.txt @@ -1,169 +1,175 @@ @@jsxConfig({version: 3}) -module FancyInput = { - @obj - external makeProps: ( - ~className: 'className=?, - ~children: 'children, - ~key: string=?, - ~ref: 'ref=?, - unit, - ) => {"className": option<'className>, "children": 'children} = "" +module V3 = { + module FancyInput = { + @obj + external makeProps: ( + ~className: 'className=?, + ~children: 'children, + ~key: string=?, + ~ref: 'ref=?, + unit, + ) => {"className": option<'className>, "children": 'children} = "" - @react.component - let make = - (@warning("-16") ~className=?, @warning("-16") ~children) => - @warning("-16") - ref => - ReactDOMRe.createDOMElementVariadic( - "div", - [ - ReactDOMRe.createDOMElementVariadic( - "input", - ~props=ReactDOMRe.domProps( - ~type_="text", - ~className?, - ~ref=?{Js.Nullable.toOption(ref)->Belt.Option.map(ReactDOM.Ref.domRef)}, - (), + @react.component + let make = + (@warning("-16") ~className=?, @warning("-16") ~children) => + @warning("-16") + ref => + ReactDOMRe.createDOMElementVariadic( + "div", + [ + ReactDOMRe.createDOMElementVariadic( + "input", + ~props=ReactDOMRe.domProps( + ~type_="text", + ~className?, + ~ref=?{Js.Nullable.toOption(ref)->Belt.Option.map(ReactDOM.Ref.domRef)}, + (), + ), + [], ), - [], - ), - children, - ], - ) - let make = React.forwardRef({ - let \"ForwardRef$FancyInput" = ( - \"Props": {"className": option<'className>, "children": 'children}, - ref, - ) => make(~children=\"Props"["children"], ~className=?\"Props"["className"], ref) - \"ForwardRef$FancyInput" - }) -} -@obj external makeProps: (~key: string=?, unit) => {.} = "" + children, + ], + ) + let make = React.forwardRef({ + let \"ForwardRef$V3$FancyInput" = ( + \"Props": {"className": option<'className>, "children": 'children}, + ref, + ) => make(~children=\"Props"["children"], ~className=?\"Props"["className"], ref) + \"ForwardRef$V3$FancyInput" + }) + } + @obj external makeProps: (~key: string=?, unit) => {.} = "" -@react.component -let make = () => { - let input = React.useRef(Js.Nullable.null) + @react.component + let make = () => { + let input = React.useRef(Js.Nullable.null) - ReactDOMRe.createDOMElementVariadic( - "div", - [ - React.createElement( - FancyInput.make, - FancyInput.makeProps(~ref=input, ~children={React.string("Click to focus")}, ()), - ), - ], - ) -} -let make = { - let \"ForwardRef" = (\"Props": {.}) => make() - \"ForwardRef" + ReactDOMRe.createDOMElementVariadic( + "div", + [ + React.createElement( + FancyInput.make, + FancyInput.makeProps(~ref=input, ~children={React.string("Click to focus")}, ()), + ), + ], + ) + } + let make = { + let \"ForwardRef$V3" = (\"Props": {.}) => make() + \"ForwardRef$V3" + } } @@jsxConfig({version: 4, mode: "classic"}) -module FancyInput = { - type props<'className, 'children> = { - key?: string, - ref?: ReactDOM.Ref.currentDomRef, - className?: 'className, - children: 'children, +module V4C = { + module FancyInput = { + type props<'className, 'children> = { + key?: string, + ref?: ReactDOM.Ref.currentDomRef, + className?: 'className, + children: 'children, + } + + @react.component + let make = ({?className, children, _}: props<'className, 'children>, ref) => + ReactDOMRe.createDOMElementVariadic( + "div", + [ + ReactDOMRe.createDOMElementVariadic( + "input", + ~props=ReactDOMRe.domProps( + ~type_="text", + ~className?, + ~ref=?{Js.Nullable.toOption(ref)->Belt.Option.map(ReactDOM.Ref.domRef)}, + (), + ), + [], + ), + children, + ], + ) + let make = React.forwardRef({ + let \"ForwardRef$V4C$FancyInput" = (props: props<_>, ref) => make(props, ref) + \"ForwardRef$V4C$FancyInput" + }) } + type props = {key?: string} @react.component - let make = ({?className, children, _}: props<'className, 'children>, ref) => + let make = (_: props) => { + let input = React.useRef(Js.Nullable.null) + ReactDOMRe.createDOMElementVariadic( "div", [ - ReactDOMRe.createDOMElementVariadic( - "input", - ~props=ReactDOMRe.domProps( - ~type_="text", - ~className?, - ~ref=?{Js.Nullable.toOption(ref)->Belt.Option.map(ReactDOM.Ref.domRef)}, - (), - ), - [], + React.createElement( + FancyInput.make, + {ref: input, children: {React.string("Click to focus")}}, ), - children, ], ) - let make = React.forwardRef({ - let \"ForwardRef$FancyInput" = (props: props<_>, ref) => make(props, ref) - \"ForwardRef$FancyInput" - }) -} -type props = {key?: string} - -@react.component -let make = (_: props) => { - let input = React.useRef(Js.Nullable.null) - - ReactDOMRe.createDOMElementVariadic( - "div", - [ - React.createElement( - FancyInput.make, - {ref: input, children: {React.string("Click to focus")}}, - ), - ], - ) -} -let make = { - let \"ForwardRef" = props => make(props) - \"ForwardRef" + } + let make = { + let \"ForwardRef$V4C" = props => make(props) + \"ForwardRef$V4C" + } } @@jsxConfig({version: 4, mode: "automatic"}) -module FancyInput = { - type props<'className, 'children> = { - key?: string, - ref?: ReactDOM.Ref.currentDomRef, - className?: 'className, - children: 'children, +module V4A = { + module FancyInput = { + type props<'className, 'children> = { + key?: string, + ref?: ReactDOM.Ref.currentDomRef, + className?: 'className, + children: 'children, + } + + @react.component + let make = ({?className, children, _}: props<'className, 'children>, ref) => + ReactDOM.jsxs( + "div", + { + children: React.array([ + ReactDOM.jsx( + "input", + { + type_: "text", + ?className, + ref: ?Js.Nullable.toOption(ref)->Belt.Option.map(ReactDOM.Ref.domRef), + }, + ), + children, + ]), + }, + ) + let make = React.forwardRef({ + let \"ForwardRef$V4A$FancyInput" = (props: props<_>, ref) => make(props, ref) + \"ForwardRef$V4A$FancyInput" + }) } + type props = {key?: string} @react.component - let make = ({?className, children, _}: props<'className, 'children>, ref) => - ReactDOM.jsxs( + let make = (_: props) => { + let input = React.useRef(Js.Nullable.null) + + ReactDOM.jsx( "div", { - children: React.array([ - ReactDOM.jsx( - "input", - { - type_: "text", - ?className, - ref: ?Js.Nullable.toOption(ref)->Belt.Option.map(ReactDOM.Ref.domRef), - }, - ), - children, - ]), + children: React.jsx( + FancyInput.make, + {ref: input, children: {React.string("Click to focus")}}, + ), }, ) - let make = React.forwardRef({ - let \"ForwardRef$FancyInput" = (props: props<_>, ref) => make(props, ref) - \"ForwardRef$FancyInput" - }) -} -type props = {key?: string} - -@react.component -let make = (_: props) => { - let input = React.useRef(Js.Nullable.null) - - ReactDOM.jsx( - "div", - { - children: React.jsx( - FancyInput.make, - {ref: input, children: {React.string("Click to focus")}}, - ), - }, - ) -} -let make = { - let \"ForwardRef" = props => make(props) - \"ForwardRef" + } + let make = { + let \"ForwardRef$V4A" = props => make(props) + \"ForwardRef$V4A" + } } diff --git a/tests/ppx/react/expected/innerModule.res.txt b/tests/ppx/react/expected/innerModule.res.txt index 8cc10ce3..28472764 100644 --- a/tests/ppx/react/expected/innerModule.res.txt +++ b/tests/ppx/react/expected/innerModule.res.txt @@ -27,57 +27,3 @@ module Bar = { \"InnerModule$Bar$component" } } - -@@jsxConfig({version: 4, mode: "classic"}) - -module Bar = { - type props<'a, 'b> = {key?: string, a: 'a, b: 'b} - - @react.component - let make = ({a, b, _}: props<'a, 'b>) => { - Js.log("This function should be named `InnerModule.react$Bar`") - ReactDOMRe.createDOMElementVariadic("div", []) - } - let make = { - let \"InnerModule$Bar" = (props: props<_>) => make(props) - \"InnerModule$Bar" - } - type props<'a, 'b> = {key?: string, a: 'a, b: 'b} - - @react.component - let component = ({a, b, _}: props<'a, 'b>) => { - Js.log("This function should be named `InnerModule.react$Bar$component`") - ReactDOMRe.createDOMElementVariadic("div", []) - } - let component = { - let \"InnerModule$Bar$component" = (props: props<_>) => make(props) - \"InnerModule$Bar$component" - } -} - -@@jsxConfig({version: 4, mode: "automatic"}) - -module Bar = { - type props<'a, 'b> = {key?: string, a: 'a, b: 'b} - - @react.component - let make = ({a, b, _}: props<'a, 'b>) => { - Js.log("This function should be named `InnerModule.react$Bar`") - ReactDOM.jsx("div", {key: ?None}) - } - let make = { - let \"InnerModule$Bar" = (props: props<_>) => make(props) - \"InnerModule$Bar" - } - type props<'a, 'b> = {key?: string, a: 'a, b: 'b} - - @react.component - let component = ({a, b, _}: props<'a, 'b>) => { - Js.log("This function should be named `InnerModule.react$Bar$component`") - ReactDOM.jsx("div", {key: ?None}) - } - let component = { - let \"InnerModule$Bar$component" = (props: props<_>) => make(props) - \"InnerModule$Bar$component" - } -} diff --git a/tests/ppx/react/expected/newtype.res.txt b/tests/ppx/react/expected/newtype.res.txt index f8b85a25..8cb1718d 100644 --- a/tests/ppx/react/expected/newtype.res.txt +++ b/tests/ppx/react/expected/newtype.res.txt @@ -1,40 +1,50 @@ @@jsxConfig({version: 3}) -@obj -external makeProps: ( - ~a: '\"type-a", - ~b: array>, - ~c: 'a, - ~key: string=?, - unit, -) => {"a": '\"type-a", "b": array>, "c": 'a} = "" - -@react.component -let make = (type a, ~a: a, ~b: array>, ~c: 'a, _) => - ReactDOMRe.createDOMElementVariadic("div", []) -let make = { - let \"Newtype" = (\"Props": {"a": '\"type-a", "b": array>, "c": 'a}) => - make(~c=\"Props"["c"], ~b=\"Props"["b"], ~a=\"Props"["a"]) - \"Newtype" + +module V3 = { + @obj + external makeProps: ( + ~a: '\"type-a", + ~b: array>, + ~c: 'a, + ~key: string=?, + unit, + ) => {"a": '\"type-a", "b": array>, "c": 'a} = "" + + @react.component + let make = (type a, ~a: a, ~b: array>, ~c: 'a, _) => + ReactDOMRe.createDOMElementVariadic("div", []) + let make = { + let \"Newtype$V3" = ( + \"Props": {"a": '\"type-a", "b": array>, "c": 'a}, + ) => make(~c=\"Props"["c"], ~b=\"Props"["b"], ~a=\"Props"["a"]) + \"Newtype$V3" + } } @@jsxConfig({version: 4, mode: "classic"}) -type props<'a, 'b, 'c> = {key?: string, a: 'a, b: 'b, c: 'c} - -@react.component -let make = ({a, b, c, _}: props<'\"type-a", array>, 'a>) => - ReactDOMRe.createDOMElementVariadic("div", []) -let make = { - let \"Newtype" = (props: props<_>) => make(props) - \"Newtype" + +module V4C = { + type props<'a, 'b, 'c> = {key?: string, a: 'a, b: 'b, c: 'c} + + @react.component + let make = ({a, b, c, _}: props<'\"type-a", array>, 'a>) => + ReactDOMRe.createDOMElementVariadic("div", []) + let make = { + let \"Newtype$V4C" = (props: props<_>) => make(props) + \"Newtype$V4C" + } } @@jsxConfig({version: 4, mode: "automatic"}) -type props<'a, 'b, 'c> = {key?: string, a: 'a, b: 'b, c: 'c} - -@react.component -let make = ({a, b, c, _}: props<'\"type-a", array>, 'a>) => - ReactDOM.jsx("div", {key: ?None}) -let make = { - let \"Newtype" = (props: props<_>) => make(props) - \"Newtype" + +module V4A = { + type props<'a, 'b, 'c> = {key?: string, a: 'a, b: 'b, c: 'c} + + @react.component + let make = ({a, b, c, _}: props<'\"type-a", array>, 'a>) => + ReactDOM.jsx("div", {key: ?None}) + let make = { + let \"Newtype$V4A" = (props: props<_>) => make(props) + \"Newtype$V4A" + } } diff --git a/tests/ppx/react/expected/topLevel.res.txt b/tests/ppx/react/expected/topLevel.res.txt index c2188eb5..2cc8aed1 100644 --- a/tests/ppx/react/expected/topLevel.res.txt +++ b/tests/ppx/react/expected/topLevel.res.txt @@ -1,39 +1,49 @@ @@jsxConfig({version: 3}) -@obj external makeProps: (~a: 'a, ~b: 'b, ~key: string=?, unit) => {"a": 'a, "b": 'b} = "" -@react.component -let make = - (@warning("-16") ~a, @warning("-16") ~b, _) => { - Js.log("This function should be named 'TopLevel.react'") - ReactDOMRe.createDOMElementVariadic("div", []) +module V3 = { + @obj external makeProps: (~a: 'a, ~b: 'b, ~key: string=?, unit) => {"a": 'a, "b": 'b} = "" + + @react.component + let make = + (@warning("-16") ~a, @warning("-16") ~b, _) => { + Js.log("This function should be named 'TopLevel.react'") + ReactDOMRe.createDOMElementVariadic("div", []) + } + let make = { + let \"TopLevel$V3" = (\"Props": {"a": 'a, "b": 'b}) => + make(~b=\"Props"["b"], ~a=\"Props"["a"], ()) + \"TopLevel$V3" } -let make = { - let \"TopLevel" = (\"Props": {"a": 'a, "b": 'b}) => make(~b=\"Props"["b"], ~a=\"Props"["a"], ()) - \"TopLevel" } @@jsxConfig({version: 4, mode: "classic"}) -type props<'a, 'b> = {key?: string, a: 'a, b: 'b} -@react.component -let make = ({a, b, _}: props<'a, 'b>) => { - Js.log("This function should be named 'TopLevel.react'") - ReactDOMRe.createDOMElementVariadic("div", []) -} -let make = { - let \"TopLevel" = (props: props<_>) => make(props) - \"TopLevel" +module V4C = { + type props<'a, 'b> = {key?: string, a: 'a, b: 'b} + + @react.component + let make = ({a, b, _}: props<'a, 'b>) => { + Js.log("This function should be named 'TopLevel.react'") + ReactDOMRe.createDOMElementVariadic("div", []) + } + let make = { + let \"TopLevel$V4C" = (props: props<_>) => make(props) + \"TopLevel$V4C" + } } @@jsxConfig({version: 4, mode: "automatic"}) -type props<'a, 'b> = {key?: string, a: 'a, b: 'b} -@react.component -let make = ({a, b, _}: props<'a, 'b>) => { - Js.log("This function should be named 'TopLevel.react'") - ReactDOM.jsx("div", {key: ?None}) -} -let make = { - let \"TopLevel" = (props: props<_>) => make(props) - \"TopLevel" +module V4A = { + type props<'a, 'b> = {key?: string, a: 'a, b: 'b} + + @react.component + let make = ({a, b, _}: props<'a, 'b>) => { + Js.log("This function should be named 'TopLevel.react'") + ReactDOM.jsx("div", {key: ?None}) + } + let make = { + let \"TopLevel$V4A" = (props: props<_>) => make(props) + \"TopLevel$V4A" + } } diff --git a/tests/ppx/react/expected/typeConstraint.res.txt b/tests/ppx/react/expected/typeConstraint.res.txt index 0a7a1d72..87968f21 100644 --- a/tests/ppx/react/expected/typeConstraint.res.txt +++ b/tests/ppx/react/expected/typeConstraint.res.txt @@ -1,32 +1,42 @@ @@jsxConfig({version: 3}) -@obj external makeProps: (~a: 'a, ~b: 'b, ~key: string=?, unit) => {"a": 'a, "b": 'b} = "" - -@react.component -let make: - type a. (~a: a, ~b: a, a) => React.element = - (~a, ~b, _) => ReactDOMRe.createDOMElementVariadic("div", []) -let make = { - let \"TypeConstraint" = (\"Props": {"a": 'a, "b": 'b}) => make(~b=\"Props"["b"], ~a=\"Props"["a"]) - \"TypeConstraint" + +module V3 = { + @obj external makeProps: (~a: 'a, ~b: 'b, ~key: string=?, unit) => {"a": 'a, "b": 'b} = "" + + @react.component + let make: + type a. (~a: a, ~b: a, a) => React.element = + (~a, ~b, _) => ReactDOMRe.createDOMElementVariadic("div", []) + let make = { + let \"TypeConstraint$V3" = (\"Props": {"a": 'a, "b": 'b}) => + make(~b=\"Props"["b"], ~a=\"Props"["a"]) + \"TypeConstraint$V3" + } } @@jsxConfig({version: 4, mode: "classic"}) -type props<'a, 'b> = {key?: string, a: 'a, b: 'b} - -@react.component -let make = ({a, b, _}: props<'\"type-a", '\"type-a">) => - ReactDOMRe.createDOMElementVariadic("div", []) -let make = { - let \"TypeConstraint" = (props: props<_>) => make(props) - \"TypeConstraint" + +module V4C = { + type props<'a, 'b> = {key?: string, a: 'a, b: 'b} + + @react.component + let make = ({a, b, _}: props<'\"type-a", '\"type-a">) => + ReactDOMRe.createDOMElementVariadic("div", []) + let make = { + let \"TypeConstraint$V4C" = (props: props<_>) => make(props) + \"TypeConstraint$V4C" + } } @@jsxConfig({version: 4, mode: "automatic"}) -type props<'a, 'b> = {key?: string, a: 'a, b: 'b} -@react.component -let make = ({a, b, _}: props<'\"type-a", '\"type-a">) => ReactDOM.jsx("div", {key: ?None}) -let make = { - let \"TypeConstraint" = (props: props<_>) => make(props) - \"TypeConstraint" +module V4A = { + type props<'a, 'b> = {key?: string, a: 'a, b: 'b} + + @react.component + let make = ({a, b, _}: props<'\"type-a", '\"type-a">) => ReactDOM.jsx("div", {key: ?None}) + let make = { + let \"TypeConstraint$V4A" = (props: props<_>) => make(props) + \"TypeConstraint$V4A" + } } diff --git a/tests/ppx/react/fileLevelConfig.res b/tests/ppx/react/fileLevelConfig.res index a8eafd95..b4e2459c 100644 --- a/tests/ppx/react/fileLevelConfig.res +++ b/tests/ppx/react/fileLevelConfig.res @@ -1,20 +1,26 @@ @@jsxConfig({version: 3}) -@react.component -let make = (~msg) => { -
{msg->React.string}
+module V3 = { + @react.component + let make = (~msg) => { +
{msg->React.string}
+ } } @@jsxConfig({version: 4, mode: "classic"}) -@react.component -let make = (~msg) => { -
{msg->React.string}
+module V4C = { + @react.component + let make = (~msg) => { +
{msg->React.string}
+ } } @@jsxConfig({version: 4, mode: "automatic"}) -@react.component -let make = (~msg) => { -
{msg->React.string}
+module V4A = { + @react.component + let make = (~msg) => { +
{msg->React.string}
+ } } diff --git a/tests/ppx/react/forwardRef.res b/tests/ppx/react/forwardRef.res index 8010499b..8481422a 100644 --- a/tests/ppx/react/forwardRef.res +++ b/tests/ppx/react/forwardRef.res @@ -1,68 +1,80 @@ @@jsxConfig({version: 3}) -module FancyInput = { +module V3 = { + module FancyInput = { + @react.component + let make = React.forwardRef((~className=?, ~children, ref) => +
+ Belt.Option.map(ReactDOM.Ref.domRef)} + /> + children +
+ ) + } + @react.component - let make = React.forwardRef((~className=?, ~children, ref) => + let make = () => { + let input = React.useRef(Js.Nullable.null) +
- Belt.Option.map(ReactDOM.Ref.domRef)} - /> - children + {React.string("Click to focus")}
- ) -} - -@react.component -let make = () => { - let input = React.useRef(Js.Nullable.null) - -
{React.string("Click to focus")}
+ } } @@jsxConfig({version: 4, mode: "classic"}) -module FancyInput = { +module V4C = { + module FancyInput = { + @react.component + let make = React.forwardRef((~className=?, ~children, ref) => +
+ Belt.Option.map(ReactDOM.Ref.domRef)} + /> + children +
+ ) + } + @react.component - let make = React.forwardRef((~className=?, ~children, ref) => + let make = () => { + let input = React.useRef(Js.Nullable.null) +
- Belt.Option.map(ReactDOM.Ref.domRef)} - /> - children + {React.string("Click to focus")}
- ) -} - -@react.component -let make = () => { - let input = React.useRef(Js.Nullable.null) - -
{React.string("Click to focus")}
+ } } @@jsxConfig({version: 4, mode: "automatic"}) -module FancyInput = { +module V4A = { + module FancyInput = { + @react.component + let make = React.forwardRef((~className=?, ~children, ref) => +
+ Belt.Option.map(ReactDOM.Ref.domRef)} + /> + children +
+ ) + } + @react.component - let make = React.forwardRef((~className=?, ~children, ref) => + let make = () => { + let input = React.useRef(Js.Nullable.null) +
- Belt.Option.map(ReactDOM.Ref.domRef)} - /> - children + {React.string("Click to focus")}
- ) -} - -@react.component -let make = () => { - let input = React.useRef(Js.Nullable.null) - -
{React.string("Click to focus")}
+ } } diff --git a/tests/ppx/react/innerModule.res b/tests/ppx/react/innerModule.res index 796690f6..742f8a65 100644 --- a/tests/ppx/react/innerModule.res +++ b/tests/ppx/react/innerModule.res @@ -11,33 +11,3 @@ module Bar = {
} } - -@@jsxConfig({version: 4, mode: "classic"}) - -module Bar = { - @react.component - let make = (~a, ~b, _) => { - Js.log("This function should be named `InnerModule.react$Bar`") -
- } - @react.component - let component = (~a, ~b, _) => { - Js.log("This function should be named `InnerModule.react$Bar$component`") -
- } -} - -@@jsxConfig({version: 4, mode: "automatic"}) - -module Bar = { - @react.component - let make = (~a, ~b, _) => { - Js.log("This function should be named `InnerModule.react$Bar`") -
- } - @react.component - let component = (~a, ~b, _) => { - Js.log("This function should be named `InnerModule.react$Bar$component`") -
- } -} diff --git a/tests/ppx/react/newtype.res b/tests/ppx/react/newtype.res index e25e9b6b..27a834eb 100644 --- a/tests/ppx/react/newtype.res +++ b/tests/ppx/react/newtype.res @@ -1,14 +1,20 @@ @@jsxConfig({version: 3}) -@react.component -let make = (type a, ~a: a, ~b: array>, ~c: 'a, _) =>
+module V3 = { + @react.component + let make = (type a, ~a: a, ~b: array>, ~c: 'a, _) =>
+} @@jsxConfig({version: 4, mode: "classic"}) -@react.component -let make = (type a, ~a: a, ~b: array>, ~c: 'a, _) =>
+module V4C = { + @react.component + let make = (type a, ~a: a, ~b: array>, ~c: 'a, _) =>
+} @@jsxConfig({version: 4, mode: "automatic"}) -@react.component -let make = (type a, ~a: a, ~b: array>, ~c: 'a, _) =>
+module V4A = { + @react.component + let make = (type a, ~a: a, ~b: array>, ~c: 'a, _) =>
+} diff --git a/tests/ppx/react/topLevel.res b/tests/ppx/react/topLevel.res index 8ea77292..6d7a7eb9 100644 --- a/tests/ppx/react/topLevel.res +++ b/tests/ppx/react/topLevel.res @@ -1,23 +1,29 @@ @@jsxConfig({version: 3}) -@react.component -let make = (~a, ~b, _) => { - Js.log("This function should be named 'TopLevel.react'") -
+module V3 = { + @react.component + let make = (~a, ~b, _) => { + Js.log("This function should be named 'TopLevel.react'") +
+ } } @@jsxConfig({version: 4, mode: "classic"}) -@react.component -let make = (~a, ~b, _) => { - Js.log("This function should be named 'TopLevel.react'") -
+module V4C = { + @react.component + let make = (~a, ~b, _) => { + Js.log("This function should be named 'TopLevel.react'") +
+ } } @@jsxConfig({version: 4, mode: "automatic"}) -@react.component -let make = (~a, ~b, _) => { - Js.log("This function should be named 'TopLevel.react'") -
+module V4A = { + @react.component + let make = (~a, ~b, _) => { + Js.log("This function should be named 'TopLevel.react'") +
+ } } diff --git a/tests/ppx/react/typeConstraint.res b/tests/ppx/react/typeConstraint.res index 28b9c218..9e1fdc14 100644 --- a/tests/ppx/react/typeConstraint.res +++ b/tests/ppx/react/typeConstraint.res @@ -1,20 +1,26 @@ @@jsxConfig({version: 3}) -@react.component -let make: - type a. (~a: a, ~b: a, a) => React.element = - (~a, ~b, _) =>
+module V3 = { + @react.component + let make: + type a. (~a: a, ~b: a, a) => React.element = + (~a, ~b, _) =>
+} @@jsxConfig({version: 4, mode: "classic"}) -@react.component -let make: - type a. (~a: a, ~b: a, a) => React.element = - (~a, ~b, _) =>
+module V4C = { + @react.component + let make: + type a. (~a: a, ~b: a, a) => React.element = + (~a, ~b, _) =>
+} @@jsxConfig({version: 4, mode: "automatic"}) -@react.component -let make: - type a. (~a: a, ~b: a, a) => React.element = - (~a, ~b, _) =>
+module V4A = { + @react.component + let make: + type a. (~a: a, ~b: a, a) => React.element = + (~a, ~b, _) =>
+} From f64e5c57ca8ccb12d0adf16608d9b7dcf6f07c5b Mon Sep 17 00:00:00 2001 From: Woonki Moon Date: Thu, 21 Jul 2022 00:42:45 +0900 Subject: [PATCH 87/94] fix raise error in transform sig --- cli/reactjs_jsx_ppx.ml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index 1ff539b7..9d6e51ba 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -2463,7 +2463,9 @@ module V4 = struct | [] -> [item] | [_] -> (* If there is another @react.component, throw error *) - if !hasReactComponent then raise (Invalid_argument "2") + if !hasReactComponent then + Location.raise_errorf ~loc:psig_loc + "Each module should have one react component at most" else hasReactComponent := true; let hasForwardRef = ref false in let rec getPropTypes types ({ptyp_loc; ptyp_desc} as fullType) = From d837748504e5b72a302a58852f9e594ef271e4bf Mon Sep 17 00:00:00 2001 From: Woonki Moon Date: Thu, 21 Jul 2022 01:58:34 +0900 Subject: [PATCH 88/94] add hasReactComponent into jsx config --- cli/reactjs_jsx_ppx.ml | 118 ++++++++++++++++++++++------------------- 1 file changed, 62 insertions(+), 56 deletions(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index 9d6e51ba..6fc6a38c 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -9,6 +9,7 @@ type jsxConfig = { mutable module_: string; mutable mode: string; mutable nestedModules: string list; + mutable hasReactComponent: bool; } let getPayloadFields payload = @@ -2004,7 +2005,7 @@ module V4 = struct | name when isOptional name -> (true, getLabel name, [], type_) :: types | _ -> types - let transformStructureItem ~hasReactComponent ~config mapper item = + let transformStructureItem ~config mapper item = match item with (* external *) | { @@ -2016,52 +2017,53 @@ module V4 = struct | [] -> [item] | [_] -> (* If there is another @react.component, throw error *) - if !hasReactComponent then + if config.hasReactComponent then Location.raise_errorf ~loc:pstr_loc "Each module should have one react component at most" - else hasReactComponent := true; - let rec getPropTypes types ({ptyp_loc; ptyp_desc} as fullType) = - match ptyp_desc with - | Ptyp_arrow (name, type_, ({ptyp_desc = Ptyp_arrow _} as rest)) - when isLabelled name || isOptional name -> - getPropTypes ((name, ptyp_loc, type_) :: types) rest - | Ptyp_arrow (Nolabel, _type, rest) -> getPropTypes types rest - | Ptyp_arrow (name, type_, returnValue) - when isLabelled name || isOptional name -> - (returnValue, (name, returnValue.ptyp_loc, type_) :: types) - | _ -> (fullType, types) - in - let innerType, propTypes = getPropTypes [] pval_type in - let namedTypeList = List.fold_left argToConcreteType [] propTypes in - let retPropsType = - Typ.constr ~loc:pstr_loc - (Location.mkloc (Lident "props") pstr_loc) - (makePropsTypeParams namedTypeList) - in - (* type props<'id, 'name> = { @optional key: string, @optional id: 'id, ... } *) - let propsRecordType = - makePropsRecordType "props" Location.none - ((true, "key", [], keyType pstr_loc) :: namedTypeList) - in - (* can't be an arrow because it will defensively uncurry *) - let newExternalType = - Ptyp_constr - ( {loc = pstr_loc; txt = Ldot (Lident "React", "componentLike")}, - [retPropsType; innerType] ) - in - let newStructure = - { - pstr with - pstr_desc = - Pstr_primitive - { - value_description with - pval_type = {pval_type with ptyp_desc = newExternalType}; - pval_attributes = List.filter otherAttrsPure pval_attributes; - }; - } - in - [propsRecordType; newStructure] + else ( + config.hasReactComponent <- true; + let rec getPropTypes types ({ptyp_loc; ptyp_desc} as fullType) = + match ptyp_desc with + | Ptyp_arrow (name, type_, ({ptyp_desc = Ptyp_arrow _} as rest)) + when isLabelled name || isOptional name -> + getPropTypes ((name, ptyp_loc, type_) :: types) rest + | Ptyp_arrow (Nolabel, _type, rest) -> getPropTypes types rest + | Ptyp_arrow (name, type_, returnValue) + when isLabelled name || isOptional name -> + (returnValue, (name, returnValue.ptyp_loc, type_) :: types) + | _ -> (fullType, types) + in + let innerType, propTypes = getPropTypes [] pval_type in + let namedTypeList = List.fold_left argToConcreteType [] propTypes in + let retPropsType = + Typ.constr ~loc:pstr_loc + (Location.mkloc (Lident "props") pstr_loc) + (makePropsTypeParams namedTypeList) + in + (* type props<'id, 'name> = { @optional key: string, @optional id: 'id, ... } *) + let propsRecordType = + makePropsRecordType "props" Location.none + ((true, "key", [], keyType pstr_loc) :: namedTypeList) + in + (* can't be an arrow because it will defensively uncurry *) + let newExternalType = + Ptyp_constr + ( {loc = pstr_loc; txt = Ldot (Lident "React", "componentLike")}, + [retPropsType; innerType] ) + in + let newStructure = + { + pstr with + pstr_desc = + Pstr_primitive + { + value_description with + pval_type = {pval_type with ptyp_desc = newExternalType}; + pval_attributes = List.filter otherAttrsPure pval_attributes; + }; + } + in + [propsRecordType; newStructure]) | _ -> raise (Invalid_argument @@ -2073,11 +2075,11 @@ module V4 = struct let emptyLoc = Location.in_file fileName in let mapBinding binding = if hasAttrOnBinding binding then - if !hasReactComponent then + if config.hasReactComponent then Location.raise_errorf ~loc:pstr_loc "Each module should have one react component at most" else ( - hasReactComponent := true; + config.hasReactComponent <- true; let bindingLoc = binding.pvb_loc in let bindingPatLoc = binding.pvb_pat.ppat_loc in let binding = @@ -2453,7 +2455,7 @@ module V4 = struct | _ -> [item] [@@raises Invalid_argument] - let transformSignatureItem ~hasReactComponent _mapper item = + let transformSignatureItem ~config _mapper item = match item with | { psig_loc; @@ -2463,10 +2465,10 @@ module V4 = struct | [] -> [item] | [_] -> (* If there is another @react.component, throw error *) - if !hasReactComponent then + if config.hasReactComponent then Location.raise_errorf ~loc:psig_loc "Each module should have one react component at most" - else hasReactComponent := true; + else config.hasReactComponent <- true; let hasForwardRef = ref false in let rec getPropTypes types ({ptyp_loc; ptyp_desc} as fullType) = match ptyp_desc with @@ -2649,9 +2651,14 @@ module V4 = struct [@@raises Invalid_argument] let module_binding ~config mapper module_binding = + let hadReactComponent = config.hasReactComponent in + (* set default false in each module *) + config.hasReactComponent <- false; config.nestedModules <- module_binding.pmb_name.txt :: config.nestedModules; let mapped = default_mapper.module_binding mapper module_binding in config.nestedModules <- List.tl config.nestedModules; + (* restore it *) + config.hasReactComponent <- hadReactComponent; mapped [@@raises Failure] @@ -2660,6 +2667,7 @@ module V4 = struct let expr = expr ~config in let module_binding = module_binding ~config in let transformStructureItem = transformStructureItem ~config in + let transformSignatureItem = transformSignatureItem ~config in (expr, module_binding, transformSignatureItem, transformStructureItem) [@@raises Invalid_argument, Failure] end @@ -2699,7 +2707,6 @@ let getMapper ~config = in let signature mapper items = let oldConfig = saveConfig () in - let hasReactComponent = ref false in let result = List.map (fun item -> @@ -2708,8 +2715,7 @@ let getMapper ~config = | _ -> ()); let item = default_mapper.signature_item mapper item in if config.version = 3 then transformSignatureItem3 mapper item - else if config.version = 4 then - transformSignatureItem4 ~hasReactComponent mapper item + else if config.version = 4 then transformSignatureItem4 mapper item else [item]) items |> List.flatten @@ -2720,7 +2726,6 @@ let getMapper ~config = in let structure mapper items = let oldConfig = saveConfig () in - let hasReactComponent = ref false in let result = List.map (fun item -> @@ -2729,8 +2734,7 @@ let getMapper ~config = | _ -> ()); let item = default_mapper.structure_item mapper item in if config.version = 3 then transformStructureItem3 mapper item - else if config.version = 4 then - transformStructureItem4 ~hasReactComponent mapper item + else if config.version = 4 then transformStructureItem4 mapper item else [item]) items |> List.flatten @@ -2750,6 +2754,7 @@ let rewrite_implementation ~jsxVersion ~jsxModule ~jsxMode module_ = jsxModule; mode = jsxMode; nestedModules = []; + hasReactComponent = false; } in let mapper = getMapper ~config in @@ -2764,6 +2769,7 @@ let rewrite_signature ~jsxVersion ~jsxModule ~jsxMode module_ = jsxModule; mode = jsxMode; nestedModules = []; + hasReactComponent = false; } in let mapper = getMapper ~config in From b78dde4c6b5c394c941bbb2c34f98d8119fd883f Mon Sep 17 00:00:00 2001 From: Woonki Moon Date: Thu, 21 Jul 2022 02:13:23 +0900 Subject: [PATCH 89/94] helper fn to raise error for multiple react comp --- cli/reactjs_jsx_ppx.ml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index 6fc6a38c..03cc8e3c 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -1510,6 +1510,10 @@ module V4 = struct let fullModuleName = String.concat "$" fullModuleName in fullModuleName + let raiseErrorMultipleReactComponent ~loc = + Location.raise_errorf ~loc + "Each module should have one react component at most" + (* AST node builders These functions help us build AST nodes that are needed when transforming a [@react.component] into a @@ -2018,8 +2022,7 @@ module V4 = struct | [_] -> (* If there is another @react.component, throw error *) if config.hasReactComponent then - Location.raise_errorf ~loc:pstr_loc - "Each module should have one react component at most" + raiseErrorMultipleReactComponent ~loc:pstr_loc else ( config.hasReactComponent <- true; let rec getPropTypes types ({ptyp_loc; ptyp_desc} as fullType) = @@ -2076,8 +2079,7 @@ module V4 = struct let mapBinding binding = if hasAttrOnBinding binding then if config.hasReactComponent then - Location.raise_errorf ~loc:pstr_loc - "Each module should have one react component at most" + raiseErrorMultipleReactComponent ~loc:pstr_loc else ( config.hasReactComponent <- true; let bindingLoc = binding.pvb_loc in @@ -2466,8 +2468,7 @@ module V4 = struct | [_] -> (* If there is another @react.component, throw error *) if config.hasReactComponent then - Location.raise_errorf ~loc:psig_loc - "Each module should have one react component at most" + raiseErrorMultipleReactComponent ~loc:psig_loc else config.hasReactComponent <- true; let hasForwardRef = ref false in let rec getPropTypes types ({ptyp_loc; ptyp_desc} as fullType) = From 5378e204ba8101f9d928c2d9c001c71108774e12 Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Wed, 20 Jul 2022 19:19:37 +0200 Subject: [PATCH 90/94] hasReactComponent: support interface files --- cli/reactjs_jsx_ppx.ml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index 03cc8e3c..b5299897 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -2652,14 +2652,9 @@ module V4 = struct [@@raises Invalid_argument] let module_binding ~config mapper module_binding = - let hadReactComponent = config.hasReactComponent in - (* set default false in each module *) - config.hasReactComponent <- false; config.nestedModules <- module_binding.pmb_name.txt :: config.nestedModules; let mapped = default_mapper.module_binding mapper module_binding in config.nestedModules <- List.tl config.nestedModules; - (* restore it *) - config.hasReactComponent <- hadReactComponent; mapped [@@raises Failure] @@ -2699,6 +2694,7 @@ let getMapper ~config = version = config.version; module_ = config.module_; mode = config.mode; + hasReactComponent = config.hasReactComponent; } in let restoreConfig oldConfig = @@ -2708,6 +2704,7 @@ let getMapper ~config = in let signature mapper items = let oldConfig = saveConfig () in + config.hasReactComponent <- false; let result = List.map (fun item -> @@ -2727,6 +2724,7 @@ let getMapper ~config = in let structure mapper items = let oldConfig = saveConfig () in + config.hasReactComponent <- false; let result = List.map (fun item -> From e65f99179b5fd25af37b8d67a0cf4a31f5d5cc4c Mon Sep 17 00:00:00 2001 From: Woonki Moon Date: Thu, 21 Jul 2022 02:28:19 +0900 Subject: [PATCH 91/94] restore config.hasReactComponent --- cli/reactjs_jsx_ppx.ml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index b5299897..a70ee7f9 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -2700,7 +2700,8 @@ let getMapper ~config = let restoreConfig oldConfig = config.version <- oldConfig.version; config.module_ <- oldConfig.module_; - config.mode <- oldConfig.mode + config.mode <- oldConfig.mode; + config.hasReactComponent <- oldConfig.hasReactComponent in let signature mapper items = let oldConfig = saveConfig () in From ba34580a0f15d9c2baca26f2d5b8a2b47e4dd14e Mon Sep 17 00:00:00 2001 From: Woonki Moon Date: Thu, 21 Jul 2022 08:52:37 +0900 Subject: [PATCH 92/94] helper fn to raise error with loc --- cli/reactjs_jsx_ppx.ml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index a70ee7f9..da287aea 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -1510,9 +1510,10 @@ module V4 = struct let fullModuleName = String.concat "$" fullModuleName in fullModuleName + let raiseError ~loc msg = Location.raise_errorf ~loc msg + let raiseErrorMultipleReactComponent ~loc = - Location.raise_errorf ~loc - "Each module should have one react component at most" + raiseError ~loc "Each module should have one react component at most" (* AST node builders From ffdb1d5d04bf1ee8eb546930b90d7e0ed4681d4d Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Thu, 21 Jul 2022 02:55:07 +0200 Subject: [PATCH 93/94] Tweak message for one component per module. --- cli/reactjs_jsx_ppx.ml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index da287aea..8dcd0ca0 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -1513,13 +1513,14 @@ module V4 = struct let raiseError ~loc msg = Location.raise_errorf ~loc msg let raiseErrorMultipleReactComponent ~loc = - raiseError ~loc "Each module should have one react component at most" - + raiseError ~loc + "Only one component definition is allowed for each module. Move to a \ + submodule or other file if necessary." (* AST node builders These functions help us build AST nodes that are needed when transforming a [@react.component] into a constructor and a props external -*) + *) (* make record from props and spread props if exists *) let recordFromProps ?(removeKey = false) callArguments = From fcab9e200762ad89ca7454e10cf2eb4a204db36d Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Thu, 21 Jul 2022 02:59:22 +0200 Subject: [PATCH 94/94] Update changelog before initial merge. --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b06cb75..448ff32c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,22 @@ ## Master +> **Tags:** +> +> - :boom: [Breaking Change] +> - :eyeglasses: [Spec Compliance] +> - :rocket: [New Feature] +> - :bug: [Bug Fix] +> - :memo: [Documentation] +> - :house: [Internal] +> - :nail_care: [Polish] + #### :rocket: New Feature - Add surface syntax for `async`/`await` https://github.com/rescript-lang/syntax/pull/600 +- Initial support for JSX V4, still work in progress. + - :boom: when V4 is activated, at most one component is allowed for each module. + ## ReScript 10.0 - Fix printing for inline nullary functor types [#477](https://github.com/rescript-lang/syntax/pull/477)