]>
Commit | Line | Data |
---|---|---|
1 | # frozen_string_literal: true | |
2 | ||
3 | require 'pronto' | |
4 | require 'shellwords' | |
5 | ||
6 | module Pronto | |
7 | class ESLintNpm < Runner | |
8 | CONFIG_FILE = '.pronto_eslint_npm.yml'.freeze | |
9 | CONFIG_KEYS = %w[eslint_executable files_to_lint cmd_line_opts].freeze | |
10 | ||
11 | attr_writer :eslint_executable, :cmd_line_opts | |
12 | ||
13 | def eslint_executable | |
14 | @eslint_executable || 'eslint' | |
15 | end | |
16 | ||
17 | def files_to_lint | |
18 | @files_to_lint || /(\.js|\.es6)$/ | |
19 | end | |
20 | ||
21 | def cmd_line_opts | |
22 | @cmd_line_opts || '' | |
23 | end | |
24 | ||
25 | def files_to_lint=(regexp) | |
26 | @files_to_lint = regexp.is_a?(Regexp) && regexp || Regexp.new(regexp) | |
27 | end | |
28 | ||
29 | def config_options | |
30 | @config_options ||= | |
31 | begin | |
32 | config_file = File.join(repo_path, CONFIG_FILE) | |
33 | File.exist?(config_file) && YAML.load_file(config_file) || {} | |
34 | end | |
35 | end | |
36 | ||
37 | def read_config | |
38 | config_options.each do |key, val| | |
39 | next unless CONFIG_KEYS.include?(key.to_s) | |
40 | send("#{key}=", val) | |
41 | end | |
42 | end | |
43 | ||
44 | def run | |
45 | return [] if !@patches || @patches.count.zero? | |
46 | ||
47 | read_config | |
48 | ||
49 | @patches | |
50 | .select { |patch| patch.additions > 0 } | |
51 | .select { |patch| js_file?(patch.new_file_full_path) } | |
52 | .map { |patch| inspect(patch) } | |
53 | .flatten.compact | |
54 | end | |
55 | ||
56 | private | |
57 | ||
58 | def repo_path | |
59 | @repo_path ||= @patches.first.repo.path | |
60 | end | |
61 | ||
62 | def inspect(patch) | |
63 | offences = run_eslint(patch) | |
64 | clean_up_eslint_output(offences) | |
65 | .map do |offence| | |
66 | patch | |
67 | .added_lines | |
68 | .select { |line| line.new_lineno == offence['line'] } | |
69 | .map { |line| new_message(offence, line) } | |
70 | end | |
71 | end | |
72 | ||
73 | def new_message(offence, line) | |
74 | path = line.patch.delta.new_file[:path] | |
75 | level = :warning | |
76 | ||
77 | Message.new(path, line, level, offence['message'], nil, self.class) | |
78 | end | |
79 | ||
80 | def js_file?(path) | |
81 | files_to_lint =~ path.to_s | |
82 | end | |
83 | ||
84 | def run_eslint(patch) | |
85 | Dir.chdir(repo_path) do | |
86 | JSON.parse `#{eslint_command_line(patch.new_file_full_path.to_s)}` | |
87 | end | |
88 | end | |
89 | ||
90 | def eslint_command_line(path) | |
91 | "#{eslint_executable} #{cmd_line_opts} #{Shellwords.escape(path)} -f json" | |
92 | end | |
93 | ||
94 | def clean_up_eslint_output(output) | |
95 | # 1. Filter out offences without a warning or error | |
96 | # 2. Get the messages for that file | |
97 | # 3. Ignore errors without a line number for now | |
98 | output | |
99 | .select { |offence| offence['errorCount'] + offence['warningCount'] > 0 } | |
100 | .map { |offence| offence['messages'] } | |
101 | .flatten.select { |offence| offence['line'] } | |
102 | end | |
103 | end | |
104 | end |