|
2 | 2 |
|
3 | 3 | require_relative "codeclimate_diff/version"
|
4 | 4 |
|
5 |
| -require "json" |
6 |
| -require "colorize" |
7 |
| - |
8 | 5 | module CodeclimateDiff
|
9 |
| - def self.generate_baseline |
10 |
| - puts "Generating the baseline. Should take about 5 minutes..." |
11 |
| - `codeclimate analyze -f json > codeclimate_diff_baseline.json` |
12 |
| - puts "Done!" |
13 |
| - end |
14 |
| - |
15 |
| - def self.calculate_changed_filenames(pattern) |
16 |
| - extra_grep_filter = pattern ? " | grep '#{pattern}'" : "" |
17 |
| - files_changed = `git diff --name-only main | grep --invert-match spec/ | grep --extended-regexp '.js$|.rb$'#{extra_grep_filter}` |
18 |
| - files_changed.split("\n") |
19 |
| - end |
20 |
| - |
21 |
| - def self.calculate_issues_in_changed_files(changed_filenames) |
22 |
| - changed_file_issues = [] |
23 |
| - |
24 |
| - changed_filenames.each do |filename| |
25 |
| - next if filename == "codeclimate_diff.rb" # TODO: fix this file's code quality issues when we make a Gem! |
26 |
| - |
27 |
| - puts "Analysing '#{filename}'..." |
28 |
| - result = `codeclimate analyze -f json #{filename}` |
29 |
| - JSON.parse(result).each do |issue| |
30 |
| - next if issue["type"] != "issue" |
31 |
| - |
32 |
| - changed_file_issues.append(issue) |
33 |
| - end |
34 |
| - end |
35 |
| - |
36 |
| - changed_file_issues |
37 |
| - end |
38 |
| - |
39 |
| - def self.calculate_preexisting_issues_in_changed_files(changed_filenames) |
40 |
| - puts "Extracting relevant preexisting issues..." |
41 |
| - all_issues = JSON.parse(File.read("./codeclimate_diff_baseline.json")) |
42 |
| - |
43 |
| - all_issues.filter { |issue| issue.key?("location") && changed_filenames.include?(issue["location"]["path"]) } |
44 |
| - end |
45 |
| - |
46 |
| - def self.remove_closest_match_from_list(issue_to_match, list) |
47 |
| - # check for exact match first |
48 |
| - index = list.index do |issue| |
49 |
| - issue["fingerprint"] == issue_to_match["fingerprint"] && |
50 |
| - issue["location"]["lines"]["begin"] == issue_to_match["location"]["lines"]["begin"] && |
51 |
| - issue["description"] == issue_to_match["description"] |
52 |
| - end |
53 |
| - |
54 |
| - if index |
55 |
| - list.delete_at(index) |
56 |
| - return |
57 |
| - end |
58 |
| - |
59 |
| - # check for same method name (description often has method name or variable name in it) |
60 |
| - index = list.index do |issue| |
61 |
| - issue["fingerprint"] == issue_to_match["fingerprint"] && |
62 |
| - issue["description"] == issue_to_match["description"] |
63 |
| - end |
64 |
| - |
65 |
| - if index |
66 |
| - list.delete_at(index) |
67 |
| - return |
68 |
| - end |
69 |
| - |
70 |
| - # otherwise just remove the first one |
71 |
| - list.pop |
72 |
| - end |
73 |
| - |
74 |
| - def self.sort_issues(preexisting_issues, changed_file_issues) |
75 |
| - puts "Sorting into :preexisting, :new and :fixed lists..." |
76 |
| - |
77 |
| - result = {} |
78 |
| - result[:preexisting] = [] |
79 |
| - result[:new] = [] |
80 |
| - result[:fixed] = [] |
81 |
| - |
82 |
| - # fingerprints are unique per issue type and file |
83 |
| - # so there could be multiple if the same issue shows up multiple times |
84 |
| - # plus line numbers and method names could have changed |
85 |
| - unique_fingerprints = (preexisting_issues + changed_file_issues).map { |issue| issue["fingerprint"] }.uniq |
86 |
| - |
87 |
| - unique_fingerprints.each do |fingerprint| |
88 |
| - baseline_issues = preexisting_issues.filter { |issue| issue["fingerprint"] == fingerprint } |
89 |
| - current_issues = changed_file_issues.filter { |issue| issue["fingerprint"] == fingerprint } |
90 |
| - |
91 |
| - if baseline_issues.count == current_issues.count |
92 |
| - # current issues are most up to date (line numbers could have changed etc.) |
93 |
| - result[:preexisting] += current_issues |
94 |
| - elsif current_issues.count < baseline_issues.count |
95 |
| - # less issues than there were before |
96 |
| - current_issues.each do |issue_to_match| |
97 |
| - CodeclimateDiff.remove_closest_match_from_list(issue_to_match, baseline_issues) |
98 |
| - end |
99 |
| - result[:fixed] += baseline_issues |
100 |
| - else |
101 |
| - # more issues than there were before |
102 |
| - baseline_issues.each do |issue_to_match| |
103 |
| - CodeclimateDiff.remove_closest_match_from_list(issue_to_match, current_issues) |
104 |
| - end |
105 |
| - result[:new] += current_issues |
106 |
| - end |
107 |
| - end |
108 |
| - |
109 |
| - # do a check to make sure the maths works out |
110 |
| - puts "#{preexisting_issues.count} issues in matching files in baseline" |
111 |
| - puts "#{changed_file_issues.count} current issues in matching files" |
112 |
| - |
113 |
| - result |
114 |
| - end |
115 |
| - |
116 |
| - def self.print_issues_in_category(issues_list) |
117 |
| - issues_list.each do |issue| |
118 |
| - filename = issue["location"]["path"] |
119 |
| - line_number = issue["location"]["lines"]["begin"] |
120 |
| - description = issue["description"] |
121 |
| - |
122 |
| - print "\u2022 #{filename}:#{line_number}".encode("utf-8").bold |
123 |
| - puts " #{description}" |
124 |
| - end |
125 |
| - puts "\n" |
126 |
| - end |
127 |
| - |
128 |
| - def self.print_category(bullet_emoji, severity, engine_name, check_name, color) |
129 |
| - message = "#{bullet_emoji} [#{severity}] #{engine_name} #{check_name}:".encode("utf-8") |
130 |
| - |
131 |
| - case color |
132 |
| - when "red" |
133 |
| - puts message.red |
134 |
| - when "yellow" |
135 |
| - puts message.yellow |
136 |
| - when "green" |
137 |
| - puts message.green |
138 |
| - else |
139 |
| - puts message |
140 |
| - end |
141 |
| - end |
142 |
| - |
143 |
| - def self.print_issues(issues_list, color, bullet_emoji) |
144 |
| - issue_categories = issues_list.map { |issue| [issue["engine_name"], issue["check_name"], issue["severity"]] }.uniq |
145 |
| - issue_categories.each do |issue_category| |
146 |
| - engine_name = issue_category[0] |
147 |
| - check_name = issue_category[1] |
148 |
| - severity = issue_category[2] |
149 |
| - issues = issues_list.filter do |issue| |
150 |
| - issue["engine_name"] == engine_name && |
151 |
| - issue["check_name"] == check_name && |
152 |
| - issue["severity"] == severity |
153 |
| - end |
154 |
| - print_category(bullet_emoji, severity, engine_name, check_name, color) |
155 |
| - print_issues_in_category(issues) |
156 |
| - end |
157 |
| - end |
158 |
| - |
159 |
| - def self.print_result(sorted_issues, show_preexisting) |
160 |
| - if show_preexisting |
161 |
| - preexisting_issues = sorted_issues[:preexisting] |
162 |
| - if preexisting_issues.count.positive? |
163 |
| - puts "\n#{preexisting_issues.count} preexisting issues in changed files:\n".bold.yellow |
164 |
| - print_issues(preexisting_issues, "yellow", "\u2718") |
165 |
| - else |
166 |
| - puts "\n0 issues in changed files!".encode("utf-8").bold.green |
167 |
| - end |
168 |
| - end |
169 |
| - |
170 |
| - new_issues = sorted_issues[:new] |
171 |
| - if new_issues.count.positive? |
172 |
| - puts "\n#{new_issues.count} new issues:\n".bold.red |
173 |
| - print_issues(new_issues, "red", "\u2718") |
174 |
| - else |
175 |
| - puts "\n0 new issues :)\n".encode("utf-8").bold |
176 |
| - end |
177 |
| - |
178 |
| - fixed_issues = sorted_issues[:fixed] |
179 |
| - if fixed_issues.count.positive? |
180 |
| - puts "\n#{fixed_issues.count} fixed issues: \n".encode("utf-8").bold.green |
181 |
| - print_issues(fixed_issues, "green", "\u2714") |
182 |
| - else |
183 |
| - puts "\n0 fixed issues\n".bold |
184 |
| - end |
185 |
| - end |
186 |
| - |
187 |
| - def self.print_call_to_action(sorted_issues) |
188 |
| - fixed_count = sorted_issues[:fixed].count |
189 |
| - new_count = sorted_issues[:new].count |
190 |
| - outstanding_count = sorted_issues[:preexisting].count + new_count |
191 |
| - if fixed_count > new_count |
192 |
| - puts "\n\u{1F389}\u{1F389} Well done! You made the code even better!! \u{1F389}\u{1F389} \n".bold.green.encode("utf-8") |
193 |
| - elsif new_count > fixed_count |
194 |
| - puts "\n\ Uh oh, you've introduced more issues than you've fixed. Better fix that! \n".bold.red.encode("utf-8") |
195 |
| - elsif outstanding_count.positive? |
196 |
| - puts "\n\ Why don't you see if you can fix some of those outstanding issues while you're here? \n".bold.encode("utf-8") |
197 |
| - else |
198 |
| - puts "\n\u{1F389}\u{1F389} Nothing to do here, the code is immaculate!! \u{1F389}\u{1F389} \n".bold.green.encode("utf-8") |
199 |
| - end |
200 |
| - end |
201 |
| - |
202 |
| - def self.run_diff_on_branch(pattern, show_preexisting: true) |
203 |
| - changed_filenames = calculate_changed_filenames(pattern) |
204 |
| - |
205 |
| - changed_file_issues = calculate_issues_in_changed_files(changed_filenames) |
206 |
| - |
207 |
| - preexisting_issues = calculate_preexisting_issues_in_changed_files(changed_filenames) |
208 |
| - |
209 |
| - sorted_issues = sort_issues(preexisting_issues, changed_file_issues) |
210 |
| - |
211 |
| - print_result(sorted_issues, show_preexisting) |
212 |
| - print_call_to_action(sorted_issues) |
213 |
| - end |
| 6 | + |
214 | 7 | end
|
0 commit comments