From f95c8c619b270284b8cf80031678bad9c72052e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Ch=C3=A1varri?= Date: Sun, 24 Nov 2024 05:48:50 +0100 Subject: [PATCH 1/2] ppx: support "custom children" in uppercase components without having to wrap in array literal (#823) * ppx: fix 822 * +changelog --- CHANGES.md | 5 +++++ ppx/reason_react_ppx.ml | 18 ++++++++++++---- ppx/test/lower.t/run.t | 4 ++-- ppx/test/upper.t/run.t | 5 +---- test/React__test.re | 48 +++++++++++++++++++++++++++++++++++------ 5 files changed, 63 insertions(+), 17 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index d9e8c08eb..7853b8f67 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,8 @@ +# Unreleased + +* BREAKING, ppx: Allow passing an array of custom children to a component + without having to wrap in array literal ([@jchavarri in #748](https://github.com/reasonml/reason-react/pull/823)) + # 0.15.0 * Add `isValidElement` (@r17x in diff --git a/ppx/reason_react_ppx.ml b/ppx/reason_react_ppx.ml index 9646da10c..7dc8e54f1 100644 --- a/ppx/reason_react_ppx.ml +++ b/ppx/reason_react_ppx.ml @@ -458,7 +458,14 @@ let makePropsType ~loc namedTypeList = }; ] -let jsxExprAndChildren ~ident ~loc ~ctxt mapper ~keyProps children = +type component_type = Uppercase | Lowercase + +let jsxExprAndChildren ~component_type ~loc ~ctxt mapper ~keyProps children = + let ident = + match component_type with + | Uppercase -> Lident "React" + | Lowercase -> Lident "ReactDOM" + in let childrenExpr = Option.map (transformChildrenIfListUpper ~loc ~mapper ~ctxt) children in @@ -492,7 +499,10 @@ let jsxExprAndChildren ~ident ~loc ~ctxt mapper ~keyProps children = children *) ( Builder.pexp_ident ~loc { loc; txt = Ldot (ident, "jsxs") }, None, - Some (Binding.React.array ~loc children) ) + Some + (match component_type with + | Uppercase -> children + | Lowercase -> Binding.React.array ~loc children) ) | None, (label, key) :: _ -> ( Builder.pexp_ident ~loc { loc; txt = Ldot (ident, "jsxKeyed") }, Some (label, key), @@ -500,8 +510,8 @@ let jsxExprAndChildren ~ident ~loc ~ctxt mapper ~keyProps children = | None, [] -> (Builder.pexp_ident ~loc { loc; txt = Ldot (ident, "jsx") }, None, None) -let reactJsxExprAndChildren = jsxExprAndChildren ~ident:(Lident "React") -let reactDomJsxExprAndChildren = jsxExprAndChildren ~ident:(Lident "ReactDOM") +let reactJsxExprAndChildren = jsxExprAndChildren ~component_type:Uppercase +let reactDomJsxExprAndChildren = jsxExprAndChildren ~component_type:Lowercase (* Builds an AST node for the entire `external` definition of props *) let makeExternalDecl fnName loc namedArgListWithKeyAndRef namedTypeList = diff --git a/ppx/test/lower.t/run.t b/ppx/test/lower.t/run.t index 0898fb7b4..808a6a190 100644 --- a/ppx/test/lower.t/run.t +++ b/ppx/test/lower.t/run.t @@ -94,7 +94,7 @@ ([@merlin.hide] ReactDOM.domProps)( ~children= examples - |> List.map(e => + |> List.map(e => { let Key = e.path; ReactDOM.jsxKeyed( ~key=Key, @@ -120,7 +120,7 @@ ), (), ); - ) + }) |> React.list, (), ), diff --git a/ppx/test/upper.t/run.t b/ppx/test/upper.t/run.t index a5ed6a84c..e3f259ba1 100644 --- a/ppx/test/upper.t/run.t +++ b/ppx/test/upper.t/run.t @@ -4,10 +4,7 @@ let upper_children_single = foo => React.jsx(Upper.make, Upper.makeProps(~children=foo, ())); let upper_children_multiple = (foo, bar) => - React.jsxs( - Upper.make, - Upper.makeProps(~children=React.array([|foo, bar|]), ()), - ); + React.jsxs(Upper.make, Upper.makeProps(~children=[|foo, bar|], ())); let upper_children = React.jsx( Page.make, diff --git a/test/React__test.re b/test/React__test.re index bfa76985e..4bb4d9158 100644 --- a/test/React__test.re +++ b/test/React__test.re @@ -21,12 +21,17 @@ module DummyComponentThatMapsChildren = { [@react.component] let make = (~children, ()) => {
- {children->React.Children.mapWithIndex((element, index) => { - React.cloneElement( - element, - {"key": string_of_int(index), "data-index": index}, - ) - })} + {children + ->React.array + ->React.Children.mapWithIndex((element, index) => { + React.cloneElement( + element, + { + "key": string_of_int(index), + "data-index": index, + }, + ) + })}
; }; }; @@ -318,7 +323,10 @@ describe("React", () => { act(() => { ReactDOM.Client.render( root, - render({name: "Joe", imageUrl: "https://foo.png"}), + render({ + name: "Joe", + imageUrl: "https://foo.png", + }), ) }); @@ -409,4 +417,30 @@ describe("React", () => { expect(Memo.renders^)->toBe(2); }); + + test("Can define components with custom children", () => { + let container = getContainer(container); + let root = ReactDOM.Client.createRoot(container); + + module Test = { + type t = {name: string}; + [@react.component] + let make = (~children) => { + Array.map(children, c => +
{React.string(c.name)}
+ ) + ->React.array; + }; + }; + + act(() => { + ReactDOM.Client.render( + root, + {Test.name: "foo"} {name: "bar"} , + ) + }); + + expect(container->DOM.findBySelector("div[name='foo']")->Option.isSome) + ->toBe(true); + }); }); From f83f216c4160faa6a4407838a7603962c7826042 Mon Sep 17 00:00:00 2001 From: David Sancho Date: Mon, 25 Nov 2024 19:27:29 +0100 Subject: [PATCH 2/2] Remove raise annotations and fix locations on errors (#863) * Enable ref in ppx * Add jest test for ref * Add test for error on key * Add locations into key * Change message on key * Fix I#843 which improves error message on react.component wrongly placed * Apply same message on similar fn, update snapshot * Add test for record-props and record-props-error * Use caller to genreate uppercase * Fix callee being always make * Remove Invalid_arguments * Add broken make_fn test and keep logic as before * Printinf on snapshot and errors has breakline * Remove test that comes with 19 --- ppx/reason_react_ppx.ml | 93 +++++++------------ ppx/test/component-without-make.t/input.re | 26 ++++++ ppx/test/component-without-make.t/run.t | 53 +++++++++++ ppx/test/component.t/input.re | 7 ++ ppx/test/component.t/run.t | 21 +++++ .../component.re | 3 + .../components-destructured-error.t/run.t | 22 +++++ ppx/test/key-as-prop-error.t/component.re | 2 + ppx/test/key-as-prop-error.t/run.t | 22 +++++ ppx/test/record-props-error.t/input.re | 6 ++ ppx/test/record-props-error.t/run.t | 8 ++ ppx/test/record-props.t/input.re | 6 ++ ppx/test/record-props.t/run.t | 19 ++++ 13 files changed, 231 insertions(+), 57 deletions(-) create mode 100644 ppx/test/component-without-make.t/input.re create mode 100644 ppx/test/component-without-make.t/run.t create mode 100644 ppx/test/components-destructured-error.t/component.re create mode 100644 ppx/test/components-destructured-error.t/run.t create mode 100644 ppx/test/key-as-prop-error.t/component.re create mode 100644 ppx/test/key-as-prop-error.t/run.t create mode 100644 ppx/test/record-props-error.t/input.re create mode 100644 ppx/test/record-props-error.t/run.t create mode 100644 ppx/test/record-props.t/input.re create mode 100644 ppx/test/record-props.t/run.t diff --git a/ppx/reason_react_ppx.ml b/ppx/reason_react_ppx.ml index 7dc8e54f1..8823c52e0 100644 --- a/ppx/reason_react_ppx.ml +++ b/ppx/reason_react_ppx.ml @@ -47,8 +47,8 @@ let labelled str = Labelled str let optional str = Optional str module Binding = struct - (* Binding is the interface that the ppx uses to interact with the bindings. - Here we define the same APIs as the bindings but it generates Parsetree *) + (* Binding is the interface that the ppx relies on to interact with the react bindings. + Here we define the same APIs as the bindings but it generates Parsetree nodes *) module ReactDOM = struct let domProps ~applyLoc ~loc props = Builder.pexp_apply ~loc:applyLoc @@ -58,9 +58,6 @@ module Binding = struct end module React = struct - let null ~loc = - Builder.pexp_ident ~loc { loc; txt = Ldot (Lident "React", "null") } - let array ~loc children = Builder.pexp_apply ~loc (Builder.pexp_ident ~loc @@ -98,18 +95,22 @@ let rec find_opt p = function | [] -> None | x :: l -> if p x then Some x else find_opt p l -let getLabel str = +let getLabelOrEmpty str = match str with Optional str | Labelled str -> str | Nolabel -> "" +let getLabel str = + match str with Optional str | Labelled str -> Some str | Nolabel -> None + let optionIdent = Lident "option" let constantString ~loc str = Builder.pexp_constant ~loc (Pconst_string (str, Location.none, None)) let safeTypeFromValue valueStr = - let valueStr = getLabel valueStr in - match String.sub valueStr 0 1 with "_" -> "T" ^ valueStr | _ -> valueStr -[@@raises Invalid_argument] + match getLabel valueStr with + | Some valueStr when String.sub valueStr 0 1 = "_" -> ("T" ^ valueStr) + | Some valueStr -> valueStr + | None -> "" let keyType loc = Builder.ptyp_constr ~loc { loc; txt = Lident "string" } [] @@ -224,14 +225,12 @@ let otherAttrsPure { attr_name = loc; _ } = loc.txt <> "react.component" 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 *) +(* Finds the name of the variable the binding is assigned to, otherwise raises an error *) let getFnName binding = match binding with | { pvb_pat = { ppat_desc = Ppat_var { txt; _ }; _ }; _ } -> txt - | _ -> - raise (Invalid_argument "react.component calls cannot be destructured.") -[@@raises Invalid_argument] + | { pvb_loc; _} -> + Location.raise_errorf ~loc:pvb_loc "[@react.component] cannot be used with a destructured binding. Please use it on a `let make = ...` binding instead." let makeNewBinding binding expression newName = match binding with @@ -243,22 +242,17 @@ let makeNewBinding binding expression newName = pvb_expr = expression; pvb_attributes = [ merlinFocus ]; } - | _ -> - raise (Invalid_argument "react.component calls cannot be destructured.") -[@@raises Invalid_argument] + | { pvb_loc; _ } -> + Location.raise_errorf ~loc:pvb_loc "[@react.component] cannot be used with a destructured binding. Please use it on a `let make = ...` binding instead." -(* Lookup the value of `props` otherwise raise Invalid_argument error *) -let getPropsNameValue _acc (loc, exp) = - match (loc, exp) with +(* Lookup the value of `props` otherwise raise errorf *) +let getPropsNameValue _acc (loc, expr) = + match (loc, expr) 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_exn txt)) -[@@raises Invalid_argument] + | { txt; loc }, _ -> + Location.raise_errorf ~loc "[@react.component] only accepts 'props' as a field, given: %s" (Longident.last_exn txt) (* Lookup the `props` record or string as part of [@react.component] and store the name for use when rewriting *) @@ -284,12 +278,10 @@ let getPropsAttr payload = } :: _rest)) -> { propsName = "props" } - | Some (PStr ({ pstr_desc = Pstr_eval (_, _); _ } :: _rest)) -> - raise - (Invalid_argument - "react.component accepts a record config with props as an options.") + | Some (PStr ({ pstr_desc = Pstr_eval (_, _); pstr_loc; _ } :: _rest)) -> + Location.raise_errorf ~loc:pstr_loc + "[@react.component] accepts a record config with 'props' as a field." | _ -> defaultProps -[@@raises Invalid_argument] (* Plucks the label, loc, and type_ from an AST node *) let pluckLabelDefaultLocType (label, default, _, _, loc, type_) = @@ -370,7 +362,6 @@ let rec recursivelyMakeNamedArgsForExternal ~types_come_from_signature list args | _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 ~types_come_from_signature loc @@ -400,7 +391,6 @@ let makePropsValue fnName ~types_come_from_signature loc ]; pval_loc = loc; } -[@@raises Invalid_argument] (* Build an AST node representing an `external` with the definition of the [@bs.obj] *) @@ -413,7 +403,6 @@ let makePropsExternal fnName loc ~component_is_external (makePropsValue ~types_come_from_signature:component_is_external fnName loc namedArgListWithKeyAndRef propsType); } -[@@raises Invalid_argument] (* Build an AST node for the signature of the `external` definition *) let makePropsExternalSig fnName loc namedArgListWithKeyAndRef propsType = @@ -424,7 +413,6 @@ let makePropsExternalSig fnName loc namedArgListWithKeyAndRef propsType = (makePropsValue ~types_come_from_signature:true fnName loc namedArgListWithKeyAndRef propsType); } -[@@raises Invalid_argument] (* Build an AST node for the props name when converted to an object inside the function signature *) @@ -518,7 +506,6 @@ let makeExternalDecl fnName loc namedArgListWithKeyAndRef namedTypeList = makePropsExternal ~component_is_external:false fnName loc (List.map pluckLabelDefaultLocType namedArgListWithKeyAndRef) (makePropsType ~loc namedTypeList) -[@@raises Invalid_argument] (* TODO: some line number might still be wrong *) let jsxMapper = @@ -529,7 +516,7 @@ let jsxMapper = let argsForMake = argsWithLabels in let keyProps, otherProps = List.partition - (fun (arg_label, _) -> "key" = getLabel arg_label) + (fun (arg_label, _) -> "key" = getLabelOrEmpty arg_label) argsForMake in let jsxExpr, key, childrenProp = @@ -543,10 +530,12 @@ let jsxMapper = (label, mapper#expression ctxt expression)) 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] + match String.length str with + | 0 -> false + | _ -> + let first = String.sub str 0 1 in + let capped = String.uppercase_ascii first in + first = capped in let ident = match modulePath with @@ -608,7 +597,7 @@ let jsxMapper = let componentNameExpr = constantString ~loc:callerLoc id in let keyProps, nonChildrenProps = List.partition - (fun (arg_label, _) -> "key" = getLabel arg_label) + (fun (arg_label, _) -> "key" = getLabelOrEmpty arg_label) nonChildrenProps in @@ -657,17 +646,9 @@ let jsxMapper = let rec recursivelyTransformNamedArgsForMake ~ctxt mapper expr list = let expr = mapper#expression ctxt 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.") + Location.raise_errorf ~loc:expr.pexp_loc + ("~key cannot be accessed from the component props. Please set the key where the component is being used.") | Pexp_fun ( ((Optional label | Labelled label) as arg), default, @@ -714,7 +695,6 @@ let jsxMapper = "reason-react-ppx: react.component refs only support plain arguments \ and type annotations." | _ -> (list, None) - [@@raises Invalid_argument] in let argToType types (name, default, _noLabelName, _alias, loc, type_) = @@ -736,7 +716,7 @@ let jsxMapper = } ) :: types | Some type_, name, Some _default -> - ( getLabel name, + ( getLabelOrEmpty name, [], { ptyp_desc = Ptyp_constr ({ loc; txt = optionIdent }, [ type_ ]); @@ -745,7 +725,7 @@ let jsxMapper = ptyp_attributes = []; } ) :: types - | Some type_, name, _ -> (getLabel name, [], type_) :: types + | Some type_, name, _ -> (getLabelOrEmpty name, [], type_) :: types | None, Optional label, _ -> ( label, [], @@ -777,7 +757,6 @@ let jsxMapper = } ) :: types | _ -> types - [@@raises Invalid_argument] in let argToConcreteType types (name, loc, type_) = @@ -1110,7 +1089,7 @@ let jsxMapper = in let pluckArg (label, _, _, alias, loc, _) = ( label, - match getLabel label with + match getLabelOrEmpty label with | "" -> Builder.pexp_ident ~loc { txt = Lident alias; loc } | labelString -> Builder.pexp_apply ~loc diff --git a/ppx/test/component-without-make.t/input.re b/ppx/test/component-without-make.t/input.re new file mode 100644 index 000000000..bba265622 --- /dev/null +++ b/ppx/test/component-without-make.t/input.re @@ -0,0 +1,26 @@ +module X_as_main_function = { + [@react.component] + let x = () =>
; +}; + +module Create_element_as_main_function = { + [@react.component] + let createElement = (~lola) =>
{React.string(lola)}
; +}; + +/* This isn't valid running code, since Foo gets transformed into Foo.make, not createElement. */ +module Invalid_case = { + [@react.component] + let make = (~lola) => { + ; + }; +}; + +/* If main function is not make, neither createElement, then it can be explicitly annotated */ +/* NOTE: If you use `createElement` refmt removes it */ +module Valid_case = { + [@react.component] + let make = () => { + ; + }; +}; diff --git a/ppx/test/component-without-make.t/run.t b/ppx/test/component-without-make.t/run.t new file mode 100644 index 000000000..b15353338 --- /dev/null +++ b/ppx/test/component-without-make.t/run.t @@ -0,0 +1,53 @@ +Since we generate invalid syntax for the argument of the make fn `(Props : <>)` +We need to output ML syntax here, otherwise refmt could not parse it. + $ ../ppx.sh --output ml input.re + module X_as_main_function = + struct + external xProps : ?key:string -> unit -> < > Js.t = ""[@@mel.obj ] + let x () = ReactDOM.jsx "div" (((ReactDOM.domProps)[@merlin.hide ]) ()) + let x = + let Output$X_as_main_function$x (Props : < > Js.t) = x () in + Output$X_as_main_function$x + end + module Create_element_as_main_function = + struct + external createElementProps : + lola:'lola -> ?key:string -> unit -> < lola: 'lola > Js.t = "" + [@@mel.obj ] + let createElement = + ((fun ~lola -> + ReactDOM.jsx "div" + (((ReactDOM.domProps)[@merlin.hide ]) + ~children:(React.string lola) ())) + [@warning "-16"]) + let createElement = + let Output$Create_element_as_main_function$createElement + (Props : < lola: 'lola > Js.t) = + createElement ~lola:(Props ## lola) in + Output$Create_element_as_main_function$createElement + end + module Invalid_case = + struct + external makeProps : + lola:'lola -> ?key:string -> unit -> < lola: 'lola > Js.t = "" + [@@mel.obj ] + let make = + ((fun ~lola -> + React.jsx Create_element_as_main_function.make + (Create_element_as_main_function.makeProps ~lola ())) + [@warning "-16"]) + let make = + let Output$Invalid_case (Props : < lola: 'lola > Js.t) = + make ~lola:(Props ## lola) in + Output$Invalid_case + end + module Valid_case = + struct + external makeProps : ?key:string -> unit -> < > Js.t = ""[@@mel.obj ] + let make () = + React.jsx Component_with_x_as_main_function.x + (Component_with_x_as_main_function.xProps ()) + let make = + let Output$Valid_case (Props : < > Js.t) = make () in + Output$Valid_case + end diff --git a/ppx/test/component.t/input.re b/ppx/test/component.t/input.re index 7fdccd901..3c9839e68 100644 --- a/ppx/test/component.t/input.re +++ b/ppx/test/component.t/input.re @@ -41,6 +41,13 @@ module Forward_Ref = { }); }; +module Ref_as_prop = { + [@react.component] + let make = (~children, ~ref) => { + ; + }; +}; + module Onclick_handler_button = { [@react.component] let make = (~name, ~isDisabled=?) => { diff --git a/ppx/test/component.t/run.t b/ppx/test/component.t/run.t index aa9583ce1..22e4bcbf5 100644 --- a/ppx/test/component.t/run.t +++ b/ppx/test/component.t/run.t @@ -68,6 +68,27 @@ We need to output ML syntax here, otherwise refmt could not parse it. make ~buttonRef:(Props ## buttonRef) ~children:(Props ## children) in Output$Forward_Ref) end + module Ref_as_prop = + struct + external makeProps : + children:'children -> + ref:'ref -> + ?key:string -> unit -> < children: 'children ;ref: 'ref > Js.t + = ""[@@mel.obj ] + let make = + ((fun ~children -> + ((fun ~ref -> + ReactDOM.jsx "button" + (((ReactDOM.domProps)[@merlin.hide ]) ~children ~ref + ~className:"FancyButton" ())) + [@warning "-16"])) + [@warning "-16"]) + let make = + let Output$Ref_as_prop + (Props : < children: 'children ;ref: 'ref > Js.t) = + make ~ref:(Props ## ref) ~children:(Props ## children) in + Output$Ref_as_prop + end module Onclick_handler_button = struct external makeProps : diff --git a/ppx/test/components-destructured-error.t/component.re b/ppx/test/components-destructured-error.t/component.re new file mode 100644 index 000000000..f737d19fc --- /dev/null +++ b/ppx/test/components-destructured-error.t/component.re @@ -0,0 +1,3 @@ +[@react.component] +let (pageState, setPageState) = React.useState(_ => 0); +let make = (~children, ()) =>
children
; diff --git a/ppx/test/components-destructured-error.t/run.t b/ppx/test/components-destructured-error.t/run.t new file mode 100644 index 000000000..492c046db --- /dev/null +++ b/ppx/test/components-destructured-error.t/run.t @@ -0,0 +1,22 @@ +Test some locations in reason-react components + + $ cat >dune-project < (lang dune 3.8) + > (using melange 0.1) + > EOF + + $ cat >dune < (melange.emit + > (alias foo) + > (target foo) + > (libraries reason-react) + > (preprocess + > (pps melange.ppx reason-react-ppx))) + > EOF + + $ dune build + File "component.re", lines 1-2, characters 0-54: + 1 | [@react.component] + 2 | let (pageState, setPageState) = React.useState(_ => 0). + Error: [@react.component] cannot be used with a destructured binding. Please use it on a `let make = ...` binding instead. + [1] diff --git a/ppx/test/key-as-prop-error.t/component.re b/ppx/test/key-as-prop-error.t/component.re new file mode 100644 index 000000000..1ce26b19b --- /dev/null +++ b/ppx/test/key-as-prop-error.t/component.re @@ -0,0 +1,2 @@ +[@react.component] +let make = (~key) =>
key->React.string
; diff --git a/ppx/test/key-as-prop-error.t/run.t b/ppx/test/key-as-prop-error.t/run.t new file mode 100644 index 000000000..99bac040b --- /dev/null +++ b/ppx/test/key-as-prop-error.t/run.t @@ -0,0 +1,22 @@ +Test some locations in reason-react components + + $ cat >dune-project < (lang dune 3.8) + > (using melange 0.1) + > EOF + + $ cat >dune < (melange.emit + > (alias foo) + > (target foo) + > (libraries reason-react) + > (preprocess + > (pps melange.ppx reason-react-ppx))) + > EOF + + $ dune build + File "component.re", line 2, characters 11-51: + 2 | let make = (~key) =>
key->React.string
; + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + Error: ~key cannot be accessed from the component props. Please set the key where the component is being used. + [1] diff --git a/ppx/test/record-props-error.t/input.re b/ppx/test/record-props-error.t/input.re new file mode 100644 index 000000000..32a555110 --- /dev/null +++ b/ppx/test/record-props-error.t/input.re @@ -0,0 +1,6 @@ +module Record_props = { + [@react.component {no_props: string}] + let make = (~lola) => { +
{React.string(lola)}
; + }; +}; diff --git a/ppx/test/record-props-error.t/run.t b/ppx/test/record-props-error.t/run.t new file mode 100644 index 000000000..d2440098d --- /dev/null +++ b/ppx/test/record-props-error.t/run.t @@ -0,0 +1,8 @@ +Since we generate invalid syntax for the argument of the make fn `(Props : <>)` +We need to output ML syntax here, otherwise refmt could not parse it. + $ ../ppx.sh --output ml input.re + File "output.ml", line 5, characters 68-76: + 5 | no_props + ^^^^^^^^ + Error: [@react.component] only accepts 'props' as a field, given: no_props + [1] diff --git a/ppx/test/record-props.t/input.re b/ppx/test/record-props.t/input.re new file mode 100644 index 000000000..4ad19da60 --- /dev/null +++ b/ppx/test/record-props.t/input.re @@ -0,0 +1,6 @@ +module Record_props = { + [@react.component {props: string}] + let make = (~lola) => { +
{React.string(lola)}
; + }; +}; diff --git a/ppx/test/record-props.t/run.t b/ppx/test/record-props.t/run.t new file mode 100644 index 000000000..fee6dc40a --- /dev/null +++ b/ppx/test/record-props.t/run.t @@ -0,0 +1,19 @@ +Since we generate invalid syntax for the argument of the make fn `(Props : <>)` +We need to output ML syntax here, otherwise refmt could not parse it. + $ ../ppx.sh --output ml input.re + module Record_props = + struct + external makeProps : + lola:'lola -> ?key:string -> unit -> < lola: 'lola > Js.t = "" + [@@mel.obj ] + let make = + ((fun ~lola -> + ReactDOM.jsx "div" + (((ReactDOM.domProps)[@merlin.hide ]) + ~children:(React.string lola) ())) + [@warning "-16"]) + let make = + let Output$Record_props (string : < lola: 'lola > Js.t) = + make ~lola:(string ## lola) in + Output$Record_props + end