Skip to content

Commit db41db1

Browse files
Merge branch 'main' into configuration
2 parents da7d09b + 628eca3 commit db41db1

File tree

4 files changed

+193
-164
lines changed

4 files changed

+193
-164
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# frozen_string_literal: true
2+
3+
require "json"
4+
require "colorize"
5+
6+
module CodeclimateDiff
7+
class CodeclimateWrapper
8+
def run_codeclimate(filename = "")
9+
`codeclimate analyze -f json #{filename}`
10+
end
11+
end
12+
end

lib/codeclimate_diff/issue_sorter.rb

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# frozen_string_literal: true
2+
3+
module CodeclimateDiff
4+
class IssueSorter
5+
def self.remove_closest_match_from_list(issue_to_match, list)
6+
# check for exact match first
7+
index = list.index do |issue|
8+
issue["fingerprint"] == issue_to_match["fingerprint"] &&
9+
issue["location"]["lines"]["begin"] == issue_to_match["location"]["lines"]["begin"] &&
10+
issue["description"] == issue_to_match["description"]
11+
end
12+
13+
if index
14+
list.delete_at(index)
15+
return
16+
end
17+
18+
# check for same method name (description often has method name or variable name in it)
19+
index = list.index do |issue|
20+
issue["fingerprint"] == issue_to_match["fingerprint"] &&
21+
issue["description"] == issue_to_match["description"]
22+
end
23+
24+
if index
25+
list.delete_at(index)
26+
return
27+
end
28+
29+
# otherwise just remove the first one
30+
list.pop
31+
end
32+
33+
def self.sort_issues(preexisting_issues, changed_file_issues)
34+
puts "Sorting into :preexisting, :new and :fixed lists..."
35+
36+
result = {}
37+
result[:preexisting] = []
38+
result[:new] = []
39+
result[:fixed] = []
40+
41+
# fingerprints are unique per issue type and file
42+
# so there could be multiple if the same issue shows up multiple times
43+
# plus line numbers and method names could have changed
44+
unique_fingerprints = (preexisting_issues + changed_file_issues).map { |issue| issue["fingerprint"] }.uniq
45+
46+
unique_fingerprints.each do |fingerprint|
47+
baseline_issues = preexisting_issues.filter { |issue| issue["fingerprint"] == fingerprint }
48+
current_issues = changed_file_issues.filter { |issue| issue["fingerprint"] == fingerprint }
49+
50+
if baseline_issues.count == current_issues.count
51+
# current issues are most up to date (line numbers could have changed etc.)
52+
result[:preexisting] += current_issues
53+
elsif current_issues.count < baseline_issues.count
54+
# less issues than there were before
55+
current_issues.each do |issue_to_match|
56+
CodeclimateDiff.remove_closest_match_from_list(issue_to_match, baseline_issues)
57+
end
58+
result[:fixed] += baseline_issues
59+
else
60+
# more issues than there were before
61+
baseline_issues.each do |issue_to_match|
62+
CodeclimateDiff.remove_closest_match_from_list(issue_to_match, current_issues)
63+
end
64+
result[:new] += current_issues
65+
end
66+
end
67+
68+
# do a check to make sure the maths works out
69+
puts "#{preexisting_issues.count} issues in matching files in baseline"
70+
puts "#{changed_file_issues.count} current issues in matching files"
71+
72+
result
73+
end
74+
end
75+
end
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# frozen_string_literal: true
2+
3+
require "json"
4+
require "colorize"
5+
6+
module CodeclimateDiff
7+
class ResultPrinter
8+
def self.print_issues_in_category(issues_list)
9+
issues_list.each do |issue|
10+
filename = issue["location"]["path"]
11+
line_number = issue["location"]["lines"]["begin"]
12+
description = issue["description"]
13+
14+
print "\u2022 #{filename}:#{line_number}".encode("utf-8").bold
15+
puts " #{description}"
16+
end
17+
puts "\n"
18+
end
19+
20+
def self.print_category(bullet_emoji, severity, engine_name, check_name, color)
21+
message = "#{bullet_emoji} [#{severity}] #{engine_name} #{check_name}:".encode("utf-8")
22+
23+
case color
24+
when "red"
25+
puts message.red
26+
when "yellow"
27+
puts message.yellow
28+
when "green"
29+
puts message.green
30+
else
31+
puts message
32+
end
33+
end
34+
35+
def self.print_issues(issues_list, color, bullet_emoji)
36+
issue_categories = issues_list.map { |issue| [issue["engine_name"], issue["check_name"], issue["severity"]] }.uniq
37+
issue_categories.each do |issue_category|
38+
engine_name = issue_category[0]
39+
check_name = issue_category[1]
40+
severity = issue_category[2]
41+
issues = issues_list.filter do |issue|
42+
issue["engine_name"] == engine_name &&
43+
issue["check_name"] == check_name &&
44+
issue["severity"] == severity
45+
end
46+
print_category(bullet_emoji, severity, engine_name, check_name, color)
47+
print_issues_in_category(issues)
48+
end
49+
end
50+
51+
def self.print_result(sorted_issues, show_preexisting)
52+
if show_preexisting
53+
preexisting_issues = sorted_issues[:preexisting]
54+
if preexisting_issues.count.positive?
55+
puts "\n#{preexisting_issues.count} preexisting issues in changed files:\n".bold.yellow
56+
print_issues(preexisting_issues, "yellow", "\u2718")
57+
else
58+
puts "\n0 issues in changed files!".encode("utf-8").bold.green
59+
end
60+
end
61+
62+
new_issues = sorted_issues[:new]
63+
if new_issues.count.positive?
64+
puts "\n#{new_issues.count} new issues:\n".bold.red
65+
print_issues(new_issues, "red", "\u2718")
66+
else
67+
puts "\n0 new issues :)\n".encode("utf-8").bold
68+
end
69+
70+
fixed_issues = sorted_issues[:fixed]
71+
if fixed_issues.count.positive?
72+
puts "\n#{fixed_issues.count} fixed issues: \n".encode("utf-8").bold.green
73+
print_issues(fixed_issues, "green", "\u2714")
74+
else
75+
puts "\n0 fixed issues\n".bold
76+
end
77+
end
78+
79+
def self.print_call_to_action(sorted_issues)
80+
fixed_count = sorted_issues[:fixed].count
81+
new_count = sorted_issues[:new].count
82+
outstanding_count = sorted_issues[:preexisting].count + new_count
83+
if fixed_count > new_count
84+
puts "\n\u{1F389}\u{1F389} Well done! You made the code even better!! \u{1F389}\u{1F389} \n".bold.green.encode("utf-8")
85+
elsif new_count > fixed_count
86+
puts "\n\ Uh oh, you've introduced more issues than you've fixed. Better fix that! \n".bold.red.encode("utf-8")
87+
elsif outstanding_count.positive?
88+
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")
89+
else
90+
puts "\n\u{1F389}\u{1F389} Nothing to do here, the code is immaculate!! \u{1F389}\u{1F389} \n".bold.green.encode("utf-8")
91+
end
92+
end
93+
end
94+
end

