zf

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

long_quote_blocks.ex (4126B)


      1 defmodule Credo.Check.Refactor.LongQuoteBlocks do
      2   use Credo.Check,
      3     base_priority: :high,
      4     param_defaults: [max_line_count: 150, ignore_comments: false],
      5     explanations: [
      6       check: """
      7       Long `quote` blocks are generally an indication that too much is done inside
      8       them.
      9 
     10       Let's look at why this is problematic:
     11 
     12           defmodule MetaCommand do
     13             def __using__(opts \\\\ []) do
     14               modes = opts[:modes]
     15               command_name = opts[:command_name]
     16 
     17               quote do
     18                 def run(filename) do
     19                   contents =
     20                     if File.exists?(filename) do
     21                       {:ok, file} = File.open(filename, unquote(modes))
     22                       {:ok, contents} = IO.read(file, :line)
     23                       File.close(file)
     24                       contents
     25                     else
     26                       ""
     27                     end
     28 
     29                   case contents do
     30                     "" ->
     31                       # ...
     32                     unquote(command_name) <> rest ->
     33                       # ...
     34                   end
     35                 end
     36 
     37                 # ...
     38               end
     39             end
     40           end
     41 
     42       A cleaner solution would be to call "regular" functions outside the
     43       `quote` block to perform the actual work.
     44 
     45           defmodule MyMetaCommand do
     46             def __using__(opts \\\\ []) do
     47               modes = opts[:modes]
     48               command_name = opts[:command_name]
     49 
     50               quote do
     51                 def run(filename) do
     52                   MyMetaCommand.run_on_file(filename, unquote(modes), unquote(command_name))
     53                 end
     54 
     55                 # ...
     56               end
     57             end
     58 
     59             def run_on_file(filename, modes, command_name) do
     60               contents =
     61                 # actual implementation
     62             end
     63           end
     64 
     65       This way it is easier to reason about what is actually happening. And to debug
     66       it.
     67       """,
     68       params: [
     69         max_line_count: "The maximum number of lines a quote block should be allowed to have.",
     70         ignore_comments: "Ignores comments when counting the lines of a `quote` block."
     71       ]
     72     ]
     73 
     74   alias Credo.IssueMeta
     75 
     76   @doc false
     77   @impl true
     78   def run(%SourceFile{} = source_file, params) do
     79     issue_meta = IssueMeta.for(source_file, params)
     80     max_line_count = Params.get(params, :max_line_count, __MODULE__)
     81     ignore_comments = Params.get(params, :ignore_comments, __MODULE__)
     82 
     83     Credo.Code.prewalk(
     84       source_file,
     85       &traverse(&1, &2, issue_meta, max_line_count, ignore_comments)
     86     )
     87   end
     88 
     89   # TODO: consider for experimental check front-loader (ast)
     90   defp traverse(
     91          {:quote, meta, arguments} = ast,
     92          issues,
     93          issue_meta,
     94          max_line_count,
     95          ignore_comments
     96        ) do
     97     max_line_no = Credo.Code.prewalk(arguments, &find_max_line_no(&1, &2), 0)
     98     line_count = max_line_no - meta[:line]
     99 
    100     issue =
    101       if line_count > max_line_count do
    102         source_file = IssueMeta.source_file(issue_meta)
    103 
    104         lines =
    105           source_file
    106           |> Credo.Code.to_lines()
    107           |> Enum.slice(meta[:line] - 1, line_count)
    108 
    109         lines =
    110           if ignore_comments do
    111             Enum.reject(lines, fn {_line_no, line} ->
    112               Regex.run(~r/^\s*#/, line)
    113             end)
    114           else
    115             lines
    116           end
    117 
    118         if Enum.count(lines) > max_line_count do
    119           issue_for(issue_meta, meta[:line])
    120         end
    121       end
    122 
    123     {ast, issues ++ List.wrap(issue)}
    124   end
    125 
    126   defp traverse(ast, issues, _issue_meta, _max_line_count, _ignore_comments) do
    127     {ast, issues}
    128   end
    129 
    130   defp find_max_line_no({_, meta, _} = ast, max_line_no) do
    131     line_no = meta[:line] || 0
    132 
    133     if line_no > max_line_no do
    134       {ast, line_no}
    135     else
    136       {ast, max_line_no}
    137     end
    138   end
    139 
    140   defp find_max_line_no(ast, max_line_no) do
    141     {ast, max_line_no}
    142   end
    143 
    144   defp issue_for(issue_meta, line_no) do
    145     format_issue(
    146       issue_meta,
    147       message: "Avoid long quote blocks.",
    148       trigger: "quote",
    149       line_no: line_no
    150     )
    151   end
    152 end