Add lint for invalid XHP class enum values
[hiphop-php.git] / hphp / hack / src / lints / lints_errors.ml
blob4510972101d73091971aa6345c204ab7cb6b2bb2
1 (*
2 * Copyright (c) Facebook, Inc. and its affiliates.
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the "hack" directory of this source tree.
7 *)
9 open Hh_prelude
10 module Codes = Lints_codes.Codes
11 open Lints_core
12 module Lints = Lints_core
14 let clone_use p =
15 Lints.add
16 Codes.clone_use
17 Lint_advice
19 ("Objects created with `clone` will have references to shared "
20 ^ "deep objects. Prefer to implement your own explicit copy "
21 ^ "method to ensure the semantics you want. See "
22 ^ "http://php.net/manual/en/language.oop5.cloning.php")
24 let deprecated p msg = Lints.add Codes.deprecated Lint_warning p msg
26 let include_use p msg = Lints.add Codes.include_use Lint_error p msg
28 let if_literal p msg = Lints.add Codes.if_literal Lint_warning p msg
30 let await_in_loop p =
31 Lints.add
32 Codes.await_in_loop
33 Lint_warning
35 ("Do not use `await` in a loop. It almost always incurs "
36 ^ "non-obvious serial fetching that is easy to miss. "
37 ^ "See https://fburl.com/awaitinloop for more information.")
39 let loop_variable_shadows_local_variable p1 id p2 =
40 Lints.add
41 Codes.loop_variable_shadows_local_variable
42 Lint_warning
44 (Printf.sprintf
45 "Loop variable %s shadows a local variable defined or last assigned here:\n%s"
46 (Local_id.get_name id |> Markdown_lite.md_codify)
47 (Pos.string (Pos.to_relative_string p2)))
49 let bad_virtualized_method p =
50 Lints.add
51 Codes.bool_method_return_hint
52 Lint_warning
54 "`__bool` methods should return `bool`. They show that an expression tree type can be used in a boolean expression."
56 let non_equatable_comparison p ret ty1 ty2 =
57 Lints.add Codes.non_equatable_comparison Lint_warning p
58 @@ Printf.sprintf
59 "Invalid comparison: This expression will always return %s.\nA value of type %s can never be equal to a value of type %s"
60 (string_of_bool ret |> Markdown_lite.md_codify)
61 (Markdown_lite.md_codify ty1)
62 (Markdown_lite.md_codify ty2)
64 let invalid_contains_check p trv_val_ty val_ty =
65 Lints.add Codes.invalid_contains_check Lint_warning p
66 @@ Printf.sprintf
67 "Invalid `C\\contains` check: This call will always return `false`.\nA `Traversable<%s>` cannot contain a value of type %s"
68 trv_val_ty
69 (Markdown_lite.md_codify val_ty)
71 let invalid_contains_key_check p trv_key_ty key_ty =
72 Lints.add Codes.invalid_contains_check Lint_warning p
73 @@ Printf.sprintf
74 "Invalid `C\\contains_key` check: This call will always return `false`.\nA `KeyedTraversable<%s, ...>` cannot contain a key of type %s"
75 trv_key_ty
76 (Markdown_lite.md_codify key_ty)
78 let non_equatable_due_to_opaque_types p ty1 ty2 enums =
79 Lints.add Codes.non_equatable_comparison Lint_warning p
80 @@ Printf.sprintf
81 "Invalid comparison:\nA value of type %s should not be compared to a value of type %s%s"
82 (Markdown_lite.md_codify ty1)
83 (Markdown_lite.md_codify ty2)
84 begin
85 match enums with
86 | [] -> ""
87 | enums ->
88 Printf.sprintf
89 " because the enum type%s %s %s opaque.\nUse functions like `%s::coerce` and `%s::assert` to convert values to the appropriate type before comparing."
90 (if List.length enums = 1 then
92 else
93 "s")
94 (match enums with
95 | [a; b] ->
96 Printf.sprintf
97 "%s and %s"
98 (Markdown_lite.md_codify a)
99 (Markdown_lite.md_codify b)
100 | _ -> String.concat ~sep:", " enums)
101 (if List.length enums = 1 then
102 "is"
103 else
104 "are")
105 (List.hd_exn enums)
106 (List.hd_exn enums)
109 let is_always_true p lhs_class rhs_class =
110 let lhs_class = Markdown_lite.md_codify lhs_class in
111 let rhs_class = Markdown_lite.md_codify rhs_class in
112 Lints.add
113 Codes.is_always_true
114 Lint_warning
116 (Printf.sprintf
117 "This `is` check is always `true`. The expression on the left is an instance of %s. It is always an instance of %s because %s derives from %s."
118 lhs_class
119 rhs_class
120 lhs_class
121 rhs_class)
123 let is_always_false p lhs_class rhs_class =
124 let lhs_class = Markdown_lite.md_codify lhs_class in
125 let rhs_class = Markdown_lite.md_codify rhs_class in
126 Lints.add
127 Codes.is_always_false
128 Lint_warning
130 (Printf.sprintf
131 "This `is` check is always `false`. The expression on the left is an instance of %s. It can never be an instance of %s because there is no class that is both %s and %s."
132 lhs_class
133 rhs_class
134 lhs_class
135 rhs_class)
137 let as_always_succeeds p lhs_class rhs_class =
138 let lhs_class = Markdown_lite.md_codify lhs_class in
139 let rhs_class = Markdown_lite.md_codify rhs_class in
140 Lints.add
141 Codes.as_always_succeeds
142 Lint_warning
144 (Printf.sprintf
145 "This `as` assertion will always succeed and hence is redundant. The expression on the left is an instance of %s. It is always an instance of %s because %s derives from %s."
146 lhs_class
147 rhs_class
148 lhs_class
149 rhs_class)
151 let as_always_fails p lhs_class rhs_class =
152 let lhs_class = Markdown_lite.md_codify lhs_class in
153 let rhs_class = Markdown_lite.md_codify rhs_class in
154 Lints.add
155 Codes.as_always_fails
156 Lint_warning
158 (Printf.sprintf
159 "This `as` assertion will always fail. The expression on the left is an instance of %s. It can never be an instance of %s because there is no class that is both %s and %s."
160 lhs_class
161 rhs_class
162 lhs_class
163 rhs_class)
165 let as_invalid_type pos var hint =
166 Lints.add
167 Codes.as_invalid_type
168 Lint_error
170 (Printf.sprintf
171 "A value of type %s will always throw an exception when refining to %s"
172 (Markdown_lite.md_codify var)
173 (Markdown_lite.md_codify hint))
175 let class_overrides_all_trait_methods pos class_name trait_name =
176 Lints.add
177 Codes.class_overrides_all_trait_methods
178 Lint_warning
180 (Printf.sprintf
181 "Unused trait: %s is overriding all the methods in %s"
182 (Utils.strip_ns class_name |> Markdown_lite.md_codify)
183 (Utils.strip_ns trait_name |> Markdown_lite.md_codify))
185 let invalid_null_check p ret ty =
186 Lints.add Codes.invalid_null_check Lint_warning p
187 @@ Printf.sprintf
188 "Invalid null check: This expression will always return %s.\nA value of type %s can never be null."
189 (string_of_bool ret |> Markdown_lite.md_codify)
190 (Markdown_lite.md_codify ty)
192 let redundant_nonnull_assertion p ty =
193 Lints.add Codes.redundant_nonnull_assertion Lint_warning p
194 @@ Printf.sprintf
195 "This `as` assertion will always succeed and hence is redundant. A value of type %s can never be null."
196 (Markdown_lite.md_codify ty)
198 let invalid_disjointness_check p name ty1 ty2 =
199 Lints.add Codes.invalid_disjointness_check Lint_warning p
200 @@ Printf.sprintf
201 "This call to '%s' will always return the same value, because type %s is disjoint from type %s."
202 name
206 let invalid_switch_case_value_type
207 (case_value_p : Ast_defs.pos) case_value_ty scrutinee_ty =
208 Lints.add Codes.invalid_switch_case_value_type Lint_warning case_value_p
209 @@ Printf.sprintf
210 "Switch statements use `===` equality. Comparing values of type %s with %s may not give the desired result."
211 (Markdown_lite.md_codify @@ Lazy.force case_value_ty)
212 (Markdown_lite.md_codify @@ Lazy.force scrutinee_ty)
214 let missing_override_attribute p ~class_name ~method_name =
215 let msg =
216 Printf.sprintf
217 "Method %s is also defined on %s, but this method is missing `<<__Override>>`."
218 (Markdown_lite.md_codify method_name)
219 (Utils.strip_ns class_name |> Markdown_lite.md_codify)
221 Lints.add Codes.missing_override_attribute Lint_error p @@ msg
223 let sketchy_null_check pos name kind =
224 let name = Option.value name ~default:"$x" in
225 Lints.add Codes.sketchy_null_check Lint_warning pos
226 @@ "This is a sketchy null check.\nIt detects nulls, but it will also detect many other falsy values, including `false`, `0`, `0.0`, `\"\"`, `\"0\"`, empty Containers, and more.\nIf you want to test for them, please consider doing so explicitly.\nIf you only meant to test for `null`, "
228 match kind with
229 | `Coalesce ->
230 Printf.sprintf "use `%s ?? $default` instead of `%s ?: $default`" name name
231 | `Eq -> Printf.sprintf "use `%s is null` instead" name
232 | `Neq -> Printf.sprintf "use `%s is nonnull` instead" name
234 let invalid_truthiness_test pos ty =
235 Lints.add Codes.invalid_truthiness_test Lint_warning pos
236 @@ Printf.sprintf
237 "Invalid condition: a value of type %s will always be truthy"
238 (Markdown_lite.md_codify ty)
240 let invalid_truthiness_test_falsy pos ty =
241 Lints.add Codes.invalid_truthiness_test Lint_warning pos
242 @@ Printf.sprintf
243 "Invalid condition: a value of type %s will always be falsy"
244 (Markdown_lite.md_codify ty)
246 let sketchy_truthiness_test pos ty truthiness =
247 Lints.add Codes.sketchy_truthiness_test Lint_warning pos
249 match truthiness with
250 | `String ->
251 Printf.sprintf
252 "Sketchy condition: testing the truthiness of %s may not behave as expected.\nThe values `\"\"` and `\"0\"` are both considered falsy. To check for emptiness, use `Str\\is_empty`."
254 | `Arraykey ->
255 Printf.sprintf
256 "Sketchy condition: testing the truthiness of %s may not behave as expected.\nThe values `0`, `\"\"`, and `\"0\"` are all considered falsy. Test for them explicitly."
258 | `Stringish ->
259 Printf.sprintf
260 "Sketchy condition: testing the truthiness of a %s may not behave as expected.\nThe values `\"\"` and `\"0\"` are both considered falsy, but objects will be truthy even if their `__toString` returns `\"\"` or `\"0\"`.\nTo check for emptiness, convert to a string and use `Str\\is_empty`."
262 | `XHPChild ->
263 Printf.sprintf
264 "Sketchy condition: testing the truthiness of an %s may not behave as expected.\nThe values `\"\"` and `\"0\"` are both considered falsy, but objects (including XHP elements) will be truthy even if their `__toString` returns `\"\"` or `\"0\"`."
266 | `Traversable ->
267 (* We have a truthiness test on a value with an interface type which is a
268 subtype of Traversable, but not a subtype of Container.
269 Since the runtime value may be a falsy-when-empty Container or an
270 always-truthy Iterable/Generator, we forbid the test. *)
271 Printf.sprintf
272 "Sketchy condition: a value of type %s may be truthy even when empty.\nHack collections and arrays are falsy when empty, but user-defined Traversables will always be truthy, even when empty.\nIf you would like to only allow containers which are falsy when empty, use the `Container` or `KeyedContainer` interfaces."
275 let redundant_covariant pos name msg suggest =
276 Lints.add Codes.redundant_generic Lint_warning pos
277 @@ "The generic parameter "
278 ^ Markdown_lite.md_codify name
279 ^ " is redundant because it only appears in a covariant (output) position"
280 ^ msg
281 ^ ". Consider replacing uses of generic parameter with "
282 ^ Markdown_lite.md_codify suggest
283 ^ " or specifying `<<__Explicit>>` on the generic parameter"
285 let redundant_contravariant pos name msg suggest =
286 Lints.add Codes.redundant_generic Lint_warning pos
287 @@ "The generic parameter "
288 ^ Markdown_lite.md_codify name
289 ^ " is redundant because it only appears in a contravariant (input) position"
290 ^ msg
291 ^ ". Consider replacing uses of generic parameter with "
292 ^ Markdown_lite.md_codify suggest
293 ^ " or specifying `<<__Explicit>>` on the generic parameter"
295 let redundant_generic pos name =
296 Lints.add Codes.redundant_generic Lint_warning pos
297 @@ Printf.sprintf
298 "The generic parameter %s is unused."
299 (Markdown_lite.md_codify name)
301 let inferred_variance pos name description syntax =
302 Lints.add Codes.inferred_variance Lint_advice pos
303 @@ "The generic parameter "
304 ^ Markdown_lite.md_codify name
305 ^ " could be marked "
306 ^ description
307 ^ ". Consider prefixing it with "
308 ^ syntax
310 let nullsafe_not_needed pos =
311 Lints.add Codes.nullsafe_not_needed Lint_advice pos
312 @@ "You are using the "
313 ^ Markdown_lite.md_codify "?->"
314 ^ " operator but this object cannot be null."
315 ^ " You can use the "
316 ^ Markdown_lite.md_codify "->"
317 ^ " operator instead."
319 let invalid_attribute_value
320 pos (attr_name : string) (valid_values : string list) =
321 let valid_values = List.map valid_values ~f:Markdown_lite.md_codify in
322 Lints.add
323 Codes.bad_xhp_enum_attribute_value
324 Lint_error
326 (Printf.sprintf
327 "Invalid value for %s, expected one of %s."
328 (Markdown_lite.md_codify attr_name)
329 (String.concat ~sep:", " valid_values))
331 let parse_error code pos msg = Lints.add code Lint_error pos msg
333 let rec prettify_class_list names =
334 match names with
335 | [] -> ""
336 | [c] -> c
337 | [c1; c2] -> c1 ^ " and " ^ c2
338 | h :: t -> h ^ ", " ^ prettify_class_list t
340 let duplicate_property_class_constant_init
341 pos ~class_name ~prop_name ~class_names =
342 Lints.add
343 Codes.duplicate_property_enum_init
344 Lint_error
346 ("Property "
347 ^ (Utils.strip_ns prop_name |> Markdown_lite.md_codify)
348 ^ ", defined in "
349 ^ prettify_class_list (List.map ~f:Utils.strip_ns class_names)
350 ^ ", is inherited multiple times by class "
351 ^ (Utils.strip_ns class_name |> Markdown_lite.md_codify)
352 ^ " and one of its instances is initialised with a class or enum constant")
354 let duplicate_property pos ~class_name ~prop_name ~class_names =
355 Lints.add
356 Codes.duplicate_property
357 Lint_warning
359 ("Duplicated property "
360 ^ (Utils.strip_ns prop_name |> Markdown_lite.md_codify)
361 ^ " in "
362 ^ (Utils.strip_ns class_name |> Markdown_lite.md_codify)
363 ^ " (defined in "
364 ^ prettify_class_list
365 (List.map
366 ~f:(fun n -> Utils.strip_ns n |> Markdown_lite.md_codify)
367 class_names)
368 ^ "): all instances will be aliased at runtime")
370 let loose_unsafe_cast_lower_bound p ty_str_opt =
371 let msg =
372 "HH\\FIXME\\UNSAFE_CAST input type annotation is too loose, please use a more specific type."
374 let msg =
375 match ty_str_opt with
376 | Some ty_str ->
378 ^ " The typechecker infers "
379 ^ Markdown_lite.md_codify ty_str
380 ^ " as the most specific type."
381 | None -> msg
383 Lints.add Codes.loose_unsafe_cast_lower_bound Lint_error p msg
385 let loose_unsafe_cast_upper_bound p =
386 Lints.add
387 Codes.loose_unsafe_cast_upper_bound
388 Lint_error
390 "HH\\FIXME\\UNSAFE_CAST output type annotation is too loose, please use a more specific type."
392 let switch_nonexhaustive p =
393 Lints.add
394 Codes.switch_nonexhaustive
395 Lint_warning
397 ("This switch statement is not exhaustive."
398 ^ " The expression it scrutinises has a type with infinitely many values and the statement does not have a default case."
399 ^ " If none of the cases match, an exception will be thrown."
400 ^ " Consider adding a default case.")