lib/codeclimate_diff/runner.rb

Lines changed: 12 additions & 164 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,12 @@
22

33
require "json"
44
require "colorize"
5+
require_relative "./codeclimate_wrapper"
6+
require_relative "./result_printer"
7+
require_relative "./issue_sorter"
58

69
module CodeclimateDiff
710
class Runner
8-
def self.generate_baseline
9-
puts "Generating the baseline. Should take about 5 minutes..."
10-
`codeclimate analyze -f json > codeclimate_diff_baseline.json`
11-
puts "Done!"
12-
end
13-
1411
def self.calculate_changed_filenames(pattern)
1512
extra_grep_filter = pattern ? " | grep '#{pattern}'" : ""
1613
files_changed = `git diff --name-only main | grep --invert-match spec/ | grep --extended-regexp '.js$|.rb$'#{extra_grep_filter}`
@@ -24,7 +21,7 @@ def self.calculate_issues_in_changed_files(changed_filenames)
2421
next if filename == "codeclimate_diff.rb" # TODO: fix this file's code quality issues when we make a Gem!
2522

2623
puts "Analysing '#{filename}'..."
27-
result = `codeclimate analyze -f json #{filename}`
24+
result = CodeclimateWrapper.new.run_codeclimate(filename)
2825
JSON.parse(result).each do |issue|
2926
next if issue["type"] != "issue"
3027

