diff --git a/lib/core.js b/lib/core.js index 255f4e89f8a..2715ed37776 100644 --- a/lib/core.js +++ b/lib/core.js @@ -608,3 +608,6 @@ declare var exports: any; /* Commonly available, shared between node and dom */ declare var console: any; + +/* JSX Intrinsics */ +type $JSXIntrinsics = Object; diff --git a/src/common/flowConfig.ml b/src/common/flowConfig.ml index c856bb63463..8f242778c44 100644 --- a/src/common/flowConfig.ml +++ b/src/common/flowConfig.ml @@ -45,6 +45,7 @@ module Opts = struct esproposal_class_instance_fields: esproposal_feature_mode; esproposal_class_static_fields: esproposal_feature_mode; esproposal_decorators: esproposal_feature_mode; + facebook_ignore_fbt: bool; ignore_non_literal_requires: bool; moduleSystem: moduleSystem; module_name_mappers: (Str.regexp * string) list; @@ -111,6 +112,7 @@ module Opts = struct esproposal_class_instance_fields = ESPROPOSAL_WARN; esproposal_class_static_fields = ESPROPOSAL_WARN; esproposal_decorators = ESPROPOSAL_WARN; + facebook_ignore_fbt = false; ignore_non_literal_requires = false; moduleSystem = Node; module_name_mappers = []; @@ -490,54 +492,64 @@ let parse_excludes config lines = let file_extension = Str.regexp "^\\(\\.[^ \t]+\\)+$" -let parse_options config lines = Opts.( - let options = Opts.parse config.options lines - |> Opts.define_opt "esproposal.class_instance_fields" Opts.({ +let parse_options config lines = + let open Opts in + let options = parse config.options lines + |> define_opt "esproposal.class_instance_fields" { _initializer = USE_DEFAULT; flags = []; optparser = optparse_esproposal_feature_flag ~allow_enable:true; setter = (fun opts v -> { opts with esproposal_class_instance_fields = v; }); - }) + } - |> Opts.define_opt "esproposal.class_static_fields" Opts.({ + |> define_opt "esproposal.class_static_fields" { _initializer = USE_DEFAULT; flags = []; optparser = optparse_esproposal_feature_flag ~allow_enable:true; setter = (fun opts v -> { opts with esproposal_class_static_fields = v; }); - }) + } - |> Opts.define_opt "esproposal.decorators" Opts.({ + |> define_opt "esproposal.decorators" { _initializer = USE_DEFAULT; flags = []; optparser = optparse_esproposal_feature_flag; setter = (fun opts v -> { opts with esproposal_decorators = v; }); - }) + } - |> Opts.define_opt "log.file" Opts.({ + |> define_opt "facebook.ignore_fbt" { + _initializer = USE_DEFAULT; + flags = []; + optparser = optparse_boolean; + setter = (fun opts v -> { + opts with facebook_ignore_fbt = v; + }); + } + + |> define_opt "log.file" { _initializer = USE_DEFAULT; flags = []; optparser = optparse_filepath; setter = (fun opts v -> { opts with log_file = Some v; }); - }) + } - |> Opts.define_opt "module.ignore_non_literal_requires" Opts.({ + |> define_opt "module.ignore_non_literal_requires" { _initializer = USE_DEFAULT; flags = []; optparser = optparse_boolean; setter = (fun opts v -> {opts with ignore_non_literal_requires = v;} ); - }) + } - |> Opts.define_opt "module.file_ext" Opts.({ + |> define_opt "module.file_ext" { _initializer = INIT_FN (fun opts -> { opts with module_file_exts = SSet.empty; }); @@ -555,9 +567,9 @@ let parse_options config lines = Opts.( let module_file_exts = SSet.add v opts.module_file_exts in {opts with module_file_exts;} ); - }) + } - |> Opts.define_opt "module.name_mapper" Opts.({ + |> define_opt "module.name_mapper" { _initializer = USE_DEFAULT; flags = [ALLOW_DUPLICATE]; optparser = (fun str -> @@ -580,9 +592,9 @@ let parse_options config lines = Opts.( let module_name_mappers = v :: opts.module_name_mappers in {opts with module_name_mappers;} ); - }) + } - |> Opts.define_opt "module.system" Opts.({ + |> define_opt "module.system" { _initializer = USE_DEFAULT; flags = []; optparser = optparse_enum [ @@ -592,9 +604,9 @@ let parse_options config lines = Opts.( setter = (fun opts v -> { opts with moduleSystem = v; }); - }) + } - |> Opts.define_opt "module.system.node.resolve_dirname" Opts.({ + |> define_opt "module.system.node.resolve_dirname" { _initializer = INIT_FN (fun opts -> { opts with node_resolver_dirnames = []; }); @@ -604,84 +616,83 @@ let parse_options config lines = Opts.( let node_resolver_dirnames = v :: opts.node_resolver_dirnames in {opts with node_resolver_dirnames;} ); - }) + } - |> Opts.define_opt "munge_underscores" Opts.({ + |> define_opt "munge_underscores" { _initializer = USE_DEFAULT; flags = []; optparser = optparse_boolean; setter = (fun opts v -> {opts with munge_underscores = v;} ); - }) + } - |> Opts.define_opt "server.max_workers" Opts.({ + |> define_opt "server.max_workers" { _initializer = USE_DEFAULT; flags = []; optparser = optparse_uint; setter = (fun opts v -> {opts with max_workers = v;} ); - }) + } - |> Opts.define_opt "strip_root" Opts.({ + |> define_opt "strip_root" { _initializer = USE_DEFAULT; flags = []; optparser = optparse_boolean; setter = (fun opts v -> {opts with strip_root = v;} ); - }) + } - |> Opts.define_opt "suppress_comment" Opts.({ + |> define_opt "suppress_comment" { _initializer = USE_DEFAULT; flags = [ALLOW_DUPLICATE]; optparser = optparse_regexp; setter = (fun opts v -> { opts with suppress_comments = v::(opts.suppress_comments); }); - }) + } - |> Opts.define_opt "suppress_type" Opts.({ + |> define_opt "suppress_type" { _initializer = USE_DEFAULT; flags = [ALLOW_DUPLICATE]; optparser = optparse_string; setter = (fun opts v -> { opts with suppress_types = SSet.add v opts.suppress_types; }); - }) + } - |> Opts.define_opt "temp_dir" Opts.({ + |> define_opt "temp_dir" { _initializer = USE_DEFAULT; flags = []; optparser = optparse_filepath; setter = (fun opts v -> { opts with temp_dir = v; }); - }) + } - |> Opts.define_opt "traces" Opts.({ + |> define_opt "traces" { _initializer = USE_DEFAULT; flags = []; optparser = optparse_uint; setter = (fun opts v -> {opts with traces = v;} ); - }) + } - |> Opts.define_opt "unsafe.enable_getters_and_setters" Opts.({ + |> define_opt "unsafe.enable_getters_and_setters" { _initializer = USE_DEFAULT; flags = []; optparser = optparse_boolean; setter = (fun opts v -> {opts with enable_unsafe_getters_and_setters = v;} ); - }) + } |> get_defined_opts in {config with options} -) let assert_version (ln, line) = try diff --git a/src/common/flowConfig.mli b/src/common/flowConfig.mli index 3130a33ce0b..f021ff6ec83 100644 --- a/src/common/flowConfig.mli +++ b/src/common/flowConfig.mli @@ -19,6 +19,7 @@ module Opts : sig esproposal_class_instance_fields: esproposal_feature_mode; esproposal_class_static_fields: esproposal_feature_mode; esproposal_decorators: esproposal_feature_mode; + facebook_ignore_fbt: bool; ignore_non_literal_requires: bool; moduleSystem: moduleSystem; module_name_mappers: (Str.regexp * string) list; diff --git a/src/typing/debug_js.ml b/src/typing/debug_js.ml index 39b9333c13f..8089ab401ed 100644 --- a/src/typing/debug_js.ml +++ b/src/typing/debug_js.ml @@ -340,6 +340,10 @@ and _json_of_use_t_impl json_cx t = Hh_json.( "type", _json_of_t json_cx t ] + | ReifyTypeT (_, t_out) -> [ + "t_out", _json_of_t json_cx t_out + ] + | SpecializeT (_, cache, targs, tvar) -> [ "cache", JSON_Bool cache; "types", JSON_Array (List.map (_json_of_t json_cx) targs); @@ -454,6 +458,7 @@ and _json_of_use_t_impl json_cx t = Hh_json.( "target_module_t", _json_of_t json_cx target_module_t; "t_out", _json_of_t json_cx t_out; ] + | DebugPrintT _reason -> [] ) ) @@ -743,9 +748,16 @@ let json_of_t ?(depth=1000) cx t = let json_cx = { cx; depth; stack = ISet.empty; } in _json_of_t json_cx t +let json_of_use_t ?(depth=1000) cx use_t = + let json_cx = { cx; depth; stack = ISet.empty; } in + _json_of_use_t json_cx use_t + let jstr_of_t ?(depth=1000) cx t = Hh_json.json_to_multiline (json_of_t ~depth cx t) +let jstr_of_use_t ?(depth=1000) cx use_t = + Hh_json.json_to_multiline (json_of_use_t ~depth cx use_t) + let json_of_graph ?(depth=1000) cx = Hh_json.( let entries = IMap.fold (fun id _ entries -> let json_cx = { cx; depth; stack = ISet.empty; } in diff --git a/src/typing/debug_js.mli b/src/typing/debug_js.mli index c12a8f1876f..e74189b7f2f 100644 --- a/src/typing/debug_js.mli +++ b/src/typing/debug_js.mli @@ -10,6 +10,8 @@ val json_of_t: ?depth:int -> Context.t -> Type.t -> Hh_json.json val jstr_of_t: ?depth:int -> Context.t -> Type.t -> string +val json_of_use_t: ?depth:int -> Context.t -> Type.use_t -> Hh_json.json +val jstr_of_use_t: ?depth:int -> Context.t -> Type.use_t -> string val json_of_graph: ?depth:int -> Context.t -> Hh_json.json val jstr_of_graph: ?depth:int -> Context.t -> string val json_of_scope: ?depth:int -> Context.t -> Scope.t -> Hh_json.json diff --git a/src/typing/flow_js.ml b/src/typing/flow_js.ml index 9e2804e7c3f..0d0becd6c06 100644 --- a/src/typing/flow_js.ml +++ b/src/typing/flow_js.ml @@ -1114,6 +1114,14 @@ let rec __flow cx ((l: Type.t), (u: Type.use_t)) trace = | l, UseT DestructuringT (reason, t, s) -> rec_flow_t cx trace (l, eval_selector cx reason t s) + (*************) + (* Debugging *) + (*************) + + | (_, DebugPrintT (reason)) -> + let msg = (spf "!!! DebugPrintT: %s" (Debug_js.jstr_of_t cx l)) in + add_error cx [reason, msg]; + (************) (* tainting *) (************) @@ -2393,6 +2401,20 @@ let rec __flow cx ((l: Type.t), (u: Type.use_t)) trace = | (TypeT(_,l), UseT TypeT(_,u)) -> rec_unify cx trace l u + | (TypeT(type_reason, l), GetPropT(get_reason, (prop_reason, name), t_out)) -> + (** + * Reify the type, extract the prop from that, then wrap that result in a + * TypeT again. + *) + let prop_t = mk_tvar_where cx get_reason (fun t -> + rec_flow cx trace (l, GetPropT(get_reason, (prop_reason, name), t)) + ) in + rec_flow cx trace (TypeT (type_reason, mk_typeof_annotation cx ~trace prop_t), T t_out) + + | (TypeT(_, l), ReifyTypeT(_, t_out)) -> + (* Extract the type denoted by the type expression *) + rec_flow cx trace (l, T t_out) + (* non-class/function values used in annotations are errors *) | _, UseT TypeT _ -> prerr_flow cx trace @@ -3563,6 +3585,7 @@ and err_operation = function | HasOwnPropT _ -> "Property not found in" | HasPropT _ -> "Property not found in" | UnaryMinusT _ -> "Expected number instead of" + | ReifyTypeT _ -> "Internal Error: Invalid type applied to ReifyTypeT!" (* unreachable or unclassified use-types. until we have a mechanical way to verify that all legit use types are listed above, we can't afford to throw on a use type, so mark the error instead *) diff --git a/src/typing/gc_js.ml b/src/typing/gc_js.ml index 4781bd931c0..b3a8c5f9e8b 100644 --- a/src/typing/gc_js.ml +++ b/src/typing/gc_js.ml @@ -287,6 +287,9 @@ and gc_use cx state = function | NotT (_, t) -> gc cx state t + | ReifyTypeT (_, t_out) -> + gc cx state t_out + | SpecializeT (_, _, ts, t) -> ts |> List.iter (gc cx state); gc cx state t @@ -373,6 +376,8 @@ and gc_use cx state = function gc cx state target_module; gc cx state t_out; + | DebugPrintT _ -> () + and gc_id cx state id = let root_id, constraints = Flow_js.find_constraints cx id in ( if state#mark id then ( diff --git a/src/typing/type.ml b/src/typing/type.ml index 4c87b6327dc..709e65104c3 100644 --- a/src/typing/type.ml +++ b/src/typing/type.ml @@ -269,6 +269,16 @@ and use_t = | OrT of reason * t * t | NotT of reason * t + (** + * A special (internal-only) type used to "reify" a TypeT back down to it's + * value-space type. One way to think of this is as the inverse of `typeof`. + * + * Note that there is no syntax for specifying this type. To surface such a + * type would result in code that cannot be compiled without extensive type + * info (or possibly at all!). + *) + | ReifyTypeT of reason * t_out + (* operation on polymorphic types *) (** SpecializeT(_, cache, targs, tresult) instantiates a polymorphic type with type arguments targs, and flows the result into tresult. If cache is set, @@ -337,6 +347,8 @@ and use_t = | SetNamedExportsT of reason * t SMap.t * t_out | SetStarExportsT of reason * t * t_out + | DebugPrintT of reason + (** Here's to the crazy ones. The misfits. The rebels. The troublemakers. The round pegs in the square holes. **) @@ -718,6 +730,7 @@ and reason_of_use_t = function | AndT (reason, _, _) | OrT (reason, _, _) | NotT (reason, _) + | ReifyTypeT (reason, _) | BecomeT (reason, _) | ReposLowerT (reason, _) -> reason @@ -773,6 +786,7 @@ and reason_of_use_t = function | CJSExtractNamedExportsT (reason, _, _) -> reason | SetNamedExportsT (reason, _, _) -> reason | SetStarExportsT (reason, _, _) -> reason + | DebugPrintT reason -> reason (* helper: we want the tvar id as well *) (* NOTE: uncalled for now, because ids are nondetermistic @@ -928,6 +942,8 @@ and mod_reason_of_use_t f = function | OrT (reason, t1, t2) -> OrT (f reason, t1, t2) | NotT (reason, t) -> NotT (f reason, t) + | ReifyTypeT (reason, t_out) -> ReifyTypeT (f reason, t_out) + | SpecializeT(reason, cache, ts, t) -> SpecializeT (f reason, cache, ts, t) | ThisSpecializeT(reason, this, t) -> ThisSpecializeT (f reason, this, t) @@ -968,6 +984,7 @@ and mod_reason_of_use_t f = function | CJSExtractNamedExportsT (reason, t1, t2) -> CJSExtractNamedExportsT (f reason, t1, t2) | SetNamedExportsT (reason, tmap, t_out) -> SetNamedExportsT(f reason, tmap, t_out) | SetStarExportsT (reason, target_module_t, t_out) -> SetStarExportsT(f reason, target_module_t, t_out) + | DebugPrintT reason -> DebugPrintT (f reason) (* type comparison mod reason *) @@ -1073,6 +1090,7 @@ let string_of_use_ctor = function | AndT _ -> "AndT" | OrT _ -> "OrT" | NotT _ -> "NotT" + | ReifyTypeT _ -> "ReifyTypeT" | SpecializeT _ -> "SpecializeT" | ThisSpecializeT _ -> "ThisSpecializeT" | VarianceCheckT _ -> "VarianceCheckT" @@ -1100,6 +1118,7 @@ let string_of_use_ctor = function | CJSExtractNamedExportsT _ -> "CJSExtractNamedExportsT" | SetNamedExportsT _ -> "SetNamedExportsT" | SetStarExportsT _ -> "SetStarExportsT" + | DebugPrintT _ -> "DebugPrintT" let string_of_binary_test = function | Instanceof -> "instanceof" diff --git a/src/typing/type_inference_js.ml b/src/typing/type_inference_js.ml index 9866e85e3a2..c842f51d3a6 100644 --- a/src/typing/type_inference_js.ml +++ b/src/typing/type_inference_js.ml @@ -4307,7 +4307,13 @@ and jsx cx type_params_map = Ast.JSX.(function { openingElement; closingElement; and jsx_title cx type_params_map openingElement children = Ast.JSX.( let eloc, { Opening.name; attributes; _ } = openingElement in + let facebook_ignore_fbt = + FlowConfig.((get_unsafe ()).options.Opts.facebook_ignore_fbt) + in match name with + | Identifier (_, { Identifier.name }) + when name = "fbt" && facebook_ignore_fbt -> + AnyT.why (mk_reason "" eloc) | Identifier (_, { Identifier.name }) when name = String.capitalize name -> let reason = mk_reason (spf "React element `%s`" name) eloc in @@ -4366,12 +4372,60 @@ and jsx_title cx type_params_map openingElement children = Ast.JSX.( ) | Identifier (_, { Identifier.name }) -> + (** + * For JSX intrinsics, we assume a built-in global + * object type: $JSXIntrinsics. The keys of this object type correspond to + * each JSX intrinsic name, and the type of the value for that key is the + * type signature of the intrinsic ReactComponent. + * + * We use a single object type for this (rather than several individual + * globals) to allow for a default `type $JSXIntrinsics = Object;` that + * ships with lib/core.js. This allows JSX to work out of the box where + * all intrinsics are typed as `any`. Users can then refine the set of + * intrinsics their application uses with a more specific libdef. + *) + let jsx_intrinsics = + Env_js.get_var + ~lookup_mode:Env_js.LookupMode.ForType + cx + "$JSXIntrinsics" + (mk_reason "JSX Intrinsics lookup" eloc) + in + + (** + * Because $JSXIntrinsics is a type alias, extracting a property off of it + * will result in a TypeT as well. This presents a problem because we need + * a value type that can be passed in to React.creatElement; So we first + * reify the TypeT into it's value, then pass this along. + * + * This is a bit strange but it's fallout from the decision to model + * $JSXIntrinsics using a type alias rather than a "value". Modeling with + * a value would be disingenous because no such value really exists (JSX + * intrinsics are just React components that are implicitly defined + * dynamically in library code such as `React.createElement`) + *) + let component_t_reason = + mk_reason (spf "JSX Intrinsic: `%s`" name) eloc + in + let component_t = Flow_js.mk_tvar_where cx component_t_reason (fun t -> + let prop_t = get_prop + ~is_cond:false + cx + component_t_reason + jsx_intrinsics + (component_t_reason, name) + in + Flow_js.flow cx (prop_t, ReifyTypeT(component_t_reason, t)) + ) in + + let attr_map = ref SMap.empty in + let spread = ref None in attributes |> List.iter (function | Opening.Attribute (aloc, { Attribute. - name = Attribute.Identifier (_, { Identifier.name = aname }); - value - }) -> - ignore (match value with + name = Attribute.Identifier (_, { Identifier.name = aname }); + value + }) -> + let attr_type = (match value with | Some (Attribute.Literal (loc, lit)) -> literal cx loc lit | Some (Attribute.ExpressionContainer (_, { @@ -4381,16 +4435,40 @@ and jsx_title cx type_params_map openingElement children = Ast.JSX.( | _ -> (* empty or nonexistent attribute values *) EmptyT.at aloc - ) + ) in + + if not (react_ignore_attribute aname) + then attr_map := !attr_map |> SMap.add aname attr_type | Opening.Attribute _ -> () (* TODO: attributes with namespaced names *) | Opening.SpreadAttribute (aloc, { SpreadAttribute.argument }) -> - () (* TODO: spread attributes *) + let spread_t = expression cx type_params_map argument in + spread := Some spread_t ); - AnyT.t + let reason_props = prefix_reason "props of " component_t_reason in + let o = Flow_js.mk_object_with_map_proto cx reason_props + !attr_map (MixedT reason_props) + in + let o = match !spread with + | None -> o + | Some ex_t -> + let reason_prop = prefix_reason "spread of " (reason_of_t ex_t) in + clone_object_with_excludes cx reason_prop o ex_t react_ignored_attributes + in + (* TODO: children *) + let react = require cx "react" eloc in + let reason = mk_reason (spf "React element: `%s`" name) eloc in + Flow_js.mk_tvar_where cx reason (fun tvar -> + let reason_createElement = mk_reason "property `createElement`" eloc in + Flow_js.flow cx (react, MethodT( + reason, + (reason_createElement, "createElement"), + Flow_js.mk_methodtype react [component_t;o] tvar + )) + ) | _ -> (* TODO? covers namespaced names, member expressions as element names *) diff --git a/src/typing/type_visitor.ml b/src/typing/type_visitor.ml index 67f3983767e..c9e7678f1b4 100644 --- a/src/typing/type_visitor.ml +++ b/src/typing/type_visitor.ml @@ -175,6 +175,7 @@ class ['a] t = object(self) | AndT (_, _, _) | OrT (_, _, _) | NotT (_, _) + | ReifyTypeT (_, _) | SpecializeT (_, _, _, _) | ThisSpecializeT (_, _, _) | VarianceCheckT (_, _, _) @@ -202,6 +203,7 @@ class ['a] t = object(self) | CJSExtractNamedExportsT (_, _, _) | SetNamedExportsT (_, _, _) | SetStarExportsT (_, _, _) + | DebugPrintT (_) -> self#__TODO__ cx acc (* The default behavior here could be fleshed out a bit, to look up the graph, diff --git a/tests/facebook_ignore_fbt_false/.flowconfig b/tests/facebook_ignore_fbt_false/.flowconfig new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/facebook_ignore_fbt_false/facebook_ignore_fbt_false.exp b/tests/facebook_ignore_fbt_false/facebook_ignore_fbt_false.exp new file mode 100644 index 00000000000..94a134f886e --- /dev/null +++ b/tests/facebook_ignore_fbt_false/facebook_ignore_fbt_false.exp @@ -0,0 +1,8 @@ + +main.js:4:2,8: React element: `fbt` +Error: +main.js:4:2,8: ReactElement +This type is incompatible with +main.js:4:11,16: number + +Found 1 error diff --git a/tests/facebook_ignore_fbt_false/main.js b/tests/facebook_ignore_fbt_false/main.js new file mode 100644 index 00000000000..e180aba3d63 --- /dev/null +++ b/tests/facebook_ignore_fbt_false/main.js @@ -0,0 +1,4 @@ +// @flow + +(: ReactElement); +(: number); // Error: ReactElement ~> number diff --git a/tests/facebook_ignore_fbt_true/.flowconfig b/tests/facebook_ignore_fbt_true/.flowconfig new file mode 100644 index 00000000000..d8a3e5e93c3 --- /dev/null +++ b/tests/facebook_ignore_fbt_true/.flowconfig @@ -0,0 +1,2 @@ +[options] +facebook.ignore_fbt=true diff --git a/tests/facebook_ignore_fbt_true/facebook_ignore_fbt_true.exp b/tests/facebook_ignore_fbt_true/facebook_ignore_fbt_true.exp new file mode 100644 index 00000000000..c82edb53368 --- /dev/null +++ b/tests/facebook_ignore_fbt_true/facebook_ignore_fbt_true.exp @@ -0,0 +1,2 @@ + +Found 0 errors diff --git a/tests/facebook_ignore_fbt_true/main.js b/tests/facebook_ignore_fbt_true/main.js new file mode 100644 index 00000000000..f3a1ede29c4 --- /dev/null +++ b/tests/facebook_ignore_fbt_true/main.js @@ -0,0 +1,4 @@ +// @flow + +(: number); +(: string); diff --git a/tests/jsx_intrinsics.builtin/.flowconfig b/tests/jsx_intrinsics.builtin/.flowconfig new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/jsx_intrinsics.builtin/jsx_intrinsics.builtin.exp b/tests/jsx_intrinsics.builtin/jsx_intrinsics.builtin.exp new file mode 100644 index 00000000000..5a2af59e46b --- /dev/null +++ b/tests/jsx_intrinsics.builtin/jsx_intrinsics.builtin.exp @@ -0,0 +1,10 @@ + +main.js:6:10,8:3: property `prop` +Property not found in +main.js:12:26,40: object type + +main.js:12:26,40: property `prop1` +Property not found in +main.js:6:10,8:3: object type + +Found 2 errors diff --git a/tests/jsx_intrinsics.builtin/main.js b/tests/jsx_intrinsics.builtin/main.js new file mode 100644 index 00000000000..a486f17d9ae --- /dev/null +++ b/tests/jsx_intrinsics.builtin/main.js @@ -0,0 +1,15 @@ +// @flow + +var React = require('react'); + +class CustomComponent extends React.Component { + props: { + prop: string + }; +} + +var a: ReactElement = ; +var b: ReactElement = ; // Error: Props<{prop}> ~> Props<{prop1}> + +// Not an error because intrinsics are typed as `any` out of the box! +var c: ReactElement =
; diff --git a/tests/jsx_intrinsics.custom/.flowconfig b/tests/jsx_intrinsics.custom/.flowconfig new file mode 100644 index 00000000000..56ce8c40d88 --- /dev/null +++ b/tests/jsx_intrinsics.custom/.flowconfig @@ -0,0 +1,2 @@ +[libs] +lib diff --git a/tests/jsx_intrinsics.custom/jsx_intrinsics.custom.exp b/tests/jsx_intrinsics.custom/jsx_intrinsics.custom.exp new file mode 100644 index 00000000000..8d29866f3c8 --- /dev/null +++ b/tests/jsx_intrinsics.custom/jsx_intrinsics.custom.exp @@ -0,0 +1,22 @@ + +main.js:6:10,8:3: property `prop` +Property not found in +main.js:12:26,40: object type + +main.js:12:26,40: property `prop1` +Property not found in +main.js:6:10,8:3: object type + +main.js:15:1,15: type parameter `Props` of React element: `div` +Error: +main.js:15:10,11: number +This type is incompatible with +[LIB] lib/jsx.js:4:27,32: string + +main.js:17:47,63: React element: `div` +Error: +main.js:17:31,36: number +This type is incompatible with +[LIB] lib/jsx.js:4:27,32: string + +Found 4 errors diff --git a/tests/jsx_intrinsics.custom/lib/jsx.js b/tests/jsx_intrinsics.custom/lib/jsx.js new file mode 100644 index 00000000000..e01bab582b0 --- /dev/null +++ b/tests/jsx_intrinsics.custom/lib/jsx.js @@ -0,0 +1,6 @@ +type $JSXIntrinsic = Class>; + +type $JSXIntrinsics = { + div: $JSXIntrinsic<{id: string}>, + span: $JSXIntrinsic<{id: string, class: string}>, +}; diff --git a/tests/jsx_intrinsics.custom/main.js b/tests/jsx_intrinsics.custom/main.js new file mode 100644 index 00000000000..556397aabf4 --- /dev/null +++ b/tests/jsx_intrinsics.custom/main.js @@ -0,0 +1,17 @@ +// @flow + +var React = require('react'); + +class CustomComponent extends React.Component { + props: { + prop: string + }; +} + +var a: ReactElement = ; +var b: ReactElement = ; // Error: Props<{prop}> ~> Props<{prop1}> + +
; +
; // Error: (`id` prop) number ~> string +var c: ReactElement =
; +var d: ReactElement =
; // Error: Props<{id:string}> ~> Props<{id:number}> diff --git a/tests/more_react/API.react.js b/tests/more_react/API.react.js index 71e7692f6c5..9cb696f75b9 100644 --- a/tests/more_react/API.react.js +++ b/tests/more_react/API.react.js @@ -5,4 +5,4 @@ app.setProps({y:42}); // error, y:number but foo expects string in App.react app.setState({z:42}); // error, z:number but foo expects string in App.react function bar(x:number) { } -bar(app.props.children); // error, app.props.children: string +bar(app.props.children); // error, app.props.children: mixed ~> number diff --git a/tests/more_react/more_react.exp b/tests/more_react/more_react.exp index a54d69a053e..191114df056 100644 --- a/tests/more_react/more_react.exp +++ b/tests/more_react/more_react.exp @@ -7,17 +7,24 @@ API.react.js:5:5,12: property `setState` Property not found in API.react.js:5:1,3: ReactElement -App.react.js:26:21,21: return -Missing annotation +API.react.js:8:15,22: property `children` +Property not found in +API.react.js:8:5,13: props of React component App.react.js:36:14,14: number This type is incompatible with App.react.js:10:16,21: string +JSX.js:8:3,14: type parameter `Props` of React element `App` +Error: +JSX.js:8:11,12: number +This type is incompatible with +App.react.js:14:37,42: string + propTypes.js:15:1,18:2: React element `D` Error: propTypes.js:9:3,11: property `name` Property not found in propTypes.js:15:1,18:2: type parameter `Props` of React element `D` -Found 5 errors +Found 6 errors diff --git a/tests/qualified/qualified.exp b/tests/qualified/qualified.exp index 610eb95dea1..9e66553299a 100644 --- a/tests/qualified/qualified.exp +++ b/tests/qualified/qualified.exp @@ -3,4 +3,8 @@ qualified.js:4:13,13: number This type is incompatible with qualified.js:4:7,9: C -Found 1 error +qualified.js:10:14,19: string +This type is incompatible with +qualified.js:10:8,10: number + +Found 2 errors diff --git a/tests/qualified/qualified.js b/tests/qualified/qualified.js index b64bf03032a..34233171584 100644 --- a/tests/qualified/qualified.js +++ b/tests/qualified/qualified.js @@ -2,3 +2,9 @@ class C { } var M = { C: C }; var x:M.C = 0; + +type foo = {bar: number}; +type bar = foo.bar; + +var a: bar = 42; +var b: bar = 'asdf'; // Error: string ~> number