function_names.ex (4396B)
1 defmodule Credo.Check.Readability.FunctionNames do 2 use Credo.Check, 3 base_priority: :high, 4 param_defaults: [ 5 allow_acronyms: false 6 ], 7 explanations: [ 8 check: """ 9 Function, macro, and guard names are always written in snake_case in Elixir. 10 11 # snake_case 12 13 def handle_incoming_message(message) do 14 end 15 16 # not snake_case 17 18 def handleIncomingMessage(message) do 19 end 20 21 Like all `Readability` issues, this one is not a technical concern. 22 But you can improve the odds of others reading and liking your code by making 23 it easier to follow. 24 """, 25 params: [ 26 allow_acronyms: "Allows acronyms like HTTP or OTP in function names." 27 ] 28 ] 29 30 alias Credo.Code.Name 31 32 @def_ops [:def, :defp, :defmacro, :defmacrop, :defguard, :defguardp] 33 @all_sigil_chars ~w(a A b B c C d D e E f F g G h H i I j J k K l L m M n N o O p P q Q r R s S t T u U v V w W x X y Y z Z) 34 @all_sigil_atoms Enum.map(@all_sigil_chars, &:"sigil_#{&1}") 35 36 # all non-special-form operators 37 @all_nonspecial_operators ~W(! && ++ -- .. <> =~ @ |> || != !== * + - / < <= == === > >= ||| &&& <<< >>> <<~ ~>> <~ ~> <~> <|> ^^^ ~~~)a 38 39 @doc false 40 @impl true 41 def run(%SourceFile{} = source_file, params \\ []) do 42 issue_meta = IssueMeta.for(source_file, params) 43 allow_acronyms? = Credo.Check.Params.get(params, :allow_acronyms, __MODULE__) 44 45 source_file 46 |> Credo.Code.prewalk(&traverse(&1, &2, issue_meta, allow_acronyms?), empty_issues()) 47 |> issues_list() 48 end 49 50 defp empty_issues, do: %{} 51 52 defp add_issue(issues, name, arity, issue), do: Map.put_new(issues, {name, arity}, issue) 53 54 defp issues_list(issues) do 55 issues 56 |> Map.values() 57 |> Enum.sort_by(& &1.line_no) 58 end 59 60 # Ignore sigil definitions 61 for sigil <- @all_sigil_atoms do 62 defp traverse( 63 {op, _meta, [{unquote(sigil), _sigil_meta, _args} | _tail]} = ast, 64 issues, 65 _issue_meta, 66 _allow_acronyms? 67 ) 68 when op in [:def, :defmacro] do 69 {ast, issues} 70 end 71 72 defp traverse( 73 {op, _op_meta, 74 [{:when, _when_meta, [{unquote(sigil), _sigil_meta, _args} | _tail]}, _block]} = ast, 75 issues, 76 _issue_meta, 77 _allow_acronyms? 78 ) 79 when op in [:def, :defmacro] do 80 {ast, issues} 81 end 82 end 83 84 # TODO: consider for experimental check front-loader (ast) 85 # NOTE: see above for how we want to avoid `sigil_X` definitions 86 for op <- @def_ops do 87 # Ignore variables named e.g. `defp` 88 defp traverse({unquote(op), _meta, nil} = ast, issues, _issue_meta, _allow_acronyms?) do 89 {ast, issues} 90 end 91 92 # ignore non-special-form (overridable) operators 93 defp traverse( 94 {unquote(op), _meta, [{operator, _at_meta, _args} | _tail]} = ast, 95 issues, 96 _issue_meta, 97 _allow_acronyms? 98 ) 99 when operator in @all_nonspecial_operators do 100 {ast, issues} 101 end 102 103 defp traverse({unquote(op), _meta, arguments} = ast, issues, issue_meta, allow_acronyms?) do 104 {ast, issues_for_definition(arguments, issues, issue_meta, allow_acronyms?)} 105 end 106 end 107 108 defp traverse(ast, issues, _issue_meta, _allow_acronyms?) do 109 {ast, issues} 110 end 111 112 defp issues_for_definition(body, issues, issue_meta, allow_acronyms?) do 113 case Enum.at(body, 0) do 114 {:when, _when_meta, [{name, meta, args} | _guard]} -> 115 issues_for_name(name, args, meta, issues, issue_meta, allow_acronyms?) 116 117 {name, meta, args} when is_atom(name) -> 118 issues_for_name(name, args, meta, issues, issue_meta, allow_acronyms?) 119 120 _ -> 121 issues 122 end 123 end 124 125 defp issues_for_name({:unquote, _, _}, _args, _meta, issues, _issue_meta, _allow_acronyms?) do 126 issues 127 end 128 129 defp issues_for_name(name, args, meta, issues, issue_meta, allow_acronyms?) do 130 if name |> to_string |> Name.snake_case?(allow_acronyms?) do 131 issues 132 else 133 issue = issue_for(issue_meta, meta[:line], name) 134 arity = length(args || []) 135 136 add_issue(issues, name, arity, issue) 137 end 138 end 139 140 defp issue_for(issue_meta, line_no, trigger) do 141 format_issue( 142 issue_meta, 143 message: "Function/macro/guard names should be written in snake_case.", 144 trigger: trigger, 145 line_no: line_no 146 ) 147 end 148 end