zf

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

unused_function_return_helper.ex (7994B)


      1 defmodule Credo.Check.Warning.UnusedFunctionReturnHelper do
      2   @moduledoc false
      3 
      4   # Finds candidates and then postwalks the AST to either VERIFY or FALSIFY
      5   # the candidates (the acc is used to keep state).
      6 
      7   @def_ops [:def, :defp, :defmacro]
      8   @block_ops_with_head_expr [:if, :unless, :case, :for, :quote]
      9 
     10   alias Credo.Code.Block
     11   alias Credo.SourceFile
     12 
     13   def find_unused_calls(%SourceFile{} = source_file, _params, required_mod_list, fun_names) do
     14     Credo.Code.prewalk(source_file, &traverse_defs(&1, &2, required_mod_list, fun_names))
     15   end
     16 
     17   for op <- @def_ops do
     18     defp traverse_defs({unquote(op), _meta, arguments} = ast, acc, mod_list, fun_names)
     19          when is_list(arguments) do
     20       candidates = Credo.Code.prewalk(ast, &find_candidates(&1, &2, mod_list, fun_names))
     21 
     22       if Enum.any?(candidates) do
     23         {nil, acc ++ filter_unused_calls(ast, candidates)}
     24       else
     25         {ast, acc}
     26       end
     27     end
     28   end
     29 
     30   defp traverse_defs(ast, acc, _, _) do
     31     {ast, acc}
     32   end
     33 
     34   #
     35 
     36   defp find_candidates(
     37          {{:., _, [{:__aliases__, _, mods}, _fun_name]}, _, _} = ast,
     38          acc,
     39          required_mod_list,
     40          nil
     41        ) do
     42     if mods == required_mod_list do
     43       {ast, acc ++ [ast]}
     44     else
     45       {ast, acc}
     46     end
     47   end
     48 
     49   defp find_candidates(
     50          {{:., _, [{:__aliases__, _, mods}, fun_name]}, _, _} = ast,
     51          acc,
     52          required_mod_list,
     53          restrict_fun_names
     54        ) do
     55     if mods == required_mod_list and fun_name in restrict_fun_names do
     56       {ast, acc ++ [ast]}
     57     else
     58       {ast, acc}
     59     end
     60   end
     61 
     62   defp find_candidates(ast, acc, _, _) do
     63     {ast, acc}
     64   end
     65 
     66   #
     67 
     68   defp filter_unused_calls(ast, candidates) do
     69     candidates
     70     |> Enum.map(&detect_unused_call(&1, ast))
     71     |> Enum.reject(&is_nil/1)
     72   end
     73 
     74   defp detect_unused_call(candidate, ast) do
     75     ast
     76     |> Credo.Code.postwalk(&traverse_verify_candidate(&1, &2, candidate), :not_verified)
     77     |> verified_or_unused_call(candidate)
     78   end
     79 
     80   defp verified_or_unused_call(:VERIFIED, _), do: nil
     81   defp verified_or_unused_call(_, candidate), do: candidate
     82 
     83   #
     84 
     85   defp traverse_verify_candidate(ast, acc, candidate) do
     86     if Credo.Code.contains_child?(ast, candidate) do
     87       verify_candidate(ast, acc, candidate)
     88     else
     89       {ast, acc}
     90     end
     91   end
     92 
     93   # we know that `candidate` is part of `ast`
     94 
     95   for op <- @def_ops do
     96     defp verify_candidate({unquote(op), _, arguments} = ast, :not_verified = _acc, candidate)
     97          when is_list(arguments) do
     98       # IO.inspect(ast, label: "#{unquote(op)} (#{Macro.to_string(candidate)} #{acc})")
     99 
    100       if last_call_in_do_block?(ast, candidate) || last_call_in_rescue_block?(ast, candidate) ||
    101            last_call_in_catch_block?(ast, candidate) do
    102         {nil, :VERIFIED}
    103       else
    104         {nil, :FALSIFIED}
    105       end
    106     end
    107   end
    108 
    109   defp last_call_in_do_block?(ast, candidate) do
    110     ast
    111     |> Block.calls_in_do_block()
    112     |> List.last()
    113     |> Credo.Code.contains_child?(candidate)
    114   end
    115 
    116   defp last_call_in_rescue_block?(ast, candidate) do
    117     ast
    118     |> Block.calls_in_rescue_block()
    119     |> List.last()
    120     |> Credo.Code.contains_child?(candidate)
    121   end
    122 
    123   defp last_call_in_catch_block?(ast, candidate) do
    124     ast
    125     |> Block.calls_in_catch_block()
    126     |> List.last()
    127     |> Credo.Code.contains_child?(candidate)
    128   end
    129 
    130   for op <- @block_ops_with_head_expr do
    131     defp verify_candidate({unquote(op), _, arguments} = ast, :not_verified = acc, candidate)
    132          when is_list(arguments) do
    133       # IO.inspect(ast, label: "#{unquote(op)} (#{Macro.to_string(candidate)} #{acc})")
    134 
    135       head_expression = Enum.slice(arguments, 0..-2)
    136 
    137       if Credo.Code.contains_child?(head_expression, candidate) do
    138         {nil, :VERIFIED}
    139       else
    140         {ast, acc}
    141       end
    142     end
    143   end
    144 
    145   defp verify_candidate({:=, _, _} = ast, :not_verified = acc, candidate) do
    146     # IO.inspect(ast, label: ":= (#{Macro.to_string(candidate)} #{acc})")
    147 
    148     if Credo.Code.contains_child?(ast, candidate) do
    149       {nil, :VERIFIED}
    150     else
    151       {ast, acc}
    152     end
    153   end
    154 
    155   defp verify_candidate(
    156          {:__block__, _, arguments} = ast,
    157          :not_verified = acc,
    158          candidate
    159        )
    160        when is_list(arguments) do
    161     # IO.inspect(ast, label: ":__block__ (#{Macro.to_string(candidate)} #{acc})")
    162 
    163     last_call = List.last(arguments)
    164 
    165     if Credo.Code.contains_child?(last_call, candidate) do
    166       {ast, acc}
    167     else
    168       {nil, :FALSIFIED}
    169     end
    170   end
    171 
    172   defp verify_candidate(
    173          {:|>, _, arguments} = ast,
    174          :not_verified = acc,
    175          candidate
    176        ) do
    177     # IO.inspect(ast, label: ":__block__ (#{Macro.to_string(candidate)} #{acc})")
    178 
    179     last_call = List.last(arguments)
    180 
    181     if Credo.Code.contains_child?(last_call, candidate) do
    182       {ast, acc}
    183     else
    184       {nil, :VERIFIED}
    185     end
    186   end
    187 
    188   defp verify_candidate({:->, _, arguments} = ast, :not_verified = acc, _candidate)
    189        when is_list(arguments) do
    190     # IO.inspect(ast, label: ":-> (#{Macro.to_string(ast)} #{acc})")
    191 
    192     {ast, acc}
    193   end
    194 
    195   defp verify_candidate({:fn, _, arguments} = ast, :not_verified = acc, _candidate)
    196        when is_list(arguments) do
    197     {ast, acc}
    198   end
    199 
    200   defp verify_candidate(
    201          {:try, _, arguments} = ast,
    202          :not_verified = acc,
    203          candidate
    204        )
    205        when is_list(arguments) do
    206     # IO.inspect(ast, label: "try (#{Macro.to_string(candidate)} #{acc})")
    207 
    208     after_block = Block.after_block_for!(ast)
    209 
    210     if after_block && Credo.Code.contains_child?(after_block, candidate) do
    211       {nil, :FALSIFIED}
    212     else
    213       {ast, acc}
    214     end
    215   end
    216 
    217   # my_fun()
    218   defp verify_candidate(
    219          {fun_name, _, arguments} = ast,
    220          :not_verified = acc,
    221          candidate
    222        )
    223        when is_atom(fun_name) and is_list(arguments) do
    224     # IO.inspect(ast, label: "my_fun() (#{Macro.to_string(candidate)} #{acc})")
    225 
    226     if Credo.Code.contains_child?(arguments, candidate) do
    227       {nil, :VERIFIED}
    228     else
    229       {ast, acc}
    230     end
    231   end
    232 
    233   # module.my_fun()
    234   defp verify_candidate(
    235          {{:., _, [{module, _, []}, fun_name]}, _, arguments} = ast,
    236          :not_verified = acc,
    237          candidate
    238        )
    239        when is_atom(fun_name) and is_atom(module) and is_list(arguments) do
    240     # IO.inspect(ast, label: "Mod.fun() /1 (#{Macro.to_string(candidate)} #{acc})")
    241 
    242     if Credo.Code.contains_child?(arguments, candidate) do
    243       {nil, :VERIFIED}
    244     else
    245       {ast, acc}
    246     end
    247   end
    248 
    249   # :erlang_module.my_fun()
    250   defp verify_candidate(
    251          {{:., _, [module, fun_name]}, _, arguments} = ast,
    252          :not_verified = acc,
    253          candidate
    254        )
    255        when is_atom(fun_name) and is_atom(module) and is_list(arguments) do
    256     # IO.inspect(ast, label: "Mod.fun() /2 (#{Macro.to_string(candidate)} #{acc})")
    257 
    258     if Credo.Code.contains_child?(arguments, candidate) do
    259       {nil, :VERIFIED}
    260     else
    261       {ast, acc}
    262     end
    263   end
    264 
    265   # MyModule.my_fun()
    266   defp verify_candidate(
    267          {{:., _, [{:__aliases__, _, mods}, fun_name]}, _, arguments} = ast,
    268          :not_verified = acc,
    269          candidate
    270        )
    271        when is_atom(fun_name) and is_list(mods) and is_list(arguments) do
    272     # IO.inspect(ast, label: "Mod.fun() /3 (#{Macro.to_string(candidate)} #{acc})")
    273 
    274     if Credo.Code.contains_child?(arguments, candidate) do
    275       {nil, :VERIFIED}
    276     else
    277       {ast, acc}
    278     end
    279   end
    280 
    281   # module.my_fun()
    282   defp verify_candidate(
    283          {{:., _, [{module_variable, _, nil}, fun_name]}, _, arguments} = ast,
    284          :not_verified = acc,
    285          candidate
    286        )
    287        when is_atom(fun_name) and is_atom(module_variable) and is_list(arguments) do
    288     # IO.inspect(ast, label: "Mod.fun() /4 (#{Macro.to_string(candidate)} #{acc})")
    289 
    290     if Credo.Code.contains_child?(arguments, candidate) do
    291       {nil, :VERIFIED}
    292     else
    293       {ast, acc}
    294     end
    295   end
    296 
    297   defp verify_candidate(ast, acc, _candidate) do
    298     # IO.inspect(ast, label: "_ (#{Macro.to_string(candidate)} #{acc})")
    299 
    300     {ast, acc}
    301   end
    302 end