Skip to content

Commit

Permalink
Implement JSX spread children syntax for Flow
Browse files Browse the repository at this point in the history
Summary: In facebook/jsx#59 spread children were added to the JSX specification. This adds type checking and parsing for spread children for CSX.

Reviewed By: calebmer

Differential Revision: D6236379

fbshipit-source-id: 5eca3b7b3748a47ff25e257fcb3c4778a73a5495
  • Loading branch information
Akshay Nanavati authored and facebook-github-bot committed Nov 9, 2017
1 parent 8ee159a commit 7782de7
Show file tree
Hide file tree
Showing 9 changed files with 169 additions and 16 deletions.
98 changes: 98 additions & 0 deletions newtests/csx/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,104 @@ export default suite(({addFile, addFiles, addCode}) => [
`)
.noNewErrors(),
]),
test('Should raise no errors if CSX children are passed as spread with list inline', [
addCode(`
// @csx
type Props = {|children: Array<string>|};
function Foo(props: Props) {}
<Foo>{...["foo", "bar"]}</Foo>;
`)
.noNewErrors(),
]),
test('Should raise no errors if CSX children are passed as spread with variable', [
addCode(`
// @csx
type Props = {|children: Array<string>|};
function Foo(props: Props) {}
const arr = ["foo", "bar"];
<Foo>{...arr}</Foo>;
`)
.noNewErrors(),
]),
test('Should raise an error if CSX spread children are not a list', [
addCode(`
// @csx
type Props = {|children: Array<number>|};
function Foo(props: Props) {}
const x = 42;
<Foo>{...x}</Foo>;
`)
.newErrors(
`
test.js:8
8: <Foo>{...x}</Foo>;
^ number. This type is incompatible with
8: <Foo>{...x}</Foo>;
^ $Iterable
Property \`@@iterator\` is incompatible:
8: <Foo>{...x}</Foo>;
^ property \`@@iterator\` of $Iterable. Property not found in
8: <Foo>{...x}</Foo>;
^ number
`
)
]),
test('Should raise an error if CSX children passed as spread have the wrong type', [
addCode(`
// @csx
type Props = {|children: Array<number>|};
function Foo(props: Props) {}
const arr = ["foo"];
<Foo>{...arr}</Foo>;
`)
.newErrors(
`
test.js:8
8: <Foo>{...arr}</Foo>;
^^^^^ props of JSX element \`Foo\`. This type is incompatible with the expected param type of
6: function Foo(props: Props) {}
^^^^^ object type
Property \`children\` is incompatible:
8: <Foo>{...arr}</Foo>;
^^^^^ JSX element \`Foo\`. Has some incompatible type argument with
5: type Props = {|children: Array<number>|};
^^^^^^^^^^^^^ array type
Type argument \`T\` is incompatible:
7: const arr = ["foo"];
^^^^^ string. This type is incompatible with
5: type Props = {|children: Array<number>|};
^^^^^^ number
`,
),
]),
test('Should raise no errors if CSX children are passed as spread with function call', [
addCode(`
// @csx
type Props = {|children: Array<number>|};
type TProps = {|text: string|};
function Foo(props: Props) {}
function Title(props: TProps) { return 42; }
function get_titles(l) {
return [];
}
var arr = ["foo", "bar"];
<Foo>{...get_titles(arr)}</Foo>;
`)
.noNewErrors(),
]),
test('Should raise no errors if CSX children are passed as spread with other children', [
addCode(`
// @csx
type Props = {|children: Array<string>|};
function Foo(props: Props) {}
<Foo>
{"foobar"}
{...["foo", "bar"]}
{"baz"}
</Foo>;
`)
.noNewErrors(),
]),
test('Should raise no errors if a JSX spread provides all required attributes for an exact type', [
addCode(`
// @csx
Expand Down
9 changes: 9 additions & 0 deletions newtests/jsx_pragma/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,15 @@ export default suite(({addFile, addFiles, addCode}) => [
<Bar x="hi" />;
`).noNewErrors(),
]),
test('Spread syntax in children should work', [
addCode(`
// @jsx Foo
function Foo(elem: number, props: null, child1: 'a', child2: 'b', child3: 'c') {}
const Bar = 123;
<Bar>{...["a", "b", "c"]}</Bar>;
`).noNewErrors(),
]),
test('Exact prop type with spread still does not work', [
addCode(`
// @jsx Foo
Expand Down
1 change: 1 addition & 0 deletions src/parser/ast.ml
Original file line number Diff line number Diff line change
Expand Up @@ -906,6 +906,7 @@ and JSX : sig
| Element of 'M element
| Fragment of 'M fragment
| ExpressionContainer of 'M ExpressionContainer.t
| SpreadChild of 'M Expression.t
| Text of Text.t

and 'M element = {
Expand Down
4 changes: 4 additions & 0 deletions src/parser/estree_translator.ml
Original file line number Diff line number Diff line change
Expand Up @@ -1289,6 +1289,10 @@ end with type t = Impl.t) = struct
| loc, Element element -> jsx_element (loc, element)
| loc, Fragment fragment -> jsx_fragment (loc, fragment)
| loc, ExpressionContainer expr -> jsx_expression_container (loc, expr)
| loc, SpreadChild expr ->
node "JSXSpreadChild" loc [|
"expression", expression expr;
|]
| loc, Text str -> jsx_text (loc, str)
)

Expand Down
32 changes: 24 additions & 8 deletions src/parser/jsx_parser.ml
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,7 @@ module JSX (Parse: Parser_common.PARSER) = struct
argument;
})

let expression_container env =
Eat.push_lex_mode env Lex_mode.NORMAL;
let start_loc = Peek.loc env in
Expect.token env T_LCURLY;
let expression_container' env start_loc =
let expression = if Peek.token env = T_RCURLY
then
let empty_loc = Loc.btwn_exclusive start_loc (Peek.loc env) in
Expand All @@ -40,6 +37,28 @@ module JSX (Parse: Parser_common.PARSER) = struct
expression;
})

let expression_container env =
Eat.push_lex_mode env Lex_mode.NORMAL;
let start_loc = Peek.loc env in
Expect.token env T_LCURLY;
expression_container' env start_loc

let expression_container_or_spread_child env =
Eat.push_lex_mode env Lex_mode.NORMAL;
let start_loc = Peek.loc env in
Expect.token env T_LCURLY;
match Peek.token env with
| T_ELLIPSIS ->
Expect.token env T_ELLIPSIS;
let expr = Parse.assignment env in
let end_loc = Peek.loc env in
Expect.token env T_RCURLY;
Eat.pop_lex_mode env;
Loc.btwn start_loc end_loc, JSX.SpreadChild expr
| _ ->
let expression_container = expression_container' env start_loc in
fst expression_container, JSX.ExpressionContainer (snd expression_container)

let identifier env =
let loc = Peek.loc env in
let name = match Peek.token env with
Expand Down Expand Up @@ -195,12 +214,9 @@ module JSX (Parse: Parser_common.PARSER) = struct
| ChildElement of (Loc.t * Loc.t JSX.element)
| ChildFragment of (Loc.t * Loc.t JSX.fragment)


let rec child env =
match Peek.token env with
| T_LCURLY ->
let expression_container = expression_container env in
fst expression_container, JSX.ExpressionContainer (snd expression_container)
| T_LCURLY -> expression_container_or_spread_child env
| T_JSX_TEXT (loc, value, raw) as token ->
Expect.token env token;
loc, JSX.Text { JSX.Text.value; raw; }
Expand Down
2 changes: 2 additions & 0 deletions src/parser_utils/flow_ast_mapper.ml
Original file line number Diff line number Diff line change
Expand Up @@ -800,6 +800,8 @@ class mapper = object(this)
id this#jsx_fragment frag child (fun frag -> loc, Fragment frag)
| ExpressionContainer expr ->
id this#jsx_expression expr child (fun expr -> loc, ExpressionContainer expr)
| SpreadChild expr ->
id this#expression expr child (fun expr -> loc, SpreadChild expr)
| Text _ -> child

method jsx_expression (jsx_expr: Loc.t Ast.JSX.ExpressionContainer.t) =
Expand Down
26 changes: 19 additions & 7 deletions src/typing/statement.ml
Original file line number Diff line number Diff line change
Expand Up @@ -3703,7 +3703,7 @@ and jsx_mk_props cx reason c name attributes children = Ast.JSX.(
cx
~use_op:UnknownUse
~reason_op:reason
(List.map (fun child -> UnresolvedArg child) children)
children
(ResolveSpreadsToArrayLiteral (mk_id (), tout))
) in
let p = Field (None, arr, Neutral) in
Expand All @@ -3717,6 +3717,13 @@ and jsx_desugar cx name component_t props attributes children eloc =
| None ->
let reason = mk_reason (RReactElement (Some name)) eloc in
let react = Env.var_ref ~lookup_mode:ForValue cx "React" eloc in
let children = List.map (function
| UnresolvedArg a -> a
| UnresolvedSpreadArg a ->
(* TODO - the location might be wrong here? *)
Flow.add_output cx Flow_error.(EUnsupportedSyntax (eloc, SpreadArgument));
AnyT.why (reason_of_t a)
) children in
Tvar.mk_where cx reason (fun tvar ->
let reason_createElement =
mk_reason (RProperty (Some "createElement")) eloc in
Expand All @@ -3740,7 +3747,10 @@ and jsx_desugar cx name component_t props attributes children eloc =
| _ -> props in
let argts =
[Arg component_t; Arg props] @
(List.map (fun c -> Arg c) children) in
(List.map (function
| UnresolvedArg c -> Arg c
| UnresolvedSpreadArg c -> SpreadArg c
) children) in
Ast.Expression.(match jsx_expr with
| _, Member {
Member._object;
Expand Down Expand Up @@ -3783,17 +3793,19 @@ and jsx_pragma_expression cx raw_jsx_expr loc = Ast.Expression.(function
)

and jsx_body cx = Ast.JSX.(function
| _, Element e -> Some (jsx cx e)
| _, Fragment f -> Some (jsx_fragment cx f)
| _, Element e -> Some (UnresolvedArg (jsx cx e))
| _, Fragment f -> Some (UnresolvedArg (jsx_fragment cx f))
| _, ExpressionContainer ec -> (
let open ExpressionContainer in
let { expression = ex } = ec in
Some (match ex with
Some (UnresolvedArg (match ex with
| Expression (loc, e) -> expression cx (loc, e)
| EmptyExpression loc ->
DefT (mk_reason (RCustom "empty jsx body") loc, EmptyT))
DefT (mk_reason (RCustom "empty jsx body") loc, EmptyT)))
)
| loc, Text { Text.value; raw=_; } -> jsx_trim_text loc value
| _, SpreadChild expr -> Some (UnresolvedSpreadArg (expression cx expr))
| loc, Text { Text.value; raw=_; } ->
Option.map (jsx_trim_text loc value) (fun c -> UnresolvedArg c)
)

and jsx_trim_text loc value =
Expand Down
6 changes: 5 additions & 1 deletion tests/react_children/react_children.exp
Original file line number Diff line number Diff line change
Expand Up @@ -878,6 +878,10 @@ Error: fun.js:142
21: class FunArray extends React.Component<{children: Fn | Array<Fn>}, void> {}
^^^^^^^^^ array type

Error: spread.js:7
7: <Foo>{...["a", "b"]}</Foo>
^^^^^ A spread argument is unsupported here

Error: tabs.js:26
26: <TabBar />; // Error: `children` is required.
^^^^^^^^^^ props of React element `TabBar`. This type is incompatible with
Expand Down Expand Up @@ -2433,4 +2437,4 @@ Error: view.js:40
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ array type


Found 111 errors
Found 112 errors
7 changes: 7 additions & 0 deletions tests/react_children/spread.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// @flow

import React from 'react';

class Foo extends React.Component<{children: Array<string>}, void> {}

<Foo>{...["a", "b"]}</Foo>

0 comments on commit 7782de7

Please # to comment.