zf

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

specs.ex (3652B)


      1 defmodule Credo.Check.Readability.Specs do
      2   use Credo.Check,
      3     tags: [:controversial],
      4     param_defaults: [
      5       include_defp: false
      6     ],
      7     explanations: [
      8       check: """
      9       Functions, callbacks and macros need typespecs.
     10 
     11       Adding typespecs gives tools like Dialyzer more information when performing
     12       checks for type errors in function calls and definitions.
     13 
     14           @spec add(integer, integer) :: integer
     15           def add(a, b), do: a + b
     16 
     17       Functions with multiple arities need to have a spec defined for each arity:
     18 
     19           @spec foo(integer) :: boolean
     20           @spec foo(integer, integer) :: boolean
     21           def foo(a), do: a > 0
     22           def foo(a, b), do: a > b
     23 
     24       The check only considers whether the specification is present, it doesn't
     25       perform any actual type checking.
     26 
     27       Like all `Readability` issues, this one is not a technical concern.
     28       But you can improve the odds of others reading and liking your code by making
     29       it easier to follow.
     30       """,
     31       params: [
     32         include_defp: "Include private functions."
     33       ]
     34     ]
     35 
     36   @doc false
     37   @impl true
     38   def run(%SourceFile{} = source_file, params) do
     39     issue_meta = IssueMeta.for(source_file, params)
     40     specs = Credo.Code.prewalk(source_file, &find_specs(&1, &2))
     41 
     42     Credo.Code.prewalk(source_file, &traverse(&1, &2, specs, issue_meta))
     43   end
     44 
     45   defp find_specs(
     46          {:spec, _, [{:when, _, [{:"::", _, [{name, _, args}, _]}, _]} | _]} = ast,
     47          specs
     48        ) do
     49     {ast, [{name, length(args)} | specs]}
     50   end
     51 
     52   defp find_specs({:spec, _, [{_, _, [{name, _, args} | _]}]} = ast, specs)
     53        when is_list(args) or is_nil(args) do
     54     args = with nil <- args, do: []
     55     {ast, [{name, length(args)} | specs]}
     56   end
     57 
     58   defp find_specs({:impl, _, [impl]} = ast, specs) when impl != false do
     59     {ast, [:impl | specs]}
     60   end
     61 
     62   defp find_specs({keyword, meta, [{:when, _, def_ast} | _]}, [:impl | specs])
     63        when keyword in [:def, :defp] do
     64     find_specs({keyword, meta, def_ast}, [:impl | specs])
     65   end
     66 
     67   defp find_specs({keyword, _, [{name, _, nil}, _]} = ast, [:impl | specs])
     68        when keyword in [:def, :defp] do
     69     {ast, [{name, 0} | specs]}
     70   end
     71 
     72   defp find_specs({keyword, _, [{name, _, args}, _]} = ast, [:impl | specs])
     73        when keyword in [:def, :defp] do
     74     {ast, [{name, length(args)} | specs]}
     75   end
     76 
     77   defp find_specs(ast, issues) do
     78     {ast, issues}
     79   end
     80 
     81   # TODO: consider for experimental check front-loader (ast)
     82   defp traverse(
     83          {keyword, meta, [{:when, _, def_ast} | _]},
     84          issues,
     85          specs,
     86          issue_meta
     87        )
     88        when keyword in [:def, :defp] do
     89     traverse({keyword, meta, def_ast}, issues, specs, issue_meta)
     90   end
     91 
     92   defp traverse(
     93          {keyword, meta, [{name, _, args} | _]} = ast,
     94          issues,
     95          specs,
     96          issue_meta
     97        )
     98        when is_list(args) or is_nil(args) do
     99     args = with nil <- args, do: []
    100 
    101     if keyword not in enabled_keywords(issue_meta) or {name, length(args)} in specs do
    102       {ast, issues}
    103     else
    104       {ast, [issue_for(issue_meta, meta[:line], name) | issues]}
    105     end
    106   end
    107 
    108   defp traverse(ast, issues, _specs, _issue_meta) do
    109     {ast, issues}
    110   end
    111 
    112   defp issue_for(issue_meta, line_no, trigger) do
    113     format_issue(
    114       issue_meta,
    115       message: "Functions should have a @spec type specification.",
    116       trigger: trigger,
    117       line_no: line_no
    118     )
    119   end
    120 
    121   defp enabled_keywords(issue_meta) do
    122     issue_meta
    123     |> IssueMeta.params()
    124     |> Params.get(:include_defp, __MODULE__)
    125     |> case do
    126       true -> [:def, :defp]
    127       _ -> [:def]
    128     end
    129   end
    130 end