Skip to content

Commit 386725e

Browse files
committed
Allow remediation points to be multipliable
When a cop has a score/limit we now multiply a per violation amount of points by the overage of the violation. eg: 25/20 would multiply the per violation points by 5 This also changes `config/cops.yml` cops to have two values, `base_points` and `violation_points`. Since Rubocop violations don't directly expose the score/limit we check for the presence and extract the values from the format of `[score/limit]` in the violation message.
1 parent 8f1867e commit 386725e

File tree

7 files changed

+132
-21
lines changed

7 files changed

+132
-21
lines changed

.rubocop.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ Metrics/ModuleLength:
44
Style/ClassAndModuleChildren:
55
Exclude:
66
- 'spec/**/*'
7+
Metrics/LineLength:
8+
Exclude:
9+
- 'spec/**/*'
710
Style/Documentation:
811
Enabled: false
912
Style/FileName:
@@ -17,3 +20,8 @@ Style/StringLiterals:
1720
Enabled: false
1821
Style/TrailingComma:
1922
Enabled: false
23+
Style/DotPosition:
24+
EnforcedStyle: 'trailing'
25+
Enabled: true
26+
Style/MultilineOperationIndentation:
27+
Enabled: false

Gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ source 'https://rubygems.org'
33
gem "activesupport", require: false
44
gem "rubocop", require: false
55
gem "rubocop-rspec", require: false
6+
gem "safe_yaml"
67
gem "pry", require: false
78

89
group :test do

Gemfile.lock

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ GEM
4242
ruby-progressbar (~> 1.4)
4343
rubocop-rspec (1.3.0)
4444
ruby-progressbar (1.7.5)
45+
safe_yaml (1.0.4)
4546
slop (3.6.0)
4647
thread_safe (0.3.5)
4748
tzinfo (1.2.2)
@@ -59,3 +60,4 @@ DEPENDENCIES
5960
rake
6061
rubocop
6162
rubocop-rspec
63+
safe_yaml

config/cops.yml

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,24 @@
1-
Metrics/ClassLength: 400_000
1+
Metrics/AbcSize:
2+
violation_points: 100_000
3+
Metrics/BlockNesting:
4+
base_points: 30_000
5+
Metrics/ClassLength:
6+
base_points: 400_000
7+
Metrics/CyclomaticComplexity:
8+
base_points: 75_000
9+
violation_points: 10_000
10+
Metrics/LineLength:
11+
base_points: 20_000
12+
violation_points: 5_000
13+
Metrics/MethodLength:
14+
base_points: 50_000
15+
violation_points: 10_000
16+
Metrics/ModuleLength:
17+
base_points: 500_000
18+
violation_points: 5_000
19+
Metrics/ParameterList:
20+
base_points: 10_000
21+
violation_points: 50_000
22+
Metrics/PerceivedComplexity:
23+
base_points: 50_000
24+
violation_points: 5000

lib/cc/engine/rubocop.rb

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,13 @@
55
require "cc/engine/category_parser"
66
require "cc/engine/file_list_resolver"
77
require "cc/engine/lockfile_specs"
8+
require "cc/engine/violation_decorator"
89
require "active_support"
910
require "active_support/core_ext"
1011

