Skip to content

Commit

Permalink
Calling react components should emit a react-rule error
Browse files Browse the repository at this point in the history
Summary:
AbstractComponentT ~> CallT errors but with a `not-a-function` error. Instead, let's emit a `react-rule`-prefixed error to go along with our other react-related rules.

Changelog: [internal]

Reviewed By: SamChou19815

Differential Revision: D54601073

fbshipit-source-id: bf3b8d7a070c184266d2be7429b839c2e86df6dc
  • Loading branch information
jbrown215 authored and facebook-github-bot committed Apr 3, 2024
1 parent c0787f4 commit 7cba414
Show file tree
Hide file tree
Showing 6 changed files with 53 additions and 6 deletions.
5 changes: 4 additions & 1 deletion src/common/errors/error_codes.ml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ type error_code =
| ReactRuleHookIncompatible
| ReactRuleHook
| ReactRuleRef
| ReactRuleCallComponent
| CannotDelete
| CannotImplement
| CannotInferType
Expand Down Expand Up @@ -215,7 +216,8 @@ let require_specific : error_code -> bool = function
| ReactRuleHookMutation
| ReactRuleHookIncompatible
| ReactRuleHook
| ReactRuleRef ->
| ReactRuleRef
| ReactRuleCallComponent ->
true
| _ -> false

Expand All @@ -225,6 +227,7 @@ let string_of_code : error_code -> string = function
| ReactRuleHookIncompatible -> "react-rule-hook-incompatible"
| ReactRuleHook -> "react-rule-hook"
| ReactRuleRef -> "react-rule-unsafe-ref"
| ReactRuleCallComponent -> "react-rule-call-component"
| AmbiguousObjectType -> "ambiguous-object-type"
| CannotDelete -> "cannot-delete"
| CannotImplement -> "cannot-implement"
Expand Down
2 changes: 2 additions & 0 deletions src/typing/debug_js.ml
Original file line number Diff line number Diff line change
Expand Up @@ -1965,6 +1965,8 @@ let dump_error_message =
| EUnionOptimization { loc; _ } -> spf "EUnionOptimization (%s)" (string_of_aloc loc)
| EUnionOptimizationOnNonUnion { loc; _ } ->
spf "EUnionOptimizationOnNonUnion (%s)" (string_of_aloc loc)
| ECannotCallReactComponent { reason } ->
spf "ECannotCallReactComponent (%s)" (dump_reason cx reason)

module Verbose = struct
let print_if_verbose_lazy
Expand Down
16 changes: 15 additions & 1 deletion src/typing/errors/error_message.ml
Original file line number Diff line number Diff line change
Expand Up @@ -704,6 +704,7 @@ and 'loc t' =
loc: 'loc;
arg: 'loc virtual_reason;
}
| ECannotCallReactComponent of { reason: 'loc virtual_reason }

and 'loc null_write = {
null_loc: 'loc;
Expand Down Expand Up @@ -1512,6 +1513,7 @@ let rec map_loc_of_error_message (f : 'a -> 'b) : 'a t' -> 'b t' =
EUnionOptimization { loc = f loc; kind }
| EUnionOptimizationOnNonUnion { loc; arg } ->
EUnionOptimizationOnNonUnion { loc = f loc; arg = map_reason arg }
| ECannotCallReactComponent { reason } -> ECannotCallReactComponent { reason = map_reason reason }

let desc_of_reason r = Reason.desc_of_reason ~unwrap:(is_scalar_reason r) r

Expand Down Expand Up @@ -1781,7 +1783,8 @@ let util_use_op_of_msg nope util = function
| EMissingPlatformSupport _
| EUnionPartialOptimizationNonUniqueKey _
| EUnionOptimization _
| EUnionOptimizationOnNonUnion _ ->
| EUnionOptimizationOnNonUnion _
| ECannotCallReactComponent _ ->
nope

(* Not all messages (i.e. those whose locations are based on use_ops) have locations that can be
Expand Down Expand Up @@ -1977,6 +1980,7 @@ let loc_of_msg : 'loc t' -> 'loc option = function
| EEnumMemberDuplicateValue { loc; _ } -> Some loc
| ESpeculationAmbiguous { reason; _ } -> Some (loc_of_reason reason)
| EBuiltinLookupFailed { reason; _ } -> Some (loc_of_reason reason)
| ECannotCallReactComponent { reason } -> Some (loc_of_reason reason)
| EPlatformSpecificImplementationModuleLookupFailed { loc; _ } -> Some loc
| EDuplicateClassMember { loc; _ } -> Some loc
| EEmptyArrayNoProvider { loc } -> Some loc
Expand Down Expand Up @@ -5682,6 +5686,15 @@ let friendly_message_of_msg loc_of_aloc msg =
[text "Invalid use of $Flow$EnforceOptimized on non-union type "; ref arg; text "."]
in
Normal { features }
| ECannotCallReactComponent { reason } ->
let features =
[
text "Cannot call ";
ref reason;
text " because React components cannot be called. Use JSX instead.";
]
in
Normal { features }

let defered_in_speculation = function
| EUntypedTypeImport _
Expand Down Expand Up @@ -6028,3 +6041,4 @@ let error_code_of_message err : error_code option =
| EUnionPartialOptimizationNonUniqueKey _ -> Some UnionPartiallyOptimizableNonUniqueKeys
| EUnionOptimization _ -> Some UnionUnoptimizable
| EUnionOptimizationOnNonUnion _ -> Some UnionUnoptimizable
| ECannotCallReactComponent _ -> Some ReactRuleCallComponent
6 changes: 6 additions & 0 deletions src/typing/flow_js.ml
Original file line number Diff line number Diff line change
Expand Up @@ -2977,6 +2977,12 @@ struct
) ->
let statics = get_builtin_type cx ~trace r "React$AbstractComponentStatics" in
rec_flow cx trace (statics, u)
(* Components can never be called *)
| ( DefT (r, ReactAbstractComponentT _),
CallT { use_op; reason; call_action = Funcalltype { call_tout; _ }; _ }
) ->
add_output cx ~trace (Error_message.ECannotCallReactComponent { reason = r });
rec_flow_t cx trace ~use_op (AnyT.error reason, OpenT call_tout)
(* Render Type Promotion *)
(* A named AbstractComponent is turned into its corresponding render type *)
| ( DefT
Expand Down
8 changes: 8 additions & 0 deletions tests/component_syntax/call.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
component Foo() {
return null;
}
// $FlowFixMe
Foo(); // ERROR, even with fixme

// $FlowFixMe[react-rule-call-component]
Foo(); // OK with specific fixme
22 changes: 18 additions & 4 deletions tests/component_syntax/component_syntax.exp
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,20 @@ References:
^ [1]


Error ------------------------------------------------------------------------------------------------------ call.js:5:1

Cannot call component Foo [1] because React components cannot be called. Use JSX instead. [react-rule-call-component]

call.js:5:1
5| Foo(); // ERROR, even with fixme
^^^

References:
call.js:1:1
1| component Foo() {
^^^^^^^^^^^^^^^ [1]


Error ------------------------------------------------------------------------------- component_in_type_position.js:5:16

Cannot assign `Foo` to `x` because component Foo [1] is incompatible with `Foo` element [2]. [incompatible-type]
Expand Down Expand Up @@ -1211,7 +1225,7 @@ Components may not be nested directly within other components.

Error --------------------------------------------------------------------------------------------------- errors.js:45:1

Cannot call `Baz` because component Baz [1] is not a function. [not-a-function]
Cannot call component Baz [1] because React components cannot be called. Use JSX instead. [react-rule-call-component]

errors.js:45:1
45| Baz(); // error
Expand All @@ -1225,7 +1239,7 @@ References:

Error --------------------------------------------------------------------------------------------------- errors.js:48:1

Cannot call `Poly` because component Poly [1] is not a function. [not-a-function]
Cannot call component Poly [1] because React components cannot be called. Use JSX instead. [react-rule-call-component]

errors.js:48:1
48| Poly(); // error
Expand All @@ -1239,7 +1253,7 @@ References:

Error --------------------------------------------------------------------------------------------------- errors.js:49:1

Cannot call `Poly` because component Poly [1] is not a function. [not-a-function]
Cannot call component Poly [1] because React components cannot be called. Use JSX instead. [react-rule-call-component]

errors.js:49:1
49| Poly<number>(); // error
Expand Down Expand Up @@ -3215,7 +3229,7 @@ Strict mode function may not have duplicate parameter names



Found 214 errors
Found 215 errors

Only showing the most relevant union/intersection branches.
To see all branches, re-run Flow with --show-all-branches

0 comments on commit 7cba414

Please sign in to comment.