zf

zenflows testing
git clone https://s.sonu.ch/~srfsh/zf.git
Log | Files | Refs | Submodules | README | LICENSE

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