@@ -42,160 +39,11 @@ def self.calculate_preexisting_issues_in_changed_files(changed_filenames)
4239
all_issues.filter { |issue| issue.key?("location") && changed_filenames.include?(issue["location"]["path"]) }
4340
end
4441

45-
def self.remove_closest_match_from_list(issue_to_match, list)
46-
# check for exact match first
47-
index = list.index do |issue|
48-
issue["fingerprint"] == issue_to_match["fingerprint"] &&
49-
issue["location"]["lines"]["begin"] == issue_to_match["location"]["lines"]["begin"] &&
50-
issue["description"] == issue_to_match["description"]
51-
end
52-
53-
if index
54-
list.delete_at(index)
55-
return
56-
end
57-
58-
# check for same method name (description often has method name or variable name in it)
59-
index = list.index do |issue|
60-
issue["fingerprint"] == issue_to_match["fingerprint"] &&
61-
issue["description"] == issue_to_match["description"]
62-
end
63-
64-
if index
65-
list.delete_at(index)
66-
return
67-
end
68-
69-
# otherwise just remove the first one
70-
list.pop
71-
end
72-
73-
def self.sort_issues(preexisting_issues, changed_file_issues)
74-
puts "Sorting into :preexisting, :new and :fixed lists..."
75-
76-
result = {}
77-
result[:preexisting] = []
78-
result[:new] = []
79-
result[:fixed] = []
80-
81-
# fingerprints are unique per issue type and file
82-
# so there could be multiple if the same issue shows up multiple times
83-
# plus line numbers and method names could have changed
84-
unique_fingerprints = (preexisting_issues + changed_file_issues).map { |issue| issue["fingerprint"] }.uniq
85-
86-
unique_fingerprints.each do |fingerprint|
87-
baseline_issues = preexisting_issues.filter { |issue| issue["fingerprint"] == fingerprint }
88-
current_issues = changed_file_issues.filter { |issue| issue["fingerprint"] == fingerprint }
89-
90-
if baseline_issues.count == current_issues.count
91-
# current issues are most up to date (line numbers could have changed etc.)
92-
result[:preexisting] += current_issues
93-
elsif current_issues.count < baseline_issues.count
94-
# less issues than there were before
95-
current_issues.each do |issue_to_match|
96-
CodeclimateDiff.remove_closest_match_from_list(issue_to_match, baseline_issues)
97-
end
98-
result[:fixed] += baseline_issues
99-
else
100-
# more issues than there were before
101-
baseline_issues.each do |issue_to_match|
102-
CodeclimateDiff.remove_closest_match_from_list(issue_to_match, current_issues)
103-
end
104-
result[:new] += current_issues
105-
end
106-
end
107-
108-
# do a check to make sure the maths works out
109-
puts "#{preexisting_issues.count} issues in matching files in baseline"
110-
puts "#{changed_file_issues.count} current issues in matching files"
111-
112-
result
113-
end
114-
115-
def self.print_issues_in_category(issues_list)
116-
issues_list.each do |issue|
117-
filename = issue["location"]["path"]
118-
line_number = issue["location"]["lines"]["begin"]
119-
description = issue["description"]
120-
121-
print "\u2022 #{filename}:#{line_number}".encode("utf-8").bold
122-
puts " #{description}"
123-
end
124-
puts "\n"
125-
end
126-
127-
def self.print_category(bullet_emoji, severity, engine_name, check_name, color)
128-
message = "#{bullet_emoji} [#{severity}] #{engine_name} #{check_name}:".encode("utf-8")
129-
130-
case color
131-
when "red"
132-
puts message.red
133-
when "yellow"
134-
puts message.yellow
135-
when "green"
136-
puts message.green
137-
else
138-
puts message
139-
end
140-
end
141-
142-
def self.print_issues(issues_list, color, bullet_emoji)
143-
issue_categories = issues_list.map { |issue| [issue["engine_name"], issue["check_name"], issue["severity"]] }.uniq
144-
issue_categories.each do |issue_category|
145-
engine_name = issue_category[0]
146-
check_name = issue_category[1]
147-
severity = issue_category[2]
148-
issues = issues_list.filter do |issue|
149-
issue["engine_name"] == engine_name &&
150-
issue["check_name"] == check_name &&
151-
issue["severity"] == severity
152-
end
153-
print_category(bullet_emoji, severity, engine_name, check_name, color)
154-
print_issues_in_category(issues)
155-
end
156-
end
157-
158-
def self.print_result(sorted_issues, show_preexisting)
159-
if show_preexisting
160-
preexisting_issues = sorted_issues[:preexisting]
161-
if preexisting_issues.count.positive?
162-
puts "\n#{preexisting_issues.count} preexisting issues in changed files:\n".bold.yellow
163-
print_issues(preexisting_issues, "yellow", "\u2718")
164-
else
165-
puts "\n0 issues in changed files!".encode("utf-8").bold.green
166-
end
167-
end
168-
169-
new_issues = sorted_issues[:new]
170-
if new_issues.count.positive?
171-
puts "\n#{new_issues.count} new issues:\n".bold.red
172-
print_issues(new_issues, "red", "\u2718")
173-
else
174-
puts "\n0 new issues :)\n".encode("utf-8").bold
175-
end
176-
177-
fixed_issues = sorted_issues[:fixed]
178-
if fixed_issues.count.positive?
179-
puts "\n#{fixed_issues.count} fixed issues: \n".encode("utf-8").bold.green
180-
print_issues(fixed_issues, "green", "\u2714")
181-
else
182-
puts "\n0 fixed issues\n".bold
183-
end
184-
end
185-
186-
def self.print_call_to_action(sorted_issues)
187-
fixed_count = sorted_issues[:fixed].count
188-
new_count = sorted_issues[:new].count
189-
outstanding_count = sorted_issues[:preexisting].count + new_count
190-
if fixed_count > new_count
191-
puts "\n\u{1F389}\u{1F389} Well done! You made the code even better!! \u{1F389}\u{1F389} \n".bold.green.encode("utf-8")
192-
elsif new_count > fixed_count
193-
puts "\n\ Uh oh, you've introduced more issues than you've fixed. Better fix that! \n".bold.red.encode("utf-8")
194-
elsif outstanding_count.positive?
195-
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")
196-
else
197-
puts "\n\u{1F389}\u{1F389} Nothing to do here, the code is immaculate!! \u{1F389}\u{1F389} \n".bold.green.encode("utf-8")
198-
end
42+
def self.generate_baseline
43+
puts "Generating the baseline. Should take about 5 minutes..."
44+
result = CodeclimateWrapper.new.run_codeclimate
45+
File.write("codeclimate_diff_baseline.json", result)
46+
puts "Done!"
19947
end
20048

20149
def self.run_diff_on_branch(pattern, show_preexisting: true)
@@ -205,10 +53,10 @@ def self.run_diff_on_branch(pattern, show_preexisting: true)
20553

20654
preexisting_issues = calculate_preexisting_issues_in_changed_files(changed_filenames)
20755

208-
sorted_issues = sort_issues(preexisting_issues, changed_file_issues)
56+
sorted_issues = IssueSorter.sort_issues(preexisting_issues, changed_file_issues)
20957

210-
print_result(sorted_issues, show_preexisting)
211-
print_call_to_action(sorted_issues)
58+
ResultPrinter.print_result(sorted_issues, show_preexisting)
59+
ResultPrinter.print_call_to_action(sorted_issues)
21260
end
21361
end
21462
end

0 commit comments

Comments
 (0)