]>
Commit | Line | Data |
---|---|---|
9c1df047 PB |
1 | # frozen_string_literal: true |
2 | ||
3 | require 'pronto' | |
4 | require 'shellwords' | |
5 | ||
6 | module Pronto | |
7 | module Hlint | |
8 | class Runner < Pronto::Runner | |
9 | CONFIG_FILE = '.pronto_hlint.yml'.freeze | |
10 | CONFIG_KEYS = %w[hlint_executable files_to_lint cmd_line_opts].freeze | |
11 | ||
12 | attr_writer :hlint_executable, :cmd_line_opts | |
13 | ||
14 | def hlint_executable | |
15 | @hlint_executable || 'hlint' | |
16 | end | |
17 | ||
18 | def files_to_lint | |
19 | @files_to_lint || /(\.hs)$/ | |
20 | end | |
21 | ||
22 | def cmd_line_opts | |
23 | @cmd_line_opts || '' | |
24 | end | |
25 | ||
26 | def files_to_lint=(regexp) | |
27 | @files_to_lint = regexp.is_a?(Regexp) && regexp || Regexp.new(regexp) | |
28 | end | |
29 | ||
30 | def config_options | |
31 | @config_options ||= | |
32 | begin | |
33 | config_file = File.join(repo_path, CONFIG_FILE) | |
34 | File.exist?(config_file) && YAML.load_file(config_file) || {} | |
35 | end | |
36 | end | |
37 | ||
38 | def read_config | |
39 | config_options.each do |key, val| | |
40 | next unless CONFIG_KEYS.include?(key.to_s) | |
41 | send("#{key}=", val) | |
42 | end | |
43 | end | |
44 | ||
45 | def run | |
46 | return [] if !@patches || @patches.count.zero? | |
47 | ||
48 | read_config | |
49 | ||
50 | @patches | |
51 | .select { |patch| patch.additions > 0 } | |
52 | .select { |patch| hs_file?(patch.new_file_full_path) } | |
53 | .map { |patch| inspect(patch) } | |
54 | .flatten.compact | |
55 | end | |
56 | ||
57 | private | |
58 | ||
59 | def repo_path | |
60 | @repo_path ||= @patches.first.repo.path | |
61 | end | |
62 | ||
63 | def inspect(patch) | |
64 | offences = run_hlint(patch) | |
65 | offences | |
66 | .map do |offence| | |
67 | patch | |
68 | .added_lines | |
69 | .select { |line| (offence['startLine']..offence['endLine']).include?(line.new_lineno) } | |
70 | .map { |line| new_message(offence, line) } | |
71 | end | |
72 | end | |
73 | ||
74 | def new_message(offence, line) | |
75 | path = line.patch.delta.new_file[:path] | |
76 | level = hlint_severity_to_pronto_level(offence['severity']) || :warning | |
77 | ||
78 | text = <<~EOF | |
79 | #{offence['severity']} offence detected by Hlint. Hint is: `#{offence['hint']}`. | |
80 | ||
81 | Consider changing the code from | |
82 | ``` | |
83 | #{offence['from']} | |
84 | ``` | |
85 | to | |
86 | ``` | |
87 | #{offence['to']} | |
88 | ``` | |
89 | EOF | |
90 | ||
91 | Message.new(path, line, level, text, nil, self.class) | |
92 | end | |
93 | ||
94 | def hlint_severity_to_pronto_level(severity) | |
95 | case severity | |
96 | when "Error" | |
97 | :error | |
98 | when "Warning" | |
99 | :warning | |
100 | when "Suggestion" | |
101 | :info | |
102 | end | |
103 | end | |
104 | ||
105 | def hs_file?(path) | |
106 | files_to_lint =~ path.to_s | |
107 | end | |
108 | ||
109 | def run_hlint(patch) | |
110 | Dir.chdir(repo_path) do | |
111 | JSON.parse `#{hlint_command_line(patch.new_file_full_path.to_s)}` | |
112 | end | |
113 | end | |
114 | ||
115 | def hlint_command_line(path) | |
116 | "#{hlint_executable} #{cmd_line_opts} #{Shellwords.escape(path)} --json" | |
117 | end | |
118 | end | |
119 | end | |
120 | end |