1112
module CC
1213
module Engine
1314
class Rubocop
14-
DEFAULT_REMEDIATION_POINTS = 200_000
15-
1615
def initialize(code, engine_config, io)
1716
@code = code
1817
@engine_config = engine_config || {}
@@ -45,7 +44,8 @@ def category(cop_name)
4544
def inspect_file(path)
4645
parsed = RuboCop::ProcessedSource.from_file(path)
4746
rubocop_team_for_path(path).inspect_file(parsed).each do |violation|
48-
json = violation_json(violation, local_path(path))
47+
decorated_violation = ViolationDecorator.new(violation)
48+
json = violation_json(decorated_violation, local_path(path))
4949
@io.print "#{json}\0"
5050
end
5151
end
@@ -103,7 +103,7 @@ def violation_json(violation, local_path)
103103
check_name: "Rubocop/#{violation.cop_name}",
104104
description: violation.message,
105105
categories: [category(violation.cop_name)],
106-
remediation_points: remediation_points_for(violation.cop_name),
106+
remediation_points: violation.remediation_points,
107107
location: {
108108
path: local_path,
109109
positions: violation_positions(violation.location),
@@ -130,22 +130,6 @@ def cops
130130
RuboCop::Cop::Cop.non_rails
131131
end
132132
end
133-
134-
def remediation_points_for(cop_name)
135-
cop_list.fetch(cop_name, DEFAULT_REMEDIATION_POINTS)
136-
end
137-
138-
def cop_list
139-
return @cop_list if @cop_list
140-
141-
cops_path = File.expand_path(
142-
File.join(File.dirname(__FILE__), "../../../config/cops.yml")
143-
)
144-
145-
File.open(cops_path, "r") do |file|
146-
@cop_list = YAML.load(file.read)
147-
end
148-
end
149133
end
150134
end
151135
end

lib/cc/engine/violation_decorator.rb

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
require 'safe_yaml'
2+
3+
module CC
4+
module Engine
5+
class ViolationDecorator < SimpleDelegator
6+
MULTIPLIER_REGEX = %r{\[([\d\.]+)\/([\d\.]+)\]}
7+
8+
DEFAULT_REMEDIATION_POINTS = 200_000
9+
DEFAULT_POINTS_PER_VIOLATION = 50_000
10+
11+
def remediation_points
12+
if multiplier?
13+
base_points + violation_points
14+
else
15+
base_points
16+
end
17+
end
18+
19+
private
20+
21+
def base_points
22+
cop_list.
23+
fetch(cop_name, {}).
24+
fetch("base_points", DEFAULT_REMEDIATION_POINTS)
25+
end
26+
27+
def violation_points
28+
per_violation_points = cop_list.
29+
fetch(cop_name, {}).
30+
fetch("violation_points", DEFAULT_POINTS_PER_VIOLATION)
31+
32+
per_violation_points * multiplier
33+
end
34+
35+
def multiplier
36+
result = message.scan(MULTIPLIER_REGEX)
37+
score, max = result[0]
38+
score.to_i - max.to_i
39+
end
40+
41+
def multiplier?
42+
message.match(MULTIPLIER_REGEX)
43+
end
44+
45+
def cop_list
46+
return @cop_list if @cop_list
47+
48+
cops_path = File.expand_path(
49+
File.join(File.dirname(__FILE__), "../../../config/cops.yml")
50+
)
51+
52+
@cop_list = YAML.load_file(cops_path)
53+
end
54+
end
55+
end
56+
end
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
require "spec_helper"
2+
require "cc/engine/violation_decorator"
3+
4+
module CC::Engine
5+
describe ViolationDecorator do
6+
describe "#remediation points" do
7+
it "returns remediation points for violation" do
8+
fake_violation = Minitest::Mock.new
9+
fake_violation.expect :cop_name, :unknown_cop
10+
fake_violation.expect :message, "This has no multiplier!"
11+
12+
decorated_violation = ViolationDecorator.new(fake_violation)
13+
14+
decorated_violation.remediation_points.must_equal(
15+
ViolationDecorator::DEFAULT_REMEDIATION_POINTS
16+
)
17+
end
18+
19+
it "returns remedation points plus per violation points multiplied by overage" do
20+
fake_violation = Minitest::Mock.new
21+
fake_violation.expect :cop_name, :unknown_cop
22+
fake_violation.expect :cop_name, :unknown_cop
23+
fake_violation.expect :message, "This has [22/20] multiplier!"
24+
fake_violation.expect :message, "This has [22/20] multiplier!"
25+
26+
decorated_violation = ViolationDecorator.new(fake_violation)
27+
28+
violation_pointss = ViolationDecorator::DEFAULT_POINTS_PER_VIOLATION * 2
29+
base_points = ViolationDecorator::DEFAULT_REMEDIATION_POINTS
30+
31+
decorated_violation.remediation_points.must_equal(
32+
violation_pointss + base_points
33+
)
34+
end
35+
end
36+
end
37+
end

0 commit comments

Comments
 (0)