diff --git a/cli/JSXV4.md b/cli/JSXV4.md index ab29a282..c8f3f8f2 100644 --- a/cli/JSXV4.md +++ b/cli/JSXV4.md @@ -40,7 +40,7 @@ It produces a type definition and a new function. // is converted to -type props<'x, 'y, 'z> = {x: 'x, @optional y: 'y, @optional z: 'z, @optional key: string} +type props<'x, 'y, 'z> = {x: 'x, y?: 'y, z?: 'z} ({x, y, z}: props<_>) => { let y = switch props.y { @@ -60,11 +60,11 @@ React.createElement(Comp.make, {x}) // is converted to -React.createElement(Comp.make, {x, y:7, @optional z}) +React.createElement(Comp.make, {x, y:7, ?z}) // is converted to -React.createElement(Comp.make, {x, key: "7"}) +React.createElement(Comp.make, Jsx.addKeyProp({x}, "7")) ``` **New "jsx" transform** @@ -76,16 +76,16 @@ The "jsx" transform affects component application but not the definition. ```rescript // is converted to -Js.React.jsx(Comp.make, {x}) +React.jsx(Comp.make, {x}) ``` ```rescript
// is converted to -Js.React.jsxDom("div", { name: "div" }) +ReactDOM.jsx("div", { name: "div" }) ``` -The props type of dom elements, e.g. `div`, is inferred to `Js.React.domProps`. +The props type of dom elements, e.g. `div`, is inferred to `ReactDOM.domProps`. ```rescript type domProps = { @@ -102,7 +102,7 @@ type domProps = { // is converted to -type props<'x, 'y, 'z> = {x: 'x, @optional y: 'y, @optional z: 'z} +type props<'x, 'y, 'z> = {x: 'x, y?: 'y, z?: 'z} props => React.element ``` @@ -121,7 +121,11 @@ function has the name of the enclosing module/file. // is converted to +// v4 ReactDOMRe.createElement(ReasonReact.fragment, [comp1, comp2, comp3]) + +// v4 @ new jsx transform +React.jsxs(React.jsxFragment, {children: [comp1, comp2, comp3]}) ``` **File-level config** diff --git a/cli/reactjs_jsx_ppx.ml b/cli/reactjs_jsx_ppx.ml index 5025e97c..5523a31b 100644 --- a/cli/reactjs_jsx_ppx.ml +++ b/cli/reactjs_jsx_ppx.ml @@ -1607,11 +1607,11 @@ module V4 = struct ~kind:(Ptype_record labelDeclList); ] - (* type props<'id, 'name, ...> = { @optional key: string, @optional id: 'id, ... } *) + (* type props<'x, 'y, ...> = { x: 'x, y?: 'y, ... } *) let makePropsRecordType propsName loc namedTypeList = Str.type_ Nonrecursive (makeTypeDecls propsName loc namedTypeList) - (* type props<'id, 'name, ...> = { @optional key: string, @optional id: 'id, ... } *) + (* type props<'x, 'y, ...> = { x: 'x, y?: 'y, ... } *) let makePropsRecordTypeSig propsName loc namedTypeList = Sig.type_ Nonrecursive (makeTypeDecls propsName loc namedTypeList) @@ -1660,11 +1660,11 @@ module V4 = struct first = capped [@@raises Invalid_argument] in - let ident = + let ident ~suffix = match modulePath with - | Lident _ -> Ldot (modulePath, "make") + | Lident _ -> Ldot (modulePath, suffix) | Ldot (_modulePath, value) as fullPath when isCap value -> - Ldot (fullPath, "make") + Ldot (fullPath, suffix) | modulePath -> modulePath in let isEmptyRecord {pexp_desc} = @@ -1675,16 +1675,16 @@ module V4 = struct (* handle key, ref, children *) (* React.createElement(Component.make, props, ...children) *) + let record = recordFromProps ~loc:jsxExprLoc ~removeKey:true args in + let props = + if isEmptyRecord record then emptyRecord ~loc:jsxExprLoc else record + in + let keyProp = + args |> List.filter (fun (arg_label, _) -> "key" = getLabel arg_label) + in match config.mode with (* The new jsx transform *) | "automatic" -> - let record = recordFromProps ~loc:jsxExprLoc ~removeKey:true args in - let props = - if isEmptyRecord record then emptyRecord ~loc:jsxExprLoc 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) :: _ -> @@ -1704,29 +1704,50 @@ module V4 = struct in Exp.apply ~attrs jsxExpr ([ - (nolabel, Exp.ident {txt = ident; loc = callExprLoc}); + (nolabel, Exp.ident {txt = ident ~suffix:"make"; loc = callExprLoc}); (nolabel, props); ] @ key) | _ -> ( - let record = recordFromProps ~loc:jsxExprLoc 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 emptyRecord ~loc:jsxExprLoc else record + let keyAddedProps ~keyExpr = + let propsType = + Typ.constr (Location.mknoloc @@ ident ~suffix:"props") [Typ.any ()] + in + Exp.apply + (Exp.ident + {loc = Location.none; txt = Ldot (Lident "Jsx", "addKeyProp")}) + [(nolabel, Exp.constraint_ props propsType); (nolabel, keyExpr)] in - match !childrenArg with - | None -> + match (!childrenArg, keyProp) with + | None, (_, keyExpr) :: _ -> + Exp.apply ~attrs + (Exp.ident + {loc = Location.none; txt = Ldot (Lident "React", "createElement")}) + [ + (nolabel, Exp.ident {txt = ident ~suffix:"make"; loc = callExprLoc}); + (nolabel, keyAddedProps ~keyExpr); + ] + | None, [] -> Exp.apply ~attrs (Exp.ident {loc = Location.none; txt = Ldot (Lident "React", "createElement")}) [ - (nolabel, Exp.ident {txt = ident; loc = callExprLoc}); + (nolabel, Exp.ident {txt = ident ~suffix:"make"; loc = callExprLoc}); (nolabel, props); ] - | Some children -> + | Some children, (_, keyExpr) :: _ -> + Exp.apply ~attrs + (Exp.ident + { + loc = Location.none; + txt = Ldot (Lident "React", "createElementVariadic"); + }) + [ + (nolabel, Exp.ident {txt = ident ~suffix:"make"; loc = callExprLoc}); + (nolabel, keyAddedProps ~keyExpr); + (nolabel, children); + ] + | Some children, [] -> Exp.apply ~attrs (Exp.ident { @@ -1734,7 +1755,7 @@ module V4 = struct txt = Ldot (Lident "React", "createElementVariadic"); }) [ - (nolabel, Exp.ident {txt = ident; loc = callExprLoc}); + (nolabel, Exp.ident {txt = ident ~suffix:"make"; loc = callExprLoc}); (nolabel, props); (nolabel, children); ]) @@ -2039,10 +2060,9 @@ module V4 = struct (Location.mkloc (Lident "props") pstr_loc) (makePropsTypeParams namedTypeList) in - (* type props<'id, 'name> = { @optional key: string, @optional id: 'id, ... } *) + (* type props<'x, 'y> = { x: 'x, y?: 'y, ... } *) let propsRecordType = - makePropsRecordType "props" Location.none - ((true, "key", [], keyType pstr_loc) :: namedTypeList) + makePropsRecordType "props" Location.none namedTypeList in (* can't be an arrow because it will defensively uncurry *) let newExternalType = @@ -2274,10 +2294,9 @@ module V4 = struct (* type props = { ... } *) let propsRecordType = makePropsRecordType "props" emptyLoc - ([(true, "key", [], keyType emptyLoc)] - @ (if hasForwardRef then - [(true, "ref", [], refType Location.none)] - else []) + ((if hasForwardRef then + [(true, "ref", [], refType Location.none)] + else []) @ namedTypeList) in let innerExpression = @@ -2505,10 +2524,9 @@ module V4 = struct in let propsRecordType = makePropsRecordTypeSig "props" Location.none - ([(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 []) + ((* 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 *) diff --git a/tests/ppx/react/expected/commentAtTop.res.txt b/tests/ppx/react/expected/commentAtTop.res.txt index 290974f3..a8b54927 100644 --- a/tests/ppx/react/expected/commentAtTop.res.txt +++ b/tests/ppx/react/expected/commentAtTop.res.txt @@ -1,4 +1,4 @@ -type props<'msg> = {key?: string, msg: 'msg} // test React JSX file +type props<'msg> = {msg: 'msg} // test React JSX file @react.component let make = ({msg, _}: props<'msg>) => { diff --git a/tests/ppx/react/expected/externalWithCustomName.res.txt b/tests/ppx/react/expected/externalWithCustomName.res.txt index cdcf46cc..335c670f 100644 --- a/tests/ppx/react/expected/externalWithCustomName.res.txt +++ b/tests/ppx/react/expected/externalWithCustomName.res.txt @@ -13,7 +13,7 @@ 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} + type props<'a, 'b> = {a: 'a, b: 'b} @module("Foo") external component: React.componentLike, React.element> = "component" @@ -24,7 +24,7 @@ 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} + type props<'a, 'b> = {a: 'a, b: 'b} @module("Foo") external component: React.componentLike, React.element> = "component" diff --git a/tests/ppx/react/expected/fileLevelConfig.res.txt b/tests/ppx/react/expected/fileLevelConfig.res.txt index ecfa665e..6e41d271 100644 --- a/tests/ppx/react/expected/fileLevelConfig.res.txt +++ b/tests/ppx/react/expected/fileLevelConfig.res.txt @@ -17,7 +17,7 @@ module V3 = { @@jsxConfig({version: 4, mode: "classic"}) module V4C = { - type props<'msg> = {key?: string, msg: 'msg} + type props<'msg> = {msg: 'msg} @react.component let make = ({msg, _}: props<'msg>) => { @@ -33,7 +33,7 @@ module V4C = { @@jsxConfig({version: 4, mode: "automatic"}) module V4A = { - type props<'msg> = {key?: string, msg: 'msg} + type props<'msg> = {msg: 'msg} @react.component let make = ({msg, _}: props<'msg>) => { diff --git a/tests/ppx/react/expected/forwardRef.res.txt b/tests/ppx/react/expected/forwardRef.res.txt index 13d0a16d..4b122588 100644 --- a/tests/ppx/react/expected/forwardRef.res.txt +++ b/tests/ppx/react/expected/forwardRef.res.txt @@ -67,7 +67,6 @@ module V3 = { module V4C = { module FancyInput = { type props<'className, 'children> = { - key?: string, ref?: ReactDOM.Ref.currentDomRef, className?: 'className, children: 'children, @@ -97,7 +96,7 @@ module V4C = { \"ForwardRef$V4C$FancyInput" }) } - type props = {key?: string} + type props = {} @react.component let make = (_: props) => { @@ -125,7 +124,6 @@ module V4C = { module V4A = { module FancyInput = { type props<'className, 'children> = { - key?: string, ref?: ReactDOM.Ref.currentDomRef, className?: 'className, children: 'children, @@ -155,7 +153,7 @@ module V4A = { \"ForwardRef$V4A$FancyInput" }) } - type props = {key?: string} + type props = {} @react.component let make = (_: props) => { diff --git a/tests/ppx/react/expected/interface.resi.txt b/tests/ppx/react/expected/interface.resi.txt new file mode 100644 index 00000000..1358d63e --- /dev/null +++ b/tests/ppx/react/expected/interface.resi.txt @@ -0,0 +1,2 @@ +type props<'x> = {x: 'x} +let make: React.componentLike, React.element> diff --git a/tests/ppx/react/expected/newtype.res.txt b/tests/ppx/react/expected/newtype.res.txt index d3a5d4af..33d46e2a 100644 --- a/tests/ppx/react/expected/newtype.res.txt +++ b/tests/ppx/react/expected/newtype.res.txt @@ -24,7 +24,7 @@ module V3 = { @@jsxConfig({version: 4, mode: "classic"}) module V4C = { - type props<'a, 'b, 'c> = {key?: string, a: 'a, b: 'b, c: 'c} + type props<'a, 'b, 'c> = {a: 'a, b: 'b, c: 'c} @react.component let make = ({a, b, c, _}: props<'\"type-a", array>, 'a>) => @@ -39,7 +39,7 @@ module V4C = { @@jsxConfig({version: 4, mode: "automatic"}) module V4A = { - type props<'a, 'b, 'c> = {key?: string, a: 'a, b: 'b, c: 'c} + type props<'a, 'b, 'c> = {a: 'a, b: 'b, c: 'c} @react.component let make = ({a, b, c, _}: props<'\"type-a", array>, 'a>) => diff --git a/tests/ppx/react/expected/removedKeyProp.res.txt b/tests/ppx/react/expected/removedKeyProp.res.txt new file mode 100644 index 00000000..5cabbdbc --- /dev/null +++ b/tests/ppx/react/expected/removedKeyProp.res.txt @@ -0,0 +1,58 @@ +@@jsxConfig({version: 4, mode: "classic"}) + +module Foo = { + type props<'x, 'y> = {x: 'x, y: 'y} + + @react.component let make = ({x, y, _}: props<'x, 'y>) => React.string(x ++ y) + let make = { + let \"RemovedKeyProp$Foo" = (props: props<_>) => make(props) + + \"RemovedKeyProp$Foo" + } +} + +module HasChildren = { + type props<'children> = {children: 'children} + + @react.component + let make = ({children, _}: props<'children>) => + ReactDOMRe.createElement(ReasonReact.fragment, [children]) + let make = { + let \"RemovedKeyProp$HasChildren" = (props: props<_>) => make(props) + + \"RemovedKeyProp$HasChildren" + } +} +type props = {} + +@react.component +let make = (_: props) => + ReactDOMRe.createElement( + ReasonReact.fragment, + [ + React.createElement(Foo.make, Jsx.addKeyProp(({x: "x", y: "y"}: Foo.props<_>), "k")), + React.createElement(Foo.make, {x: "x", y: "y"}), + React.createElement( + HasChildren.make, + Jsx.addKeyProp( + ( + { + children: React.createElement(Foo.make, {x: "x", y: "y"}), + }: HasChildren.props<_> + ), + "k", + ), + ), + React.createElement( + HasChildren.make, + { + children: React.createElement(Foo.make, {x: "x", y: "y"}), + }, + ), + ], + ) +let make = { + let \"RemovedKeyProp" = props => make(props) + + \"RemovedKeyProp" +} diff --git a/tests/ppx/react/expected/topLevel.res.txt b/tests/ppx/react/expected/topLevel.res.txt index 35c5ebe1..51550dc1 100644 --- a/tests/ppx/react/expected/topLevel.res.txt +++ b/tests/ppx/react/expected/topLevel.res.txt @@ -19,7 +19,7 @@ module V3 = { @@jsxConfig({version: 4, mode: "classic"}) module V4C = { - type props<'a, 'b> = {key?: string, a: 'a, b: 'b} + type props<'a, 'b> = {a: 'a, b: 'b} @react.component let make = ({a, b, _}: props<'a, 'b>) => { @@ -36,7 +36,7 @@ module V4C = { @@jsxConfig({version: 4, mode: "automatic"}) module V4A = { - type props<'a, 'b> = {key?: string, a: 'a, b: 'b} + type props<'a, 'b> = {a: 'a, b: 'b} @react.component let make = ({a, b, _}: props<'a, 'b>) => { diff --git a/tests/ppx/react/expected/typeConstraint.res.txt b/tests/ppx/react/expected/typeConstraint.res.txt index e2aaff86..9bd8ed22 100644 --- a/tests/ppx/react/expected/typeConstraint.res.txt +++ b/tests/ppx/react/expected/typeConstraint.res.txt @@ -17,7 +17,7 @@ module V3 = { @@jsxConfig({version: 4, mode: "classic"}) module V4C = { - type props<'a, 'b> = {key?: string, a: 'a, b: 'b} + type props<'a, 'b> = {a: 'a, b: 'b} @react.component let make = ({a, b, _}: props<'\"type-a", '\"type-a">) => @@ -32,7 +32,7 @@ module V4C = { @@jsxConfig({version: 4, mode: "automatic"}) module V4A = { - type props<'a, 'b> = {key?: string, a: 'a, b: 'b} + type props<'a, 'b> = {a: 'a, b: 'b} @react.component let make = ({a, b, _}: props<'\"type-a", '\"type-a">) => ReactDOM.jsx("div", {}) let make = { diff --git a/tests/ppx/react/expected/v4.res.txt b/tests/ppx/react/expected/v4.res.txt index 7ac379e6..fcdd0132 100644 --- a/tests/ppx/react/expected/v4.res.txt +++ b/tests/ppx/react/expected/v4.res.txt @@ -1,4 +1,4 @@ -type props<'x, 'y> = {key?: string, x: 'x, y: 'y} // Component with type constraint +type props<'x, 'y> = {x: 'x, y: 'y} // Component with type constraint @react.component let make = ({x, y, _}: props) => React.string(x ++ y) let make = { let \"V4" = (props: props<_>) => make(props) @@ -6,7 +6,7 @@ let make = { } module AnotherName = { - type props<'x> = {key?: string, x: 'x} + type props<'x> = {x: 'x} // Component with another name than "make" @react.component let anotherName = ({x, _}: props<'x>) => React.string(x) diff --git a/tests/ppx/react/interface.resi b/tests/ppx/react/interface.resi new file mode 100644 index 00000000..8838796f --- /dev/null +++ b/tests/ppx/react/interface.resi @@ -0,0 +1,2 @@ +@react.component +let make: (~x:string) => React.element \ No newline at end of file diff --git a/tests/ppx/react/removedKeyProp.res b/tests/ppx/react/removedKeyProp.res new file mode 100644 index 00000000..5e60acce --- /dev/null +++ b/tests/ppx/react/removedKeyProp.res @@ -0,0 +1,23 @@ +@@jsxConfig({version: 4, mode: "classic"}) + +module Foo = { + @react.component + let make = (~x, ~y) => React.string(x++y) +} + +module HasChildren = { + @react.component + let make = (~children) => <> children +} + +@react.component +let make = () => <> + + + + + + + + +