validate_config.ex (3811B)
1 defmodule Credo.Execution.Task.ValidateConfig do 2 @moduledoc false 3 4 use Credo.Execution.Task 5 6 alias Credo.Check 7 alias Credo.Check.Params 8 alias Credo.CLI.Output.UI 9 10 def call(exec, _opts) do 11 exec 12 |> validate_checks() 13 |> validate_only_checks() 14 |> validate_ignore_checks() 15 |> remove_missing_checks() 16 |> inspect_config_if_debug() 17 end 18 19 defp validate_only_checks(%Execution{only_checks: only_checks} = exec) 20 when is_list(only_checks) do 21 if Enum.any?(only_checks, &(&1 == "")) do 22 UI.warn([ 23 :red, 24 "** (config) Including all checks, since an empty string was given as a pattern: #{inspect(only_checks)}" 25 ]) 26 end 27 28 exec 29 end 30 31 defp validate_only_checks(exec) do 32 exec 33 end 34 35 defp validate_ignore_checks(%Execution{ignore_checks: ignore_checks} = exec) 36 when is_list(ignore_checks) do 37 if Enum.any?(ignore_checks, &(&1 == "")) do 38 UI.warn([ 39 :red, 40 "** (config) Ignoring all checks, since an empty string was given as a pattern: #{inspect(ignore_checks)}" 41 ]) 42 end 43 44 exec 45 end 46 47 defp validate_ignore_checks(exec) do 48 exec 49 end 50 51 defp validate_checks(%Execution{checks: %{enabled: checks}} = exec) do 52 Enum.each(checks, fn check_tuple -> 53 warn_if_check_missing(check_tuple) 54 warn_if_check_params_invalid(check_tuple) 55 end) 56 57 exec 58 end 59 60 defp warn_if_check_params_invalid({_check, false}), do: nil 61 defp warn_if_check_params_invalid({_check, []}), do: nil 62 63 defp warn_if_check_params_invalid({check, params}) do 64 if Check.defined?(check) do 65 do_warn_if_check_params_invalid({check, params}) 66 end 67 end 68 69 defp do_warn_if_check_params_invalid({check, params}) do 70 valid_param_names = check.param_names ++ Params.builtin_param_names() 71 check = check |> to_string |> String.to_existing_atom() 72 73 Enum.each(params, fn {param_name, _param_value} -> 74 unless Enum.member?(valid_param_names, param_name) do 75 candidate = find_best_match(valid_param_names, param_name) 76 warning = warning_message_for(check, param_name, candidate) 77 78 UI.warn([:red, warning]) 79 end 80 end) 81 end 82 83 defp warning_message_for(check, param_name, candidate) do 84 if candidate do 85 "** (config) #{check_name(check)}: unknown param `#{param_name}`. Did you mean `#{candidate}`?" 86 else 87 "** (config) #{check_name(check)}: unknown param `#{param_name}`." 88 end 89 end 90 91 defp find_best_match(candidates, given, threshold \\ 0.8) do 92 given_string = to_string(given) 93 94 {jaro_distance, candidate} = 95 candidates 96 |> Enum.map(fn candidate_name -> 97 distance = String.jaro_distance(given_string, to_string(candidate_name)) 98 {distance, candidate_name} 99 end) 100 |> Enum.sort() 101 |> List.last() 102 103 if jaro_distance > threshold do 104 candidate 105 end 106 end 107 108 defp warn_if_check_missing({check, _params}) do 109 unless Check.defined?(check) do 110 UI.warn([:red, "** (config) Ignoring an undefined check: #{check_name(check)}"]) 111 end 112 end 113 114 defp check_name(atom) do 115 atom 116 |> to_string() 117 |> String.replace(~r/^Elixir\./, "") 118 end 119 120 defp inspect_config_if_debug(%Execution{debug: true} = exec) do 121 require Logger 122 123 Logger.debug(fn -> 124 """ 125 Execution struct after #{__MODULE__}: 126 127 #{inspect(exec, pretty: true)} 128 """ 129 end) 130 131 exec 132 end 133 134 defp inspect_config_if_debug(exec), do: exec 135 136 defp remove_missing_checks( 137 %Execution{checks: %{enabled: enabled_checks, disabled: disabled_checks}} = exec 138 ) do 139 enabled_checks = Enum.filter(enabled_checks, &Check.defined?/1) 140 disabled_checks = Enum.filter(disabled_checks, &Check.defined?/1) 141 142 %Execution{exec | checks: %{enabled: enabled_checks, disabled: disabled_checks}} 143 end 144 end