From 7cba414a667a2d7407b4ed15eccdc2b29874bff0 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Tue, 2 Apr 2024 20:53:54 -0700 Subject: [PATCH] Calling react components should emit a react-rule error 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 --- src/common/errors/error_codes.ml | 5 ++++- src/typing/debug_js.ml | 2 ++ src/typing/errors/error_message.ml | 16 ++++++++++++++- src/typing/flow_js.ml | 6 ++++++ tests/component_syntax/call.js | 8 ++++++++ tests/component_syntax/component_syntax.exp | 22 +++++++++++++++++---- 6 files changed, 53 insertions(+), 6 deletions(-) create mode 100644 tests/component_syntax/call.js diff --git a/src/common/errors/error_codes.ml b/src/common/errors/error_codes.ml index b1a8a4162d4..02e4a5c6cd9 100644 --- a/src/common/errors/error_codes.ml +++ b/src/common/errors/error_codes.ml @@ -12,6 +12,7 @@ type error_code = | ReactRuleHookIncompatible | ReactRuleHook | ReactRuleRef + | ReactRuleCallComponent | CannotDelete | CannotImplement | CannotInferType @@ -215,7 +216,8 @@ let require_specific : error_code -> bool = function | ReactRuleHookMutation | ReactRuleHookIncompatible | ReactRuleHook - | ReactRuleRef -> + | ReactRuleRef + | ReactRuleCallComponent -> true | _ -> false @@ -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" diff --git a/src/typing/debug_js.ml b/src/typing/debug_js.ml index 492f2f9aca5..3b5af013fa8 100644 --- a/src/typing/debug_js.ml +++ b/src/typing/debug_js.ml @@ -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 diff --git a/src/typing/errors/error_message.ml b/src/typing/errors/error_message.ml index 6b83848ab96..2dda2b7475f 100644 --- a/src/typing/errors/error_message.ml +++ b/src/typing/errors/error_message.ml @@ -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; @@ -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 @@ -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 @@ -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 @@ -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 _ @@ -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 diff --git a/src/typing/flow_js.ml b/src/typing/flow_js.ml index c542b174417..a6fbc597dc1 100644 --- a/src/typing/flow_js.ml +++ b/src/typing/flow_js.ml @@ -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 diff --git a/tests/component_syntax/call.js b/tests/component_syntax/call.js new file mode 100644 index 00000000000..e2366811557 --- /dev/null +++ b/tests/component_syntax/call.js @@ -0,0 +1,8 @@ +component Foo() { + return null; +} +// $FlowFixMe +Foo(); // ERROR, even with fixme + +// $FlowFixMe[react-rule-call-component] +Foo(); // OK with specific fixme diff --git a/tests/component_syntax/component_syntax.exp b/tests/component_syntax/component_syntax.exp index 44ee91bc3d9..96a2064a6f6 100644 --- a/tests/component_syntax/component_syntax.exp +++ b/tests/component_syntax/component_syntax.exp @@ -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] @@ -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 @@ -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 @@ -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(); // error @@ -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