diff --git a/jscomp/syntax/src/res_core.ml b/jscomp/syntax/src/res_core.ml index b88a9fd4cc..95ab19e2ba 100644 --- a/jscomp/syntax/src/res_core.ml +++ b/jscomp/syntax/src/res_core.ml @@ -982,6 +982,75 @@ let parseRegion p ~grammar ~f = Parser.eatBreadcrumb p; nodes +let isJsxPropWellFormed p = + (* jsx-prop ::= prop = value *) + (* Possible tokens after `=` *) + (* value ::= *) + (* | True *) + (* | False *) + (* | #Variant *) + (* | `string` *) + (* | [array] *) + (* | list{} *) + (* | () *) + (* | ?value *) + (* | *) + (* | %raw *) + (* | module(P) *) + (* | string-literal *) + (* | int-literal *) + (* | float-literal *) + (* | variable *) + (* | A.B *) + let isPossibleAfterEqual token = + match token with + | Token.True | False | Hash | Backtick | Lbracket | List | Lparen | Question + | LessThan | Percent | Module | String _ | Int _ | Float _ | Lident _ + | Uident _ -> + true + | _ -> false + in + (* jsx-prop ::= prop = {value} *) + (* Possible tokens after `}` *) + (* prop={{expr}} *) + (* prop={value} > ...children *) + (* prop={value} /> *) + (* prop={value} prop2={value2} /> *) + (* prop={value} ?prop2 /> *) + let isPossibleAfterRbrace token = + match token with + | Token.Rbrace | GreaterThan | Forwardslash | Lident _ | Question | Eof -> + true + | _ -> false + in + let res = + Parser.lookahead p (fun state -> + match state.Parser.token with + (* arrived at k1= *) + | Equal -> ( + Parser.next state; + match state.Parser.token with + (* arrived at k1={ *) + | Lbrace -> ( + Parser.next state; + match state.Parser.token with + | Rbrace -> false + | _ -> + goToClosing Rbrace state; + isPossibleAfterRbrace state.Parser.token) + (* arrived at k1=v1 *) + | token when isPossibleAfterEqual token -> ( + Parser.next state; + match state.Parser.token with + (* arrived at k1=v1 =v2 *) + | Equal -> false + | _ -> true) + (* arrived at k1=x *) + | _ -> false) + | _ -> false) + in + res + (* let-binding ::= pattern = expr *) (* ∣ value-name { parameter } [: typexpr] [:> typexpr] = expr *) (* ∣ value-name : poly-typexpr = expr *) @@ -2745,18 +2814,31 @@ and parseJsxProp p = else match p.Parser.token with | Equal -> - Parser.next p; - (* no punning *) - let optional = Parser.optional p Question in - Scanner.popMode p.scanner Jsx; - let attrExpr = - let e = parsePrimaryExpr ~operand:(parseAtomicExpr p) p in - {e with pexp_attributes = propLocAttr :: e.pexp_attributes} - in - let label = - if optional then Asttypes.Optional name else Asttypes.Labelled name - in - Some (label, attrExpr) + let wellFormed = isJsxPropWellFormed p in + if wellFormed then ( + (* no punning *) + Parser.next p; + let optional = Parser.optional p Question in + Scanner.popMode p.scanner Jsx; + let attrExpr = + let e = parsePrimaryExpr ~operand:(parseAtomicExpr p) p in + {e with pexp_attributes = propLocAttr :: e.pexp_attributes} + in + let label = + if optional then Asttypes.Optional name else Asttypes.Labelled name + in + Some (label, attrExpr)) + else + let label = + if optional then Asttypes.Optional name else Asttypes.Labelled name + in + let id = Location.mknoloc "rescript.exprhole" in + let attrExpr = + Ast_helper.Exp.mk + (Pexp_extension (id, PStr [])) + ~attrs:[propLocAttr] + in + Some (label, attrExpr) | _ -> let attrExpr = Ast_helper.Exp.ident ~loc ~attrs:[propLocAttr] diff --git a/jscomp/syntax/tests/parsing/recovery/expression/expected/jsx.res.txt b/jscomp/syntax/tests/parsing/recovery/expression/expected/jsx.res.txt index 428af6eb03..8ba8b74cbb 100644 --- a/jscomp/syntax/tests/parsing/recovery/expression/expected/jsx.res.txt +++ b/jscomp/syntax/tests/parsing/recovery/expression/expected/jsx.res.txt @@ -4,9 +4,118 @@ 1 │ let x =
2 │ + 3 │ let x = {...props} props=mo + │ dule(Foo) /> I'm not sure what to parse here when looking at "@". + + Syntax error! + tests/parsing/recovery/expression/jsx.res:3:25 + + 1 │ let x =
+ 2 │ + 3 │ let x = {...props} props=mo + │ dule(Foo) /> + 4 │ + 5 │ let x = + + I'm not sure what to parse here when looking at "=". + + + Syntax error! + tests/parsing/recovery/expression/jsx.res:5:46 + + 3 │ let x = {...props} props=mo + │ dule(Foo) /> + 4 │ + 5 │ let x = + 6 │ + 7 │ let x = {...props} pr + │ ops4= /> + + I'm not sure what to parse here when looking at "=". + + + Syntax error! + tests/parsing/recovery/expression/jsx.res:7:77 + + 5 │ let x = + 6 │ + 7 │ let x = {...props} pr + │ ops4= /> + 8 │ + 9 │ let x = {f(v + 1)}} pr + │ op1= prop3=1 /> + + I'm not sure what to parse here when looking at "=". + + + Syntax error! + tests/parsing/recovery/expression/jsx.res:9:76 + + 7 │ let x = {...props} p + │ rops4= /> + 8 │ + 9 │ let x = {f(v + 1)}} p + │ rop1= prop3=1 /> + 10 │ + 11 │ let x = + + I'm not sure what to parse here when looking at "=". + + + Syntax error! + tests/parsing/recovery/expression/jsx.res:12:10 + + 10 │ + 11 │ let x = + 12 │ + 13 │ {("View the transaction on " ++ txExplererUrl)->restr} + 14 │ + + I'm not sure what to parse here when looking at "=". + let x = ((div ~children:[] ())[@JSX ]) [@@@ ] -;;[%rescript.exprhole ][@@ ] \ No newline at end of file +;;[%rescript.exprhole ][@@ ] +let x = + ((Component.createElement ~prop1:(([%rescript.exprhole ]) + [@res.namedArgLoc ]) ~prop2:(({js|value2|js})[@res.namedArgLoc ]) + ~prop3:((C.createElement ~children:[] ())[@res.namedArgLoc ][@JSX ]) + ~_spreadProps:((props)[@res.namedArgLoc ]) ~props:(((module Foo)) + [@res.namedArgLoc ]) ~children:[] ()) + [@JSX ]) +let x = + ((Component.createElement ?prop0:((prop0)[@res.namedArgLoc ]) + ~prop1:(([|1;2;3|])[@res.namedArgLoc ]) ~prop2:(([%rescript.exprhole ]) + [@res.namedArgLoc ]) ~prop3:((value3)[@res.namedArgLoc ][@res.braces ]) + ?prop4:((value4)[@res.namedArgLoc ]) ~children:[] ()) + [@JSX ]) +let x = + ((Component.createElement ~prop1:((value1)[@res.namedArgLoc ]) + ~prop2:(({js|value2|js})[@res.namedArgLoc ]) + ~prop3:((C.createElement ~children:[] ())[@res.namedArgLoc ][@JSX ]) + ~_spreadProps:((props)[@res.namedArgLoc ]) + ~props4:(([%rescript.exprhole ])[@res.namedArgLoc ]) ~children:[] ()) + [@JSX ]) +let x = + ((Component.createElement ~className:((Styles.something) + [@res.namedArgLoc ]) ~prop2:((fun v -> ((f (v + 1))[@res.braces ])) + [@res.namedArgLoc ][@res.braces ]) ~prop1:(([%rescript.exprhole ]) + [@res.namedArgLoc ]) ~prop3:((1)[@res.namedArgLoc ]) ~children:[] ()) + [@JSX ]) +let x = + ((a ~prop:(([%rescript.exprhole ])[@res.namedArgLoc ]) + ~href:(({j|https://$txExplererUrl/tx/$txHash|j}) + [@res.namedArgLoc ][@res.braces ][@res.template ]) + ~target:(({js|_blank|js})[@res.namedArgLoc ]) + ~rel:(({js|noopener noreferrer|js})[@res.namedArgLoc ]) + ~children:[((({js|View the transaction on |js} ^ txExplererUrl) |. + restr) + [@res.braces ])] ()) + [@JSX ]) \ No newline at end of file diff --git a/jscomp/syntax/tests/parsing/recovery/expression/jsx.res b/jscomp/syntax/tests/parsing/recovery/expression/jsx.res index c20ee242a0..1f5850ba42 100644 --- a/jscomp/syntax/tests/parsing/recovery/expression/jsx.res +++ b/jscomp/syntax/tests/parsing/recovery/expression/jsx.res @@ -1 +1,14 @@ let x =
+ +let x = {...props} props=module(Foo) /> + +let x = + +let x = {...props} props4= /> + +let x = {f(v + 1)}} prop1= prop3=1 /> + +let x = + + {("View the transaction on " ++ txExplererUrl)->restr} +