zf

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

nested_function_calls.ex (3384B)


      1 defmodule Credo.Check.Readability.NestedFunctionCalls do
      2   use Credo.Check,
      3     tags: [:controversial],
      4     param_defaults: [min_pipeline_length: 2],
      5     explanations: [
      6       check: """
      7       A function call should not be nested inside another function call.
      8 
      9       So while this is fine:
     10 
     11           Enum.shuffle([1,2,3])
     12 
     13       The code in this example ...
     14 
     15           Enum.shuffle(Enum.uniq([1,2,3,3]))
     16 
     17       ... should be refactored to look like this:
     18 
     19           [1,2,3,3]
     20           |> Enum.uniq()
     21           |> Enum.shuffle()
     22 
     23       Nested function calls make the code harder to read. Instead, break the
     24       function calls out into a pipeline.
     25 
     26       Like all `Readability` issues, this one is not a technical concern.
     27       But you can improve the odds of others reading and liking your code by making
     28       it easier to follow.
     29       """,
     30       params: [
     31         min_pipeline_length: "Set a minimum pipeline length"
     32       ]
     33     ]
     34 
     35   alias Credo.Code.Name
     36 
     37   @doc false
     38   @impl true
     39   def run(%SourceFile{} = source_file, params) do
     40     issue_meta = IssueMeta.for(source_file, params)
     41 
     42     min_pipeline_length = Params.get(params, :min_pipeline_length, __MODULE__)
     43 
     44     {_continue, issues} =
     45       Credo.Code.prewalk(
     46         source_file,
     47         &traverse(&1, &2, issue_meta, min_pipeline_length),
     48         {true, []}
     49       )
     50 
     51     issues
     52   end
     53 
     54   # A call with no arguments
     55   defp traverse({{:., _loc, _call}, _meta, []} = ast, {_, issues}, _, min_pipeline_length) do
     56     {ast, {min_pipeline_length, issues}}
     57   end
     58 
     59   # A call with arguments
     60   defp traverse(
     61          {{:., _loc, call}, meta, args} = ast,
     62          {_, issues},
     63          issue_meta,
     64          min_pipeline_length
     65        ) do
     66     if valid_chain_start?(ast) do
     67       {ast, {min_pipeline_length, issues}}
     68     else
     69       case length_as_pipeline(args) + 1 do
     70         potential_pipeline_length when potential_pipeline_length >= min_pipeline_length ->
     71           {ast,
     72            {min_pipeline_length, issues ++ [issue_for(issue_meta, meta[:line], Name.full(call))]}}
     73 
     74         _ ->
     75           {ast, {min_pipeline_length, issues}}
     76       end
     77     end
     78   end
     79 
     80   # Another expression
     81   defp traverse(ast, {_, issues}, _issue_meta, min_pipeline_length) do
     82     {ast, {min_pipeline_length, issues}}
     83   end
     84 
     85   # Call with no arguments
     86   defp length_as_pipeline([{{:., _loc, _call}, _meta, []} | _]) do
     87     0
     88   end
     89 
     90   # Call with function call for first argument
     91   defp length_as_pipeline([{{:., _loc, _call}, _meta, args} = call_ast | _]) do
     92     if valid_chain_start?(call_ast) do
     93       0
     94     else
     95       1 + length_as_pipeline(args)
     96     end
     97   end
     98 
     99   # Call where the first argument isn't another function call
    100   defp length_as_pipeline(_args) do
    101     0
    102   end
    103 
    104   defp issue_for(issue_meta, line_no, trigger) do
    105     format_issue(
    106       issue_meta,
    107       message: "Use a pipeline when there are nested function calls",
    108       trigger: trigger,
    109       line_no: line_no
    110     )
    111   end
    112 
    113   # Taken from the Credo.Check.Refactor.PipeChainStart module, with modifications
    114   # map[:access]
    115   defp valid_chain_start?({{:., _, [Access, :get]}, _, _}), do: true
    116 
    117   # Module.function_call()
    118   defp valid_chain_start?({{:., _, _}, _, []}), do: true
    119 
    120   # Kernel.to_string is invoked for string interpolation e.g. "string #{variable}"
    121   defp valid_chain_start?({{:., _, [Kernel, :to_string]}, _, _}), do: true
    122 
    123   defp valid_chain_start?(_), do: false
    124 end