double_boolean_negation.ex (2222B)
1 defmodule Credo.Check.Refactor.DoubleBooleanNegation do 2 use Credo.Check, 3 base_priority: :low, 4 tags: [:controversial], 5 explanations: [ 6 check: """ 7 Having double negations in your code can obscure the parameter's original value. 8 9 # NOT preferred 10 11 !!var 12 13 This will return `false` for `false` and `nil`, and `true` for anything else. 14 15 At first this seems like an extra clever shorthand to cast anything truthy to 16 `true` and anything non-truthy to `false`. But in most scenarios you want to 17 be explicit about your input parameters (because it is easier to reason about 18 edge-cases, code-paths and tests). 19 Also: `nil` and `false` do mean two different things. 20 21 A scenario where you want this kind of flexibility, however, is parsing 22 external data, e.g. a third party JSON-API where a value is sometimes `null` 23 and sometimes `false` and you want to normalize that before handing it down 24 in your program. 25 26 In these case, you would be better off making the cast explicit by introducing 27 a helper function: 28 29 # preferred 30 31 defp present?(nil), do: false 32 defp present?(false), do: false 33 defp present?(_), do: true 34 35 This makes your code more explicit than relying on the implications of `!!`. 36 """ 37 ] 38 39 @doc false 40 @impl true 41 def run(%SourceFile{} = source_file, params) do 42 issue_meta = IssueMeta.for(source_file, params) 43 44 Credo.Code.prewalk(source_file, &traverse(&1, &2, issue_meta)) 45 end 46 47 # Checking for `!!` 48 defp traverse({:!, meta, [{:!, _, ast}]}, issues, issue_meta) do 49 issue = 50 format_issue( 51 issue_meta, 52 message: "Double boolean negation found.", 53 trigger: "!!", 54 line_no: meta[:line] 55 ) 56 57 {ast, [issue | issues]} 58 end 59 60 # Checking for `not not` 61 defp traverse({:not, meta, [{:not, _, ast}]}, issues, issue_meta) do 62 issue = 63 format_issue( 64 issue_meta, 65 message: "Double boolean negation found.", 66 trigger: "not not", 67 line_no: meta[:line] 68 ) 69 70 {ast, [issue | issues]} 71 end 72 73 defp traverse(ast, issues, _issue_meta) do 74 {ast, issues} 75 end 76 end