diff --git a/src/common/lints/lints.ml b/src/common/lints/lints.ml index d3337705b23..187aaadc4a5 100644 --- a/src/common/lints/lints.ml +++ b/src/common/lints/lints.ml @@ -20,6 +20,7 @@ type lint_kind = | DeprecatedType | UnsafeGettersSetters | InexactSpread + | UnnecessaryOptionalChain let string_of_sketchy_null_kind = function | SketchyBool -> "sketchy-null-bool" @@ -36,6 +37,7 @@ let string_of_kind = function | DeprecatedType -> "deprecated-type" | UnsafeGettersSetters -> "unsafe-getters-setters" | InexactSpread -> "inexact-spread" + | UnnecessaryOptionalChain -> "unnecessary-optional-chain" let kinds_of_string = function | "sketchy-null" -> Some [ @@ -55,6 +57,7 @@ let kinds_of_string = function | "deprecated-type" -> Some [DeprecatedType] | "unsafe-getters-setters" -> Some [UnsafeGettersSetters] | "inexact-spread" -> Some [InexactSpread] + | "unnecessary-optional-chain" -> Some [UnnecessaryOptionalChain] | _ -> None module LintKind = struct diff --git a/src/common/lints/lints.mli b/src/common/lints/lints.mli index 3dfa47dbc23..7a2efe28dd9 100644 --- a/src/common/lints/lints.mli +++ b/src/common/lints/lints.mli @@ -20,6 +20,7 @@ type lint_kind = | DeprecatedType | UnsafeGettersSetters | InexactSpread + | UnnecessaryOptionalChain val string_of_kind: lint_kind -> string diff --git a/src/typing/context.ml b/src/typing/context.ml index dd8dcf33383..3d156dc4573 100644 --- a/src/typing/context.ml +++ b/src/typing/context.ml @@ -94,7 +94,9 @@ type sig_t = { * it wouldn't be excused. *) mutable exists_excuses: ExistsCheck.t LocMap.t; - mutable test_prop_hits_and_misses: test_prop_hit_or_miss IMap.t + mutable test_prop_hits_and_misses: test_prop_hit_or_miss IMap.t; + + mutable optional_chains_useful: (Reason.t * bool) LocMap.t; } type t = { @@ -176,6 +178,7 @@ let make_sig () = { exists_checks = LocMap.empty; exists_excuses = LocMap.empty; test_prop_hits_and_misses = IMap.empty; + optional_chains_useful = LocMap.empty; } (* create a new context structure. @@ -372,6 +375,7 @@ let clear_intermediates cx = cx.sig_cx.exists_checks <- LocMap.empty; cx.sig_cx.exists_excuses <- LocMap.empty; cx.sig_cx.test_prop_hits_and_misses <- IMap.empty; + cx.sig_cx.optional_chains_useful <- LocMap.empty; () (* Given a sig context, it makes sense to clear the parts that are shared with @@ -404,6 +408,16 @@ let test_prop_get_never_hit cx = | Miss (name, reasons, use_op) -> (name, reasons, use_op)::acc ) [] (IMap.bindings cx.sig_cx.test_prop_hits_and_misses) +let mark_optional_chain cx loc lhs_reason ~useful = + cx.sig_cx.optional_chains_useful <- LocMap.add loc (lhs_reason, useful) ~combine:( + fun (r, u) (_, u') -> (r, u || u') + ) cx.sig_cx.optional_chains_useful + +let unnecessary_optional_chains cx = + LocMap.fold (fun loc (r, useful) acc -> + if useful then acc else (loc, r) :: acc + ) cx.sig_cx.optional_chains_useful [] + (* utils *) let iter_props cx id f = find_props cx id diff --git a/src/typing/context.mli b/src/typing/context.mli index 352724dcd48..02d5cedeaac 100644 --- a/src/typing/context.mli +++ b/src/typing/context.mli @@ -172,6 +172,9 @@ val test_prop_hit: t -> Constraint.ident -> unit val test_prop_miss: t -> Constraint.ident -> string option -> (Reason.t * Reason.t) -> Type.use_op -> unit val test_prop_get_never_hit: t -> (string option * (Reason.t * Reason.t) * Type.use_op) list +val mark_optional_chain: t -> Loc.t -> Reason.t -> useful:bool -> unit +val unnecessary_optional_chains: t -> (Loc.t * Reason.t) list + (* utils *) val iter_props: t -> Type.Properties.id -> (string -> Type.Property.t -> unit) -> unit val has_prop: t -> Type.Properties.id -> string -> bool diff --git a/src/typing/debug_js.ml b/src/typing/debug_js.ml index 30ac20d0d73..cff03c21d00 100644 --- a/src/typing/debug_js.ml +++ b/src/typing/debug_js.ml @@ -788,7 +788,7 @@ and _json_of_use_t_impl json_cx t = Hh_json.( "t_out", _json_of_t json_cx t_out ] - | OptionalChainT (_, uses) -> [ + | OptionalChainT (_, _, uses) -> [ "chain", JSON_Array (Nel.to_list @@ Nel.map (fun (use, tout) -> _json_of_use_t json_cx (apply_opt_use use tout) ) uses); @@ -2769,6 +2769,8 @@ let dump_flow_error = spf "EExperimentalOptionalChaining (%s)" (string_of_loc loc) | EOptionalChainingMethods loc -> spf "EOptionalChainingMethods (%s)" (string_of_loc loc) + | EUnnecessaryOptionalChain (loc, _) -> + spf "EUnnecessaryOptionalChain (%s)" (string_of_loc loc) | EInexactSpread (reason, reason_op) -> spf "EInexactSpread (%s, %s)" (dump_reason cx reason) diff --git a/src/typing/flow_error.ml b/src/typing/flow_error.ml index 7db4aacb386..39fe6b52329 100644 --- a/src/typing/flow_error.ml +++ b/src/typing/flow_error.ml @@ -162,6 +162,7 @@ type error_message = | EInvalidPrototype of reason | EExperimentalOptionalChaining of Loc.t | EOptionalChainingMethods of Loc.t + | EUnnecessaryOptionalChain of Loc.t * reason | EInexactSpread of reason * reason and binding_error = @@ -381,6 +382,7 @@ let util_use_op_of_msg nope util = function | EInvalidPrototype (_) | EExperimentalOptionalChaining _ | EOptionalChainingMethods _ +| EUnnecessaryOptionalChain _ | EInexactSpread _ -> nope @@ -1998,6 +2000,12 @@ let rec error_of_msg ~trace_reasons ~source_file = text "Flow does not yet support method or property calls in optional chains." ] + | EUnnecessaryOptionalChain (loc, lhs_reason) -> + mk_error ~trace_infos ~kind:(LintError Lints.UnnecessaryOptionalChain) loc [ + text "This use of optional chaining ("; code "?."; text ") is unnecessary because "; + ref lhs_reason; text " cannot be null or undefined." + ] + | EInexactSpread (reason, reason_op) -> mk_error ~kind:(LintError Lints.InexactSpread) (loc_of_reason reason) [ text "Cannot determine the type of "; ref reason_op; text " because "; diff --git a/src/typing/flow_js.ml b/src/typing/flow_js.ml index a0d12eb9c67..874fd51a682 100644 --- a/src/typing/flow_js.ml +++ b/src/typing/flow_js.ml @@ -2369,14 +2369,20 @@ let rec __flow cx ((l: Type.t), (u: Type.use_t)) trace = (* optional chaining *) (*********************) - | DefT (r, (NullT | VoidT)), OptionalChainT (_, chain) -> + | DefT (r, (NullT | VoidT)), OptionalChainT (r', lhs_reason, chain) -> + Context.mark_optional_chain cx (loc_of_reason r') lhs_reason ~useful:true; Nel.iter (fun (_, t_out) -> rec_flow_t cx trace (DefT (r, VoidT), t_out)) chain; - | _, OptionalChainT (_, chain) when ( + | _, OptionalChainT (r', lhs_reason, chain) when ( match l with | DefT (_, (MaybeT _ | OptionalT _ | UnionT _ | IntersectionT _)) -> false | _ -> true ) -> + Context.mark_optional_chain cx (loc_of_reason r') lhs_reason ~useful:( + match l with + | DefT (_, (MixedT _ | AnyT | AnyObjT | AnyFunT)) -> true + | _ -> false + ); let lhs_t = ref l in Nel.iter (fun (opt_use, t_out) -> let t_out' = Tvar.mk cx (reason_of_t t_out) in diff --git a/src/typing/merge_js.ml b/src/typing/merge_js.ml index 9bd9ce44cef..dffb1d99e89 100644 --- a/src/typing/merge_js.ml +++ b/src/typing/merge_js.ml @@ -172,6 +172,11 @@ let detect_test_prop_misses cx = Flow_js.add_output cx (Flow_error.EPropNotFound (name, reasons, use_op)) ) misses +let detect_unnecessary_optional_chains cx = + List.iter (fun (loc, lhs_reason) -> + Flow_js.add_output cx (Flow_error.EUnnecessaryOptionalChain (loc, lhs_reason)) + ) (Context.unnecessary_optional_chains cx) + let apply_docblock_overrides (metadata: Context.metadata) docblock_info = let open Context in @@ -343,6 +348,7 @@ let merge_component_strict ~metadata ~lint_severities ~strict_mode ~file_sigs *) detect_sketchy_null_checks cx; detect_test_prop_misses cx; + detect_unnecessary_optional_chains cx; cx, other_cxs diff --git a/src/typing/statement.ml b/src/typing/statement.ml index dd8af4e96cc..9d33e01c182 100644 --- a/src/typing/statement.ml +++ b/src/typing/statement.ml @@ -2978,7 +2978,7 @@ and subscript = Flow_error.(EUnsupportedSyntax (loc, RequireDynamicArgument)); AnyT.at loc ) in - lhs_t, acc, lhs_t + ex, lhs_t, acc, lhs_t | Call { Call.callee = _, Identifier (_, "requireLazy"); @@ -3044,7 +3044,7 @@ and subscript = Flow_error.(EUnsupportedSyntax (loc, RequireLazyDynamicArgument)); AnyT.at loc ) in - lhs_t, acc, lhs_t + ex, lhs_t, acc, lhs_t | Call { Call.callee = (callee_loc, Member { @@ -3059,7 +3059,7 @@ and subscript = let lhs_t = static_method_call_Object cx loc callee_loc prop_loc expr obj_t name targs arguments in - lhs_t, acc, lhs_t + ex, lhs_t, acc, lhs_t | Call { Call.callee = (callee_loc, Member { @@ -3098,7 +3098,7 @@ and subscript = funtype, Some prop_t) ) ) in - lhs_t, acc, lhs_t + ex, lhs_t, acc, lhs_t | Call { Call.callee = (lookup_loc, Member { Member. @@ -3136,7 +3136,7 @@ and subscript = Flow.flow cx (ot, CallElemT (reason_call, reason_lookup, elem_t, funtype)) )) in - lhs_t, acc, lhs_t + ex, lhs_t, acc, lhs_t | Call { Call.callee = (ploc, Super) as callee; @@ -3168,7 +3168,7 @@ and subscript = }) in Flow.flow cx (super, MethodT (use_op, reason, super_reason, propref, funtype, None)) ) in - lhs_t, acc, lhs_t + ex, lhs_t, acc, lhs_t (******************************************) (* See ~/www/static_upstream/core/ *) @@ -3217,7 +3217,7 @@ and subscript = }); ); let lhs_t = VoidT.at loc in - lhs_t, acc, lhs_t + ex, lhs_t, acc, lhs_t | Call { Call.callee; targs; arguments } -> begin match callee with @@ -3239,25 +3239,25 @@ and subscript = let f = expression cx callee in let reason = mk_reason (RFunctionCall (desc_of_t f)) loc in let lhs_t = func_call cx reason ~use_op f targts argts in - lhs_t, acc, lhs_t + ex, lhs_t, acc, lhs_t | NewChain -> let lhs_t = expression cx callee in let reason = mk_reason (RFunctionCall (desc_of_t lhs_t)) loc in let tout = Tvar.mk cx reason in let opt_use = func_call_opt_use reason ~use_op targts argts in - lhs_t, ref (loc, opt_use, tout) :: acc, tout + callee, lhs_t, ref (loc, opt_use, tout) :: acc, tout | ContinueChain -> (* Hacky reason handling *) let reason = mk_reason ROptionalChain loc in let tout = Tvar.mk cx reason in let opt_use = func_call_opt_use reason ~use_op targts argts in let step = ref (loc, opt_use, tout) in - let lhs_t, chain, f = build_chain ~is_cond cx callee (step :: acc) in - let reason = mk_reason (RFunctionCall (desc_of_t f)) loc in + let lhs, lhs_t, chain, f = build_chain ~is_cond cx callee (step :: acc) in + let reason = replace_reason_const (RFunctionCall (desc_of_t f)) reason in let tout = mod_reason_of_t (Fn.const reason) tout in let opt_use = mod_reason_of_opt_use_t (Fn.const reason) opt_use in step := (loc, opt_use, tout); - lhs_t, chain, tout + lhs, lhs_t, chain, tout end | Member { @@ -3280,15 +3280,15 @@ and subscript = Flow.flow cx (tobj, use) ) ) in - lhs_t, acc, lhs_t + ex, lhs_t, acc, lhs_t | NewChain -> let tout = Tvar.mk cx reason in let lhs_t = expression cx _object in - lhs_t, ref (loc, opt_use, tout) :: acc, tout + _object, lhs_t, ref (loc, opt_use, tout) :: acc, tout | ContinueChain -> let tout = Tvar.mk cx reason in - let lhs_t, chain, _ = build_chain ~is_cond cx _object (ref (loc, opt_use, tout) :: acc) in - lhs_t, chain, tout + let lhs, lhs_t, chain, _ = build_chain ~is_cond cx _object (ref (loc, opt_use, tout) :: acc) in + lhs, lhs_t, chain, tout end | Member { @@ -3296,7 +3296,7 @@ and subscript = property = Member.PropertyIdentifier (_, "exports"); _ } -> let lhs_t = get_module_exports cx loc in - lhs_t, acc, lhs_t + ex, lhs_t, acc, lhs_t | Member { Member._object = @@ -3306,7 +3306,7 @@ and subscript = } -> let reason = mk_reason (RCustom "ReactGraphQLMixin") loc in let lhs_t = Flow.get_builtin cx "ReactGraphQLMixin" reason in - lhs_t, acc, lhs_t + ex, lhs_t, acc, lhs_t | Member { Member._object = super_loc, Super; @@ -3335,7 +3335,7 @@ and subscript = Env.add_type_table_info cx ploc id_info; t end in - lhs_t, acc, lhs_t + ex, lhs_t, acc, lhs_t | Member { Member._object; @@ -3355,28 +3355,28 @@ and subscript = | None -> get_prop ~is_cond cx expr_reason ~use_op tobj (prop_reason, name) end in - lhs_t, acc, lhs_t, tobj + ex, lhs_t, acc, lhs_t, tobj | NewChain -> let lhs_t = expression cx _object in let tout = if Type_inference_hooks_js.dispatch_member_hook cx name ploc lhs_t then AnyT.at ploc else Tvar.mk cx expr_reason in let opt_use = get_prop_opt_use ~is_cond expr_reason ~use_op (prop_reason, name) in - lhs_t, ref (loc, opt_use, tout) :: acc, tout, lhs_t + _object, lhs_t, ref (loc, opt_use, tout) :: acc, tout, lhs_t | ContinueChain -> let tout = AnyT.at ploc in let opt_use = get_prop_opt_use ~is_cond expr_reason ~use_op (prop_reason, name) in let step = ref (loc, opt_use, tout) in - let lhs_t, chain, tobj = build_chain ~is_cond cx _object (step :: acc) in + let lhs, lhs_t, chain, tobj = build_chain ~is_cond cx _object (step :: acc) in let tout = if (Type_inference_hooks_js.dispatch_member_hook cx name ploc tobj) then tout else let tout = Tvar.mk cx expr_reason in step := (loc, opt_use, tout); tout in - lhs_t, chain, tout, tobj + lhs, lhs_t, chain, tout, tobj end - |> begin fun (lhs_t, chain, tout, tobj) -> + |> begin fun (lhs, lhs_t, chain, tout, tobj) -> let id_info = name, tout, Type_table.PropertyAccess tobj in Env.add_type_table_info cx ploc id_info; - lhs_t, chain, tout + lhs, lhs_t, chain, tout end | Member { @@ -3397,37 +3397,37 @@ and subscript = then AnyT.at ploc else get_private_field cx expr_reason ~use_op tobj name ) in - lhs_t, acc, lhs_t + ex, lhs_t, acc, lhs_t | NewChain -> let lhs_t = expression cx _object in let tout = if Type_inference_hooks_js.dispatch_member_hook cx name ploc lhs_t then AnyT.at ploc else Tvar.mk cx expr_reason in let opt_use = get_private_field_opt_use expr_reason ~use_op name in - lhs_t, ref (loc, opt_use, tout) :: acc, tout + _object, lhs_t, ref (loc, opt_use, tout) :: acc, tout | ContinueChain -> let tout = AnyT.at ploc in let opt_use = get_private_field_opt_use expr_reason ~use_op name in let step = ref (loc, opt_use, tout) in - let lhs_t, chain, tobj = build_chain ~is_cond cx _object (step :: acc) in + let lhs, lhs_t, chain, tobj = build_chain ~is_cond cx _object (step :: acc) in let tout = if (Type_inference_hooks_js.dispatch_member_hook cx name ploc tobj) then tout else let tout = Tvar.mk cx expr_reason in step := (loc, opt_use, tout); tout in - lhs_t, chain, tout + lhs, lhs_t, chain, tout end - |> begin fun (lhs_t, chain, t) -> + |> begin fun (lhs, lhs_t, chain, t) -> (* TODO use PropertyAccess *) let id_info = name, t, Type_table.Other in Env.add_type_table_info cx ploc id_info; - lhs_t, chain, t + lhs, lhs_t, chain, t end | _ -> let lhs_t = expression cx ex in - lhs_t, acc, lhs_t + ex, lhs_t, acc, lhs_t in fun ~is_cond cx ex -> - let lhs_t, chain, tout = build_chain ~is_cond cx ex [] in + let lhs, lhs_t, chain, tout = build_chain ~is_cond cx ex [] in begin match chain with | [] -> () | hd :: tl -> @@ -3438,7 +3438,8 @@ and subscript = use, t ) (hd, tl) in let reason = mk_reason ROptionalChain hd_loc in - Flow.flow cx (lhs_t, OptionalChainT (reason, chain)); + let lhs_reason = mk_expression_reason lhs in + Flow.flow cx (lhs_t, OptionalChainT (reason, lhs_reason, chain)); end; tout diff --git a/src/typing/type.ml b/src/typing/type.ml index 201ff86831f..dec317563b0 100644 --- a/src/typing/type.ml +++ b/src/typing/type.ml @@ -566,7 +566,7 @@ module rec TypeTerm : sig | IdxUnwrap of reason * t_out | IdxUnMaybeifyT of reason * t_out - | OptionalChainT of reason * (opt_use_t * t_out) Nel.t + | OptionalChainT of reason * reason * (opt_use_t * t_out) Nel.t (* Function predicate uses *) @@ -2144,7 +2144,7 @@ end = struct | ObjSealT (reason, _) -> reason | ObjTestProtoT (reason, _) -> reason | ObjTestT (reason, _, _) -> reason - | OptionalChainT (reason, _) -> reason + | OptionalChainT (reason, _, _) -> reason | OrT (reason, _, _) -> reason | PredicateT (_, t) -> reason_of_t t | ReactKitT (_, reason, _) -> reason @@ -2308,7 +2308,7 @@ end = struct | ObjSealT (reason, t) -> ObjSealT (f reason, t) | ObjTestProtoT (reason, t) -> ObjTestProtoT (f reason, t) | ObjTestT (reason, t1, t2) -> ObjTestT (f reason, t1, t2) - | OptionalChainT (reason, us) -> OptionalChainT (f reason, us) + | OptionalChainT (reason, lhs_reason, us) -> OptionalChainT (f reason, lhs_reason, us) | OrT (reason, t1, t2) -> OrT (f reason, t1, t2) | PredicateT (pred, t) -> PredicateT (pred, mod_reason_of_t f t) | ReactKitT (use_op, reason, tool) -> ReactKitT (use_op, f reason, tool) @@ -2448,7 +2448,7 @@ end = struct | SentinelPropTestT (_, _, _, _, _, _) | IdxUnwrap (_, _) | IdxUnMaybeifyT (_, _) - | OptionalChainT (_, _) + | OptionalChainT (_, _, _) | CallLatentPredT (_, _, _, _, _) | CallOpenPredT (_, _, _, _, _) | SubstOnPredT (_, _, _) diff --git a/src/typing/type_mapper.ml b/src/typing/type_mapper.ml index 12ef76ce56f..e4450f2dc59 100644 --- a/src/typing/type_mapper.ml +++ b/src/typing/type_mapper.ml @@ -824,13 +824,13 @@ class ['a] t = object(self) let t'' = self#type_ cx map_cx t' in if t'' == t' then t else IdxUnMaybeifyT (r, t'') - | OptionalChainT (r, uses) -> + | OptionalChainT (r, lhs_r, uses) -> let uses' = Nel.map (fun (use, tout) -> self#opt_use_type cx map_cx use, self#type_ cx map_cx tout ) uses in if uses' == uses then t - else OptionalChainT (r, uses') + else OptionalChainT (r, lhs_r, uses') | CallLatentPredT (r, b, i, t1, t2) -> let t1' = self#type_ cx map_cx t1 in let t2' = self#type_ cx map_cx t2 in diff --git a/src/typing/type_visitor.ml b/src/typing/type_visitor.ml index f4618285274..5d96c0a553e 100644 --- a/src/typing/type_visitor.ml +++ b/src/typing/type_visitor.ml @@ -550,7 +550,7 @@ class ['a] t = object(self) | IdxUnMaybeifyT (_, tout) -> self#type_ cx pole_TODO acc tout - | OptionalChainT (_, uses) -> + | OptionalChainT (_, _, uses) -> Nel.fold_left (fun acc (use, tout) -> self#use_type_ cx acc (apply_opt_use use tout) ) acc uses diff --git a/tests/optional_chaining/.flowconfig b/tests/optional_chaining/.flowconfig index add1e2aecff..0dc93a79b98 100644 --- a/tests/optional_chaining/.flowconfig +++ b/tests/optional_chaining/.flowconfig @@ -1,2 +1,5 @@ [options] esproposal.optional_chaining=enable + +[lints] +unnecessary-optional-chain=error diff --git a/tests/optional_chaining/lhs_types.js b/tests/optional_chaining/lhs_types.js new file mode 100644 index 00000000000..1e3305f5b10 --- /dev/null +++ b/tests/optional_chaining/lhs_types.js @@ -0,0 +1,17 @@ +// @flow + +type Foo = {foo: number}; + +declare var x: Foo; +declare var mixed: mixed; +declare var any: any; +declare var empty: empty; +declare var maybe: ?Foo; +declare var union: Foo | null | void; + +(x?.foo: ?number); // no error, lint +(mixed?.foo: ?number); // error, no lint +(any?.foo: ?number); // no error, no lint +(empty?.foo: ?number); // no error, no lint +(maybe?.foo: ?number); // no error, no lint +(union?.foo: ?number); // no error, no lint diff --git a/tests/optional_chaining/optional_chaining.exp b/tests/optional_chaining/optional_chaining.exp index b132f7c23d5..8c4e5f40dc1 100644 --- a/tests/optional_chaining/optional_chaining.exp +++ b/tests/optional_chaining/optional_chaining.exp @@ -37,6 +37,21 @@ References: ^^^^^ [2] +Error -------------------------------------------------------------------------------------- computed_properties.js:13:2 + +This use of optional chaining (`?.`) is unnecessary because `x2` [1] cannot be null or undefined. +(`unnecessary-optional-chain`) + + computed_properties.js:13:2 + 13| (x2?.["foo"]: empty); + ^^^^^^^^^^^ + +References: + computed_properties.js:13:2 + 13| (x2?.["foo"]: empty); + ^^ [1] + + Error -------------------------------------------------------------------------------------- computed_properties.js:15:2 Cannot cast `y1?.["bar"]?.["foo"]` to empty because: @@ -59,6 +74,21 @@ References: ^^^^^^ [3] +Error -------------------------------------------------------------------------------------- computed_properties.js:16:2 + +This use of optional chaining (`?.`) is unnecessary because `y2` [1] cannot be null or undefined. +(`unnecessary-optional-chain`) + + computed_properties.js:16:2 + 16| (y2?.["bar"]?.["foo"]: empty); + ^^^^^^^^^^^ + +References: + computed_properties.js:16:2 + 16| (y2?.["bar"]?.["foo"]: empty); + ^^ [1] + + Error -------------------------------------------------------------------------------------- computed_properties.js:16:2 Cannot cast `y2?.["bar"]?.["foo"]` to empty because number [1] is incompatible with empty [2]. @@ -76,6 +106,21 @@ References: ^^^^^ [2] +Error -------------------------------------------------------------------------------------- computed_properties.js:16:2 + +This use of optional chaining (`?.`) is unnecessary because `y2?.["bar"]` [1] cannot be null or undefined. +(`unnecessary-optional-chain`) + + computed_properties.js:16:2 + 16| (y2?.["bar"]?.["foo"]: empty); + ^^^^^^^^^^^^^^^^^^^^ + +References: + computed_properties.js:16:2 + 16| (y2?.["bar"]?.["foo"]: empty); + ^^^^^^^^^^^ [1] + + Error -------------------------------------------------------------------------------------- computed_properties.js:17:2 Cannot cast `y1?.["baz"]?.["foo"]` to empty because: @@ -102,6 +147,21 @@ References: ^^^^^^ [4] +Error -------------------------------------------------------------------------------------- computed_properties.js:18:2 + +This use of optional chaining (`?.`) is unnecessary because `y2` [1] cannot be null or undefined. +(`unnecessary-optional-chain`) + + computed_properties.js:18:2 + 18| (y2?.["baz"]?.["foo"]: empty); + ^^^^^^^^^^^ + +References: + computed_properties.js:18:2 + 18| (y2?.["baz"]?.["foo"]: empty); + ^^ [1] + + Error -------------------------------------------------------------------------------------- computed_properties.js:18:2 Cannot cast `y2?.["baz"]?.["foo"]` to empty because: @@ -146,6 +206,21 @@ References: ^^^^^^ [3] +Error -------------------------------------------------------------------------------------- computed_properties.js:21:2 + +This use of optional chaining (`?.`) is unnecessary because `y2` [1] cannot be null or undefined. +(`unnecessary-optional-chain`) + + computed_properties.js:21:2 + 21| (y2?.["bar"]["foo"]: empty); + ^^^^^^^^^^^ + +References: + computed_properties.js:21:2 + 21| (y2?.["bar"]["foo"]: empty); + ^^ [1] + + Error -------------------------------------------------------------------------------------- computed_properties.js:21:2 Cannot cast `y2?.["bar"]["foo"]` to empty because number [1] is incompatible with empty [2]. @@ -199,6 +274,21 @@ References: ^^ [1] +Error -------------------------------------------------------------------------------------- computed_properties.js:23:2 + +This use of optional chaining (`?.`) is unnecessary because `y2` [1] cannot be null or undefined. +(`unnecessary-optional-chain`) + + computed_properties.js:23:2 + 23| (y2?.["baz"]["foo"]: empty); + ^^^^^^^^^^^ + +References: + computed_properties.js:23:2 + 23| (y2?.["baz"]["foo"]: empty); + ^^ [1] + + Error -------------------------------------------------------------------------------------- computed_properties.js:23:2 Cannot cast `y2?.["baz"]["foo"]` to empty because number [1] is incompatible with empty [2]. @@ -261,6 +351,21 @@ References: ^^^^^ [2] +Error -------------------------------------------------------------------------------------- computed_properties.js:25:2 + +This use of optional chaining (`?.`) is unnecessary because `y1["bar"]` [1] cannot be null or undefined. +(`unnecessary-optional-chain`) + + computed_properties.js:25:2 + 25| (y1["bar"]?.["foo"]: empty); + ^^^^^^^^^^^^^^^^^^ + +References: + computed_properties.js:25:2 + 25| (y1["bar"]?.["foo"]: empty); + ^^^^^^^^^ [1] + + Error -------------------------------------------------------------------------------------- computed_properties.js:26:2 Cannot cast `y2["bar"]?.["foo"]` to empty because number [1] is incompatible with empty [2]. @@ -278,6 +383,21 @@ References: ^^^^^ [2] +Error -------------------------------------------------------------------------------------- computed_properties.js:26:2 + +This use of optional chaining (`?.`) is unnecessary because `y2["bar"]` [1] cannot be null or undefined. +(`unnecessary-optional-chain`) + + computed_properties.js:26:2 + 26| (y2["bar"]?.["foo"]: empty); + ^^^^^^^^^^^^^^^^^^ + +References: + computed_properties.js:26:2 + 26| (y2["bar"]?.["foo"]: empty); + ^^^^^^^^^ [1] + + Error -------------------------------------------------------------------------------------- computed_properties.js:27:2 Cannot get `y1["baz"]` because an indexer property is missing in null or undefined [1]. @@ -384,6 +504,21 @@ References: ^^^^^ [2] +Error -------------------------------------------------------------------------------------- computed_properties.js:31:3 + +This use of optional chaining (`?.`) is unnecessary because `y2` [1] cannot be null or undefined. +(`unnecessary-optional-chain`) + + computed_properties.js:31:3 + 31| ((y2?.["bar"])["foo"]: empty); + ^^^^^^^^^^^ + +References: + computed_properties.js:31:3 + 31| ((y2?.["bar"])["foo"]: empty); + ^^ [1] + + Error -------------------------------------------------------------------------------------- computed_properties.js:32:2 Cannot cast `y1?.["baz"]["foo"]` to empty because number [1] is incompatible with empty [2]. @@ -451,6 +586,21 @@ References: ^^ [1] +Error -------------------------------------------------------------------------------------- computed_properties.js:33:3 + +This use of optional chaining (`?.`) is unnecessary because `y2` [1] cannot be null or undefined. +(`unnecessary-optional-chain`) + + computed_properties.js:33:3 + 33| ((y2?.["baz"])["foo"]: empty); + ^^^^^^^^^^^ + +References: + computed_properties.js:33:3 + 33| ((y2?.["baz"])["foo"]: empty); + ^^ [1] + + Error -------------------------------------------------------------------------------------------- function_calls.js:8:2 Cannot cast `x1?.()` to empty because: @@ -490,6 +640,50 @@ References: ^^^^^ [2] +Error -------------------------------------------------------------------------------------------- function_calls.js:9:2 + +This use of optional chaining (`?.`) is unnecessary because `x2` [1] cannot be null or undefined. +(`unnecessary-optional-chain`) + + function_calls.js:9:2 + 9| (x2?.(): empty) + ^^^^^^ + +References: + function_calls.js:9:2 + 9| (x2?.(): empty) + ^^ [1] + + +Error ------------------------------------------------------------------------------------------------ lhs_types.js:12:2 + +This use of optional chaining (`?.`) is unnecessary because `x` [1] cannot be null or undefined. +(`unnecessary-optional-chain`) + + lhs_types.js:12:2 + 12| (x?.foo: ?number); // no error, lint + ^^^^^^ + +References: + lhs_types.js:12:2 + 12| (x?.foo: ?number); // no error, lint + ^ [1] + + +Error ------------------------------------------------------------------------------------------------ lhs_types.js:13:2 + +Cannot get `mixed?.foo` because property `foo` is missing in mixed [1]. + + lhs_types.js:13:2 + 13| (mixed?.foo: ?number); // error, no lint + ^^^^^^^^^^ + +References: + lhs_types.js:6:20 + 6| declare var mixed: mixed; + ^^^^^ [1] + + Error ---------------------------------------------------------------------------------------------- method_calls.js:9:2 Flow does not yet support method or property calls in optional chains. @@ -673,6 +867,21 @@ References: ^^^^^ [2] +Error ---------------------------------------------------------------------------------------- private_properties.js:6:6 + +This use of optional chaining (`?.`) is unnecessary because `this` [1] cannot be null or undefined. +(`unnecessary-optional-chain`) + + private_properties.js:6:6 + 6| (this?.#foo: empty); + ^^^^^^^^^^ + +References: + private_properties.js:6:6 + 6| (this?.#foo: empty); + ^^^^ [1] + + Error --------------------------------------------------------------------------------------- private_properties.js:14:6 Cannot cast `this?.#bar` to empty because `X` [1] is incompatible with empty [2]. @@ -690,6 +899,21 @@ References: ^^^^^ [2] +Error --------------------------------------------------------------------------------------- private_properties.js:14:6 + +This use of optional chaining (`?.`) is unnecessary because `this` [1] cannot be null or undefined. +(`unnecessary-optional-chain`) + + private_properties.js:14:6 + 14| (this?.#bar: empty); + ^^^^^^^^^^ + +References: + private_properties.js:14:6 + 14| (this?.#bar: empty); + ^^^^ [1] + + Error --------------------------------------------------------------------------------------- private_properties.js:15:6 Cannot cast `this?.#baz` to empty because: @@ -712,6 +936,21 @@ References: ^ [3] +Error --------------------------------------------------------------------------------------- private_properties.js:15:6 + +This use of optional chaining (`?.`) is unnecessary because `this` [1] cannot be null or undefined. +(`unnecessary-optional-chain`) + + private_properties.js:15:6 + 15| (this?.#baz: empty); + ^^^^^^^^^^ + +References: + private_properties.js:15:6 + 15| (this?.#baz: empty); + ^^^^ [1] + + Error ------------------------------------------------------------------------------------------- static_members.js:12:2 Cannot cast `x1?.foo` to empty because: @@ -751,6 +990,21 @@ References: ^^^^^ [2] +Error ------------------------------------------------------------------------------------------- static_members.js:13:2 + +This use of optional chaining (`?.`) is unnecessary because `x2` [1] cannot be null or undefined. +(`unnecessary-optional-chain`) + + static_members.js:13:2 + 13| (x2?.foo: empty); + ^^^^^^^ + +References: + static_members.js:13:2 + 13| (x2?.foo: empty); + ^^ [1] + + Error ------------------------------------------------------------------------------------------- static_members.js:15:2 Cannot cast `y1?.bar?.foo` to empty because: @@ -773,6 +1027,21 @@ References: ^^^^^^ [3] +Error ------------------------------------------------------------------------------------------- static_members.js:16:2 + +This use of optional chaining (`?.`) is unnecessary because `y2` [1] cannot be null or undefined. +(`unnecessary-optional-chain`) + + static_members.js:16:2 + 16| (y2?.bar?.foo: empty); + ^^^^^^^ + +References: + static_members.js:16:2 + 16| (y2?.bar?.foo: empty); + ^^ [1] + + Error ------------------------------------------------------------------------------------------- static_members.js:16:2 Cannot cast `y2?.bar?.foo` to empty because number [1] is incompatible with empty [2]. @@ -790,6 +1059,21 @@ References: ^^^^^ [2] +Error ------------------------------------------------------------------------------------------- static_members.js:16:2 + +This use of optional chaining (`?.`) is unnecessary because `y2?.bar` [1] cannot be null or undefined. +(`unnecessary-optional-chain`) + + static_members.js:16:2 + 16| (y2?.bar?.foo: empty); + ^^^^^^^^^^^^ + +References: + static_members.js:16:2 + 16| (y2?.bar?.foo: empty); + ^^^^^^^ [1] + + Error ------------------------------------------------------------------------------------------- static_members.js:17:2 Cannot cast `y1?.baz?.foo` to empty because: @@ -816,6 +1100,21 @@ References: ^^^^^^ [4] +Error ------------------------------------------------------------------------------------------- static_members.js:18:2 + +This use of optional chaining (`?.`) is unnecessary because `y2` [1] cannot be null or undefined. +(`unnecessary-optional-chain`) + + static_members.js:18:2 + 18| (y2?.baz?.foo: empty); + ^^^^^^^ + +References: + static_members.js:18:2 + 18| (y2?.baz?.foo: empty); + ^^ [1] + + Error ------------------------------------------------------------------------------------------- static_members.js:18:2 Cannot cast `y2?.baz?.foo` to empty because: @@ -860,6 +1159,21 @@ References: ^^^^^^ [3] +Error ------------------------------------------------------------------------------------------- static_members.js:21:2 + +This use of optional chaining (`?.`) is unnecessary because `y2` [1] cannot be null or undefined. +(`unnecessary-optional-chain`) + + static_members.js:21:2 + 21| (y2?.bar.foo: empty); + ^^^^^^^ + +References: + static_members.js:21:2 + 21| (y2?.bar.foo: empty); + ^^ [1] + + Error ------------------------------------------------------------------------------------------- static_members.js:21:2 Cannot cast `y2?.bar.foo` to empty because number [1] is incompatible with empty [2]. @@ -913,6 +1227,21 @@ References: ^^ [1] +Error ------------------------------------------------------------------------------------------- static_members.js:23:2 + +This use of optional chaining (`?.`) is unnecessary because `y2` [1] cannot be null or undefined. +(`unnecessary-optional-chain`) + + static_members.js:23:2 + 23| (y2?.baz.foo: empty); + ^^^^^^^ + +References: + static_members.js:23:2 + 23| (y2?.baz.foo: empty); + ^^ [1] + + Error ------------------------------------------------------------------------------------------- static_members.js:23:2 Cannot cast `y2?.baz.foo` to empty because number [1] is incompatible with empty [2]. @@ -975,6 +1304,21 @@ References: ^^^^^ [2] +Error ------------------------------------------------------------------------------------------- static_members.js:25:2 + +This use of optional chaining (`?.`) is unnecessary because `y1.bar` [1] cannot be null or undefined. +(`unnecessary-optional-chain`) + + static_members.js:25:2 + 25| (y1.bar?.foo: empty); + ^^^^^^^^^^^ + +References: + static_members.js:25:2 + 25| (y1.bar?.foo: empty); + ^^^^^^ [1] + + Error ------------------------------------------------------------------------------------------- static_members.js:26:2 Cannot cast `y2.bar?.foo` to empty because number [1] is incompatible with empty [2]. @@ -992,6 +1336,21 @@ References: ^^^^^ [2] +Error ------------------------------------------------------------------------------------------- static_members.js:26:2 + +This use of optional chaining (`?.`) is unnecessary because `y2.bar` [1] cannot be null or undefined. +(`unnecessary-optional-chain`) + + static_members.js:26:2 + 26| (y2.bar?.foo: empty); + ^^^^^^^^^^^ + +References: + static_members.js:26:2 + 26| (y2.bar?.foo: empty); + ^^^^^^ [1] + + Error ------------------------------------------------------------------------------------------- static_members.js:27:2 Cannot get `y1.baz` because property `baz` is missing in null or undefined [1]. @@ -1098,6 +1457,21 @@ References: ^^^^^ [2] +Error ------------------------------------------------------------------------------------------- static_members.js:31:3 + +This use of optional chaining (`?.`) is unnecessary because `y2` [1] cannot be null or undefined. +(`unnecessary-optional-chain`) + + static_members.js:31:3 + 31| ((y2?.bar).foo: empty); + ^^^^^^^ + +References: + static_members.js:31:3 + 31| ((y2?.bar).foo: empty); + ^^ [1] + + Error ------------------------------------------------------------------------------------------- static_members.js:32:2 Cannot cast `y1?.baz.foo` to empty because number [1] is incompatible with empty [2]. @@ -1165,5 +1539,20 @@ References: ^^ [1] +Error ------------------------------------------------------------------------------------------- static_members.js:33:3 + +This use of optional chaining (`?.`) is unnecessary because `y2` [1] cannot be null or undefined. +(`unnecessary-optional-chain`) + + static_members.js:33:3 + 33| ((y2?.baz).foo: empty); + ^^^^^^^ + +References: + static_members.js:33:3 + 33| ((y2?.baz).foo: empty); + ^^ [1] + + -Found 93 errors +Found 119 errors