zf

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

collector.ex (4295B)


      1 defmodule Credo.Check.Consistency.SpaceAroundOperators.Collector do
      2   @moduledoc false
      3 
      4   use Credo.Check.Consistency.Collector
      5 
      6   import Credo.Check.Consistency.SpaceAroundOperators.SpaceHelper,
      7     only: [
      8       operator?: 1,
      9       space_between?: 2,
     10       no_space_between?: 2,
     11       usually_no_space_before?: 3,
     12       usually_no_space_after?: 3
     13     ]
     14 
     15   def collect_matches(source_file, _params) do
     16     source_file
     17     |> Credo.Code.to_tokens()
     18     |> traverse_tokens(&record_spaces(&1, &2, &3, &4), %{})
     19   end
     20 
     21   def find_locations_not_matching(expected, source_file) do
     22     source_file
     23     |> Credo.Code.to_tokens()
     24     |> traverse_tokens(&record_not_matching(expected, &1, &2, &3, &4), [])
     25     |> Enum.reverse()
     26   end
     27 
     28   defp traverse_tokens(tokens, callback, acc) do
     29     tokens
     30     |> skip_specs_types_captures_and_binary_patterns()
     31     |> case do
     32       [prev | [current | [next | rest]]] ->
     33         acc =
     34           if operator?(current) do
     35             callback.(prev, current, next, acc)
     36           else
     37             acc
     38           end
     39 
     40         traverse_tokens([current | [next | rest]], callback, acc)
     41 
     42       _ ->
     43         acc
     44     end
     45   end
     46 
     47   defp skip_specs_types_captures_and_binary_patterns([{:at_op, {line, _, _}, :@} | tokens]) do
     48     case tokens do
     49       # @spec - drop whole line
     50       [{:identifier, _, :spec} | tokens] ->
     51         drop_while_on_line(tokens, line)
     52 
     53       # @type - drop whole line
     54       [{:identifier, _, :type} | tokens] ->
     55         drop_while_on_line(tokens, line)
     56 
     57       tokens ->
     58         tokens
     59     end
     60   end
     61 
     62   defp skip_specs_types_captures_and_binary_patterns([{:capture_op, _, _} | tokens]) do
     63     drop_while_in_fun_capture(tokens)
     64   end
     65 
     66   # When quoting &//2 (which captures the / operator via &fun_name/2), the
     67   # {:capture_op, _, :&} token becomes an {:identifier, _, :&} token ...
     68   defp skip_specs_types_captures_and_binary_patterns([
     69          {:identifier, _, :&} | [{:identifier, _, :/} | tokens]
     70        ]) do
     71     drop_while_in_fun_capture(tokens)
     72   end
     73 
     74   defp skip_specs_types_captures_and_binary_patterns([{:"<<", _} | tokens]) do
     75     drop_while_in_binary_pattern(tokens)
     76   end
     77 
     78   defp skip_specs_types_captures_and_binary_patterns(tokens), do: tokens
     79 
     80   defp drop_while_on_line(tokens, line) do
     81     Enum.drop_while(tokens, fn
     82       {_, {^line, _, _}} -> true
     83       {_, {^line, _, _}, _} -> true
     84       _ -> false
     85     end)
     86   end
     87 
     88   defp drop_while_in_fun_capture(tokens) do
     89     Enum.drop_while(tokens, fn
     90       # :erlang_module
     91       {:atom, _, _} ->
     92         true
     93 
     94       # ElixirModule (Elixir >= 1.6.0)
     95       {:alias, _, _} ->
     96         true
     97 
     98       # ElixirModule
     99       {:aliases, _, _} ->
    100         true
    101 
    102       # function_name
    103       {:identifier, _, _} ->
    104         true
    105 
    106       # unquote
    107       {:paren_identifier, _, :unquote} ->
    108         true
    109 
    110       # @module_attribute
    111       {:at_op, _, _} ->
    112         true
    113 
    114       {:mult_op, _, :/} ->
    115         true
    116 
    117       {:., _} ->
    118         true
    119 
    120       {:"(", _} ->
    121         true
    122 
    123       {:")", _} ->
    124         true
    125 
    126       _ ->
    127         false
    128     end)
    129   end
    130 
    131   defp drop_while_in_binary_pattern(tokens) do
    132     Enum.drop_while(tokens, fn
    133       :">>" ->
    134         false
    135 
    136       _ ->
    137         true
    138     end)
    139   end
    140 
    141   defp record_spaces(prev, current, next, acc) do
    142     acc
    143     |> increment(:with_space, with_space?(prev, current, next))
    144     |> increment(:without_space, without_space?(prev, current, next))
    145   end
    146 
    147   defp increment(map, key, matches) do
    148     if matches do
    149       Map.update(map, key, 1, &(&1 + 1))
    150     else
    151       map
    152     end
    153   end
    154 
    155   defp record_not_matching(expected, prev, current, next, acc) do
    156     match_found =
    157       case expected do
    158         :with_space ->
    159           without_space?(prev, current, next)
    160 
    161         :without_space ->
    162           with_space?(prev, current, next)
    163       end
    164 
    165     if match_found do
    166       {_, {line_no, column, _}, trigger} = current
    167 
    168       [[line_no: line_no, column: column, trigger: trigger] | acc]
    169     else
    170       acc
    171     end
    172   end
    173 
    174   defp with_space?(prev, op, next) do
    175     space_between?(prev, op) || space_between?(op, next)
    176   end
    177 
    178   defp without_space?(prev, op, next) do
    179     (!usually_no_space_before?(prev, op, next) && no_space_between?(prev, op)) ||
    180       (!usually_no_space_after?(prev, op, next) && no_space_between?(op, next) &&
    181          !(elem(next, 0) == :eol))
    182   end
    183 end