zf

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

nesting.ex (3681B)


      1 defmodule Credo.Check.Refactor.Nesting do
      2   use Credo.Check,
      3     param_defaults: [max_nesting: 2],
      4     explanations: [
      5       check: """
      6       Code should not be nested more than once inside a function.
      7 
      8           defmodule CredoSampleModule do
      9             def some_function(parameter1, parameter2) do
     10               Enum.reduce(var1, list, fn({_hash, nodes}, list) ->
     11                 filenames = nodes |> Enum.map(&(&1.filename))
     12 
     13                 Enum.reduce(list, [], fn(item, acc) ->
     14                   if item.filename do
     15                     item               # <-- this is nested 3 levels deep
     16                   end
     17                   acc ++ [item]
     18                 end)
     19               end)
     20             end
     21           end
     22 
     23       At this point it might be a good idea to refactor the code to separate the
     24       different loops and conditions.
     25       """,
     26       params: [
     27         max_nesting: "The maximum number of levels code should be nested."
     28       ]
     29     ]
     30 
     31   @def_ops [:def, :defp, :defmacro]
     32   @nest_ops [:if, :unless, :case, :cond, :fn]
     33 
     34   @doc false
     35   @impl true
     36   def run(%SourceFile{} = source_file, params) do
     37     issue_meta = IssueMeta.for(source_file, params)
     38     max_nesting = Params.get(params, :max_nesting, __MODULE__)
     39 
     40     Credo.Code.prewalk(source_file, &traverse(&1, &2, issue_meta, max_nesting))
     41   end
     42 
     43   # TODO: consider for experimental check front-loader (ast)
     44   for op <- @def_ops do
     45     defp traverse(
     46            {unquote(op) = op, meta, arguments} = ast,
     47            issues,
     48            issue_meta,
     49            max_nesting
     50          )
     51          when is_list(arguments) do
     52       arguments
     53       |> find_depth([], meta[:line], op)
     54       |> handle_depth(ast, issue_meta, issues, max_nesting)
     55     end
     56   end
     57 
     58   defp traverse(ast, issues, _issue_meta, _max_nesting) do
     59     {ast, issues}
     60   end
     61 
     62   defp handle_depth(nil, ast, _issue_meta, issues, _max_nesting) do
     63     {ast, issues}
     64   end
     65 
     66   defp handle_depth(
     67          {depth, line_no, trigger},
     68          ast,
     69          issue_meta,
     70          issues,
     71          max_nesting
     72        ) do
     73     if depth > max_nesting do
     74       {
     75         ast,
     76         issues ++ [issue_for(issue_meta, line_no, trigger, max_nesting, depth)]
     77       }
     78     else
     79       {ast, issues}
     80     end
     81   end
     82 
     83   # Searches for the depth level and returns a tuple `{depth, line_no, trigger}`
     84   # where the greatest depth was reached.
     85   defp find_depth(arguments, nest_list, line_no, trigger)
     86        when is_list(arguments) do
     87     arguments
     88     |> Credo.Code.Block.do_block_for!()
     89     |> List.wrap()
     90     |> Enum.map(&find_depth(&1, nest_list, line_no, trigger))
     91     |> Enum.sort()
     92     |> List.last()
     93   end
     94 
     95   for op <- @nest_ops do
     96     defp find_depth(
     97            {unquote(op) = op, meta, arguments},
     98            nest_list,
     99            _line_no,
    100            _trigger
    101          )
    102          when is_list(arguments) do
    103       arguments
    104       |> Enum.map(&find_depth(&1, nest_list ++ [op], meta[:line], op))
    105       |> Enum.sort()
    106       |> List.last()
    107     end
    108   end
    109 
    110   defp find_depth({atom, _meta, arguments}, nest_list, line_no, trigger)
    111        when (is_atom(atom) or is_tuple(atom)) and is_list(arguments) do
    112     arguments
    113     |> Enum.map(&find_depth(&1, nest_list, line_no, trigger))
    114     |> Enum.sort()
    115     |> List.last()
    116   end
    117 
    118   defp find_depth(_, nest_list, line_no, trigger) do
    119     {Enum.count(nest_list), line_no, trigger}
    120   end
    121 
    122   defp issue_for(issue_meta, line_no, trigger, max_value, actual_value) do
    123     format_issue(
    124       issue_meta,
    125       message:
    126         "Function body is nested too deep (max depth is #{max_value}, was #{actual_value}).",
    127       line_no: line_no,
    128       trigger: trigger,
    129       severity: Severity.compute(actual_value, max_value)
    130     )
    131   end
    132 end