zf

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

explain_command.ex (6148B)


      1 defmodule Credo.CLI.Command.Explain.ExplainCommand do
      2   @moduledoc false
      3 
      4   use Credo.CLI.Command,
      5     short_description: "Show code object and explain why it is/might be an issue",
      6     cli_switches: Credo.CLI.Command.Suggest.SuggestCommand.cli_switches()
      7 
      8   alias Credo.Check
      9   alias Credo.CLI.Command.Explain.ExplainOutput, as: Output
     10   alias Credo.CLI.Filename
     11   alias Credo.CLI.Task
     12   alias Credo.Execution
     13   alias Credo.Issue
     14   alias Credo.SourceFile
     15 
     16   def init(exec) do
     17     exec
     18     |> Execution.put_pipeline(__MODULE__.ExplainIssue,
     19       validate_given_location: [
     20         {__MODULE__.ExplainIssuePreCheck, []}
     21       ],
     22       load_and_validate_source_files: [
     23         {Task.LoadAndValidateSourceFiles, []}
     24       ],
     25       prepare_analysis: [
     26         {Task.PrepareChecksToRun, []}
     27       ],
     28       run_analysis: [
     29         {Task.RunChecks, []}
     30       ],
     31       filter_issues: [
     32         {Task.SetRelevantIssues, []}
     33       ],
     34       print_explanation: [
     35         {__MODULE__.ExplainIssue, []}
     36       ]
     37     )
     38     |> Execution.put_pipeline(__MODULE__.ExplainCheck,
     39       print_explanation: [
     40         {__MODULE__.ExplainCheck, []}
     41       ]
     42     )
     43   end
     44 
     45   @doc false
     46   def call(%Execution{help: true} = exec, _opts), do: Output.print_help(exec)
     47 
     48   def call(exec, _opts) do
     49     filename = get_filename_from_args(exec)
     50 
     51     cond do
     52       Filename.contains_line_no?(filename) ->
     53         Execution.run_pipeline(exec, __MODULE__.ExplainIssue)
     54 
     55       Check.defined?("Elixir.#{filename}") ->
     56         Execution.run_pipeline(exec, __MODULE__.ExplainCheck)
     57 
     58       true ->
     59         Output.print_help(exec)
     60     end
     61   end
     62 
     63   @doc false
     64   def get_filename_from_args(exec) do
     65     exec.cli_options.args
     66     |> List.wrap()
     67     |> List.first()
     68   end
     69 
     70   defmodule ExplainCheck do
     71     use Credo.Execution.Task
     72 
     73     alias Credo.CLI.Command.Explain.ExplainCommand
     74 
     75     def call(exec, _opts) do
     76       check_name = ExplainCommand.get_filename_from_args(exec)
     77       check = :"Elixir.#{check_name}"
     78       explanations = [cast_to_explanation(check)]
     79 
     80       Output.print_after_info(explanations, exec, nil, nil)
     81 
     82       exec
     83     end
     84 
     85     defp cast_to_explanation(check) do
     86       %{
     87         category: check.category,
     88         check: check,
     89         explanation_for_issue: check.explanation,
     90         priority: check.base_priority
     91       }
     92     end
     93   end
     94 
     95   defmodule ExplainIssuePreCheck do
     96     use Credo.Execution.Task
     97 
     98     alias Credo.CLI.Command.Explain.ExplainCommand
     99 
    100     def call(exec, _opts) do
    101       filename_with_location = ExplainCommand.get_filename_from_args(exec)
    102       working_dir = Execution.working_dir(exec)
    103 
    104       filename =
    105         filename_with_location
    106         |> String.split(":")
    107         |> List.first()
    108         |> Path.expand()
    109 
    110       if path_contains_file?(working_dir, filename) do
    111         exec
    112       else
    113         Execution.halt(exec, """
    114         Given location is not part of the working dir.
    115 
    116           Location:     #{filename_with_location}
    117           Working dir:  #{working_dir}
    118         """)
    119       end
    120     end
    121 
    122     # def error(exec, _opts) do
    123     #   halt_message = Execution.get_halt_message(exec)
    124 
    125     #   UI.warn([:red, "** (explain) ", halt_message])
    126 
    127     #   exec
    128     # end
    129 
    130     defp path_contains_file?(path, filename) do
    131       case Path.relative_to(filename, path) do
    132         ^filename -> false
    133         _ -> true
    134       end
    135     end
    136   end
    137 
    138   defmodule ExplainIssue do
    139     use Credo.Execution.Task
    140 
    141     alias Credo.CLI.Command.Explain.ExplainCommand
    142 
    143     def call(exec, _opts) do
    144       filename = ExplainCommand.get_filename_from_args(exec)
    145 
    146       source_files = Execution.get_source_files(exec)
    147 
    148       filename
    149       |> String.split(":")
    150       |> print_result(source_files, exec)
    151     end
    152 
    153     def print_result([filename], source_files, exec) do
    154       print_result([filename, nil, nil], source_files, exec)
    155     end
    156 
    157     def print_result([filename, line_no], source_files, exec) do
    158       print_result([filename, line_no, nil], source_files, exec)
    159     end
    160 
    161     def print_result([filename, line_no, column], source_files, exec) do
    162       source_file = Enum.find(source_files, &(&1.filename == filename))
    163 
    164       if source_file do
    165         explanations =
    166           exec
    167           |> Execution.get_issues(source_file.filename)
    168           |> filter_issues(line_no, column)
    169           |> Enum.map(&cast_to_explanation(&1, source_file))
    170 
    171         Output.print_after_info(explanations, exec, line_no, column)
    172 
    173         exec
    174       else
    175         Execution.halt(exec, "Could not find source file: #{filename}")
    176       end
    177     end
    178 
    179     defp cast_to_explanation(issue, source_file) do
    180       %{
    181         category: issue.category,
    182         check: issue.check,
    183         column: issue.column,
    184         explanation_for_issue: issue.check.explanation,
    185         filename: issue.filename,
    186         line_no: issue.line_no,
    187         message: issue.message,
    188         priority: issue.priority,
    189         related_code: find_related_code(source_file, issue.line_no),
    190         scope: issue.scope,
    191         trigger: issue.trigger
    192       }
    193     end
    194 
    195     defp find_related_code(source_file, line_no) do
    196       [
    197         get_source_line(source_file, line_no - 2),
    198         get_source_line(source_file, line_no - 1),
    199         get_source_line(source_file, line_no),
    200         get_source_line(source_file, line_no + 1),
    201         get_source_line(source_file, line_no + 2)
    202       ]
    203       |> Enum.reject(&is_nil/1)
    204     end
    205 
    206     defp get_source_line(_, line_no) when line_no < 1 do
    207       nil
    208     end
    209 
    210     defp get_source_line(source_file, line_no) do
    211       line = SourceFile.line_at(source_file, line_no)
    212 
    213       if line do
    214         {line_no, line}
    215       end
    216     end
    217 
    218     defp filter_issues(issues, line_no, nil) do
    219       line_no = line_no |> String.to_integer()
    220       issues |> Enum.filter(&filter_issue(&1, line_no, nil))
    221     end
    222 
    223     defp filter_issues(issues, line_no, column) do
    224       line_no = line_no |> String.to_integer()
    225       column = column |> String.to_integer()
    226 
    227       issues |> Enum.filter(&filter_issue(&1, line_no, column))
    228     end
    229 
    230     defp filter_issue(%Issue{line_no: a, column: b}, a, b), do: true
    231     defp filter_issue(%Issue{line_no: a}, a, _), do: true
    232     defp filter_issue(_, _, _), do: false
    233   end
    234 end