debt_ceiling 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -13
- data/.gitignore +2 -1
- data/.travis.yml +3 -0
- data/Gemfile +1 -1
- data/debt_ceiling.gemspec +3 -2
- data/examples/.debt_ceiling.rb.example +24 -5
- data/lib/debt_ceiling.rb +71 -36
- data/lib/debt_ceiling/accounting.rb +45 -35
- data/lib/debt_ceiling/compatibility.rb +7 -0
- data/lib/debt_ceiling/custom_debt_analysis.rb +51 -0
- data/lib/debt_ceiling/debt.rb +46 -45
- data/lib/debt_ceiling/file_attributes.rb +10 -0
- data/lib/debt_ceiling/version.rb +1 -1
- data/spec/debt_ceiling_spec.rb +12 -1
- data/spec/spec_helper.rb +12 -1
- data/spec/support/long_file_example.rb +14 -0
- metadata +45 -29
checksums.yaml
CHANGED
|
@@ -1,15 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
|
|
5
|
-
data.tar.gz: !binary |-
|
|
6
|
-
ZDdmOWM0NWE1NmVkOGQ4NWIyNTFkZjFiNTA0NWNmMTQ2OWRiZDQ1NQ==
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: 50413717fff100682e4d62f350b6b0625c92f5bf
|
|
4
|
+
data.tar.gz: 17ad97252626d36a70b221725fd7213962951dd6
|
|
7
5
|
SHA512:
|
|
8
|
-
metadata.gz:
|
|
9
|
-
|
|
10
|
-
YzEyY2Y1OGQ2YTllNzU2NDJhY2VlZjhkNzFjNjEyZDgxOTY5MWQzYzZlZWUw
|
|
11
|
-
NTFlNmMyZjFkZTkwNzI4NjY4Yjg4M2M3MDc1NGUyNGJmMTJmYjI=
|
|
12
|
-
data.tar.gz: !binary |-
|
|
13
|
-
NzI3ZGNhMzJlOTBiNDY2MDNmOTBlM2I3OWQxYjQ2MWFiZTAwMjEyMzIyOWJh
|
|
14
|
-
ZDc5ZWFlZDZhNWNmYmY1M2MwMzc0NTdmZjUxYjkxMTFmM2IyY2Q4ZjcwZDU4
|
|
15
|
-
YTlmZWJhZmIzYjcxYmM1YTZjNDk1NDUzNGU2M2U2ZTQ4ZjAxM2U=
|
|
6
|
+
metadata.gz: 9cb9b3b28fa5aa7f3b09e832b7b279412181d71157e7ac4cbd9f8bc4e5fb2d3d7cc759a76806aad8193bb9f997e15d9ec732a617d65619ff620081fd6131d4ae
|
|
7
|
+
data.tar.gz: 76afab03d0406fe9a1988ec25e648629afea95702e9356645073253b325d00a28b6cde6ea74169fd36dcc43805a97c6216e1cbeb14a97f5695083b24190da677
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
source
|
|
1
|
+
source "https://rubygems.org"
|
|
2
2
|
gemspec
|
data/debt_ceiling.gemspec
CHANGED
|
@@ -23,11 +23,12 @@ Gem::Specification.new do |s|
|
|
|
23
23
|
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
|
24
24
|
s.require_paths = ['lib']
|
|
25
25
|
|
|
26
|
-
s.add_runtime_dependency 'rubycritic', '~> 1.
|
|
26
|
+
s.add_runtime_dependency 'rubycritic', '~> 1.4'
|
|
27
27
|
s.add_runtime_dependency 'chronic', '~> 0.10'
|
|
28
|
-
s.add_runtime_dependency 'configurations', '~> 2.0
|
|
28
|
+
s.add_runtime_dependency 'configurations', '~> 2.0'
|
|
29
29
|
s.add_development_dependency 'pry', '~> 0.10'
|
|
30
30
|
s.add_development_dependency 'rake', '~> 10.3'
|
|
31
31
|
s.add_development_dependency 'rspec', '~> 3.1'
|
|
32
32
|
s.add_development_dependency 'coveralls', '~> 0.7'
|
|
33
|
+
s.add_development_dependency 'codeclimate-test-reporter', '~> 0.4'
|
|
33
34
|
end
|
|
@@ -4,19 +4,38 @@ DebtCeiling.configure do |c|
|
|
|
4
4
|
#exceeding this target will fail after the reduction date (parsed by Chronic)
|
|
5
5
|
c.reduction_target = 100
|
|
6
6
|
c.reduction_date = 'Jan 1 2015'
|
|
7
|
-
#
|
|
8
|
-
c.
|
|
7
|
+
#Max debt to allow in any one file/module before failing tests
|
|
8
|
+
c.max_debt_per_module = 200
|
|
9
|
+
|
|
10
|
+
#ABOVE config ^ effect what amount of debt where and when can fail your test suite
|
|
11
|
+
# none of the above have any default settings
|
|
12
|
+
|
|
13
|
+
#BELOW config v effect how debt is calculated
|
|
14
|
+
# most of these have 'sane' default settings, look at code and/or docs for details
|
|
15
|
+
|
|
16
|
+
#set the base cost per file for each letter grade, these are the current defaults
|
|
17
|
+
c.grade_points = { a: 0, b: 3, c: 13, d: 55, f: 144 }
|
|
18
|
+
#default multiplier for debt factors from flog, flay, reek and method count
|
|
19
|
+
c.complexity_multiplier = 0.5
|
|
20
|
+
c.method_count_multiplier = 0.5
|
|
21
|
+
c.smells_multiplier = 3
|
|
22
|
+
c.duplication_multiplier = 1.5
|
|
23
|
+
c.ideal_max_line_count = 100
|
|
24
|
+
c.cost_per_line_over_ideal = 1
|
|
9
25
|
#load custom debt calculations (see examples/debt.rb) from this path
|
|
10
26
|
c.extension_path = "./debt.rb"
|
|
11
|
-
#below two both use same mechanic,
|
|
27
|
+
#below two both use same mechanic, cost_per_todo just assumes capital
|
|
28
|
+
#TODO as string, cost_per_todo defaults to 0
|
|
12
29
|
c.cost_per_todo = 50
|
|
13
30
|
c.deprecated_reference_pairs = { 'DEPRECATED_API' => 20}
|
|
14
|
-
#manually assign
|
|
31
|
+
#manually assign arbitrary case specific amount of debt to code sections
|
|
32
|
+
#with specified terms or with default "TECH DEBT", as a comment like #TECH DEBT +50
|
|
15
33
|
c.manual_callouts += ["REFACTOR THIS", "HORRIBLE HACK"]
|
|
16
34
|
#only count debt scores for files/folders matching these strings (converted to regexes)
|
|
17
35
|
c.whitelist = %w(app lib)
|
|
18
36
|
#or
|
|
19
|
-
#exclude debt scores for files/folders matching these strings
|
|
37
|
+
#exclude debt scores for files/folders matching these strings
|
|
38
|
+
#(commented out here as white/black lists are mutually exclusive)
|
|
20
39
|
#c.blacklist = %w(config version debt_ceiling.rb)
|
|
21
40
|
end
|
|
22
41
|
|
data/lib/debt_ceiling.rb
CHANGED
|
@@ -1,53 +1,82 @@
|
|
|
1
1
|
require 'configurations'
|
|
2
2
|
require 'chronic'
|
|
3
|
+
require 'rubycritic'
|
|
4
|
+
require 'rubycritic/cli/application'
|
|
5
|
+
require 'ostruct'
|
|
3
6
|
require_relative 'debt_ceiling/accounting'
|
|
7
|
+
require_relative 'debt_ceiling/custom_debt_analysis'
|
|
4
8
|
require_relative 'debt_ceiling/debt'
|
|
9
|
+
require_relative 'debt_ceiling/compatibility'
|
|
10
|
+
require_relative 'debt_ceiling/file_attributes'
|
|
5
11
|
|
|
6
12
|
module DebtCeiling
|
|
7
13
|
include Configurations
|
|
8
14
|
extend Forwardable
|
|
9
15
|
extend self
|
|
10
16
|
|
|
11
|
-
attr_reader :
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
17
|
+
attr_reader :total_debt, :accounting_result
|
|
18
|
+
|
|
19
|
+
def_delegators :configuration, :extension_path, :blacklist, :whitelist,
|
|
20
|
+
:cost_per_todo, :deprecated_reference_pairs, :manual_callouts,
|
|
21
|
+
:grade_points, :reduction_date, :reduction_target, :debt_ceiling,
|
|
22
|
+
:max_debt_per_module, :non_grade_scoring, :complexity_multiplier ,
|
|
23
|
+
:method_count_multiplier, :smells_multiplier, :duplication_multiplier,
|
|
24
|
+
:ideal_max_line_count, :cost_per_line_over_ideal
|
|
25
|
+
|
|
26
|
+
configuration_defaults do |config|
|
|
27
|
+
config.extension_path = "#{Dir.pwd}/debt.rb"
|
|
28
|
+
config.blacklist = []
|
|
29
|
+
config.whitelist = []
|
|
30
|
+
config.deprecated_reference_pairs = {}
|
|
31
|
+
config.manual_callouts = ['TECH DEBT']
|
|
32
|
+
config.grade_points = { a: 0, b: 3, c: 13, d: 55, f: 144 }
|
|
33
|
+
config.complexity_multiplier = 0.5
|
|
34
|
+
config.method_count_multiplier = 0.5
|
|
35
|
+
config.smells_multiplier = 3
|
|
36
|
+
config.duplication_multiplier = 1.5
|
|
37
|
+
config.ideal_max_line_count = 100
|
|
38
|
+
config.cost_per_line_over_ideal = 1
|
|
39
|
+
#smells are pretty valid/fixable, complexity and method count
|
|
40
|
+
#may be inherent/way to improve smells
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def calculate(dir = '.', opts={preconfigured: false})
|
|
45
|
+
@total_debt = accounting_result(dir, opts).total_debt
|
|
46
|
+
fail_test if failed_condition?
|
|
47
|
+
total_debt
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def accounting_result(dir = '.', opts={preconfigured: false})
|
|
51
|
+
@accounting_result ||= begin
|
|
52
|
+
load_configuration unless @loaded || opts[:preconfigured]
|
|
53
|
+
Accounting.calculate(dir)
|
|
54
|
+
end
|
|
30
55
|
end
|
|
31
56
|
|
|
32
|
-
def load_configuration
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
load(
|
|
57
|
+
def load_configuration(config_file_name=".debt_ceiling.rb")
|
|
58
|
+
pwd = Dir.pwd
|
|
59
|
+
home = Dir.home
|
|
60
|
+
if File.exist?("#{pwd}/#{config_file_name}")
|
|
61
|
+
load("#{pwd}/#{config_file_name}")
|
|
62
|
+
elsif File.exist?("#{home}/#{config_file_name}")
|
|
63
|
+
load("#{home}/#{config_file_name}")
|
|
37
64
|
else
|
|
38
|
-
puts "No
|
|
65
|
+
puts "No #{config_file_name} configuration file detected in #{pwd} or ~/, using defaults"
|
|
39
66
|
end
|
|
40
67
|
|
|
41
68
|
load extension_path if extension_path && File.exist?(extension_path)
|
|
42
69
|
@loaded = true
|
|
43
70
|
end
|
|
44
71
|
|
|
45
|
-
def
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
evaluate
|
|
72
|
+
def clear
|
|
73
|
+
@accounting_result = nil
|
|
74
|
+
Accounting.clear
|
|
49
75
|
end
|
|
50
76
|
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
|
|
51
80
|
def blacklist_matching(matchers)
|
|
52
81
|
@blacklist = matchers.map { |matcher| Regexp.new(matcher) }
|
|
53
82
|
end
|
|
@@ -61,15 +90,21 @@ module DebtCeiling
|
|
|
61
90
|
deprecated_reference_pairs[string] = value
|
|
62
91
|
end
|
|
63
92
|
|
|
93
|
+
def failed_condition?
|
|
94
|
+
exceeded_total_limit? || missed_target? || max_debt_per_module_exceeded?
|
|
95
|
+
end
|
|
64
96
|
|
|
65
|
-
def
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
97
|
+
def exceeded_total_limit?
|
|
98
|
+
debt_ceiling && debt_ceiling <= total_debt
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def missed_target?
|
|
102
|
+
reduction_target && reduction_target <= total_debt &&
|
|
69
103
|
Time.now > Chronic.parse(reduction_date)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def max_debt_per_module_exceeded?
|
|
107
|
+
max_debt_per_module && max_debt_per_module <= accounting_result.max_debt.to_i
|
|
73
108
|
end
|
|
74
109
|
|
|
75
110
|
def fail_test
|
|
@@ -1,47 +1,57 @@
|
|
|
1
|
-
require 'rubycritic'
|
|
2
|
-
require 'ostruct'
|
|
3
1
|
module DebtCeiling
|
|
4
2
|
class Accounting
|
|
5
3
|
DebtCeilingExceeded = Class.new(StandardError)
|
|
6
4
|
TargetDeadlineMissed = Class.new(StandardError)
|
|
7
5
|
class << self
|
|
6
|
+
attr_reader :result, :path
|
|
8
7
|
def calculate(path)
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
8
|
+
@path = path
|
|
9
|
+
print_results
|
|
10
|
+
result
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def result
|
|
14
|
+
@result ||= result_from_analysed_modules(construct_rubycritic_modules(path))
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def clear
|
|
18
|
+
@result = nil
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def result_from_analysed_modules(analysed_modules)
|
|
22
|
+
analysis = OpenStruct.new
|
|
23
|
+
analysis.debts = analysed_modules.map {|mod| Debt.new(FileAttributes.new(mod)) }
|
|
24
|
+
analysis.max_debt = analysis.debts.max_by(&:to_i)
|
|
25
|
+
analysis.total_debt = analysis.debts.map(&:to_i).reduce(:+)
|
|
26
|
+
analysis
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def print_results
|
|
30
|
+
puts <<-RESULTS
|
|
31
|
+
Current total tech debt: #{result.total_debt}
|
|
32
|
+
Largest source of debt is: #{max_debt.name} at #{max_debt.to_i}
|
|
33
|
+
The rubycritic grade for that debt is: #{max_debt.letter_grade}
|
|
34
|
+
The flog complexity for that debt is: #{max_debt.analysed_module.complexity}
|
|
35
|
+
Flay suspects #{max_debt.analysed_module.duplication.to_i} areas of code duplication
|
|
36
|
+
There are #{method_count} methods and #{smell_count} smell(s) from reek.
|
|
37
|
+
The file is #{max_debt.linecount} lines long.
|
|
38
|
+
RESULTS
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def max_debt
|
|
42
|
+
result.max_debt
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def method_count
|
|
46
|
+
max_debt.analysed_module.methods_count
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def smell_count
|
|
50
|
+
max_debt.analysed_module.smells.count
|
|
28
51
|
end
|
|
29
52
|
|
|
30
53
|
def construct_rubycritic_modules(path)
|
|
31
|
-
|
|
32
|
-
Rubycritic::Orchestrator.new.critique([path])
|
|
33
|
-
else
|
|
34
|
-
# temporarily use Rubycritic internals until they provide an API
|
|
35
|
-
require 'rubycritic/modules_initializer'
|
|
36
|
-
require 'rubycritic/analysers/complexity'
|
|
37
|
-
require 'rubycritic/analysers/smells/flay'
|
|
38
|
-
|
|
39
|
-
modules = Rubycritic::ModulesInitializer.init([path])
|
|
40
|
-
[Rubycritic::Analyser::Complexity, Rubycritic::Analyser::FlaySmells].each do |analyser|
|
|
41
|
-
analyser.new(modules).run
|
|
42
|
-
end
|
|
43
|
-
modules
|
|
44
|
-
end
|
|
54
|
+
Rubycritic.create(mode: :ci, format: :json, paths: Array(path)).critique
|
|
45
55
|
end
|
|
46
56
|
end
|
|
47
57
|
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
module DebtCeiling
|
|
2
|
+
module CustomDebtAnalysis
|
|
3
|
+
|
|
4
|
+
def external_measure_debt
|
|
5
|
+
public_send(:measure_debt) if self.respond_to?(:measure_debt)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def external_augmented_debt
|
|
9
|
+
(public_send(:augment_debt) if respond_to?(:augment_debt)).to_i
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def debt_from_source_code_rules
|
|
13
|
+
manual_callout_debt +
|
|
14
|
+
text_match_debt('TODO', DebtCeiling.cost_per_todo) +
|
|
15
|
+
deprecated_reference_debt
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def text_match_debt(string, cost)
|
|
19
|
+
source_code.scan(string).count * cost.to_i
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def manual_callout_debt
|
|
23
|
+
DebtCeiling.manual_callouts.reduce(0) do |sum, callout|
|
|
24
|
+
sum + debt_from_callout(callout)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def deprecated_reference_debt
|
|
29
|
+
DebtCeiling.deprecated_reference_pairs
|
|
30
|
+
.reduce(0) {|accum, (string, value)| accum + text_match_debt(string, value.to_i) }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def debt_from_callout(callout)
|
|
34
|
+
source_code.each_line.reduce(0) do |sum, line|
|
|
35
|
+
match_data = line.match(Regexp.new(callout + '.*'))
|
|
36
|
+
string = match_data.to_s.split(callout).last
|
|
37
|
+
amount = string.match(/\d+/).to_s if string
|
|
38
|
+
sum + amount.to_i
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def valid_debt?
|
|
43
|
+
black_empty = DebtCeiling.blacklist.empty?
|
|
44
|
+
white_empty = DebtCeiling.whitelist.empty?
|
|
45
|
+
fail DoNotWhitelistAndBlacklistSimulateneously unless black_empty || white_empty
|
|
46
|
+
(black_empty && white_empty) ||
|
|
47
|
+
(black_empty && self.class.whitelist_includes?(self)) ||
|
|
48
|
+
(white_empty && !self.class.blacklist_includes?(self))
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
data/lib/debt_ceiling/debt.rb
CHANGED
|
@@ -2,10 +2,12 @@ require 'forwardable'
|
|
|
2
2
|
module DebtCeiling
|
|
3
3
|
class Debt
|
|
4
4
|
extend Forwardable
|
|
5
|
+
include CustomDebtAnalysis
|
|
5
6
|
DoNotWhitelistAndBlacklistSimulateneously = Class.new(StandardError)
|
|
6
7
|
|
|
7
|
-
attr_reader :file_attributes
|
|
8
8
|
def_delegators :file_attributes, :path, :analysed_module, :module_name, :linecount, :source_code
|
|
9
|
+
def_delegators :non_grade_scoring, :complexity_multiplier, :duplication_multiplier, :smells_multiplier,
|
|
10
|
+
:method_count_multiplier, :ideal_max_line_count, :cost_per_line_over_ideal
|
|
9
11
|
def_delegator :analysed_module, :rating
|
|
10
12
|
attr_accessor :debt_amount
|
|
11
13
|
def_delegator :debt_amount, :to_i
|
|
@@ -15,69 +17,68 @@ module DebtCeiling
|
|
|
15
17
|
default_measure_debt if valid_debt?
|
|
16
18
|
end
|
|
17
19
|
|
|
20
|
+
def name
|
|
21
|
+
file_attributes.analysed_module.name || path.to_s.split('/').last
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def +(other)
|
|
25
|
+
to_i + other.to_i
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def letter_grade
|
|
29
|
+
rating.to_s.downcase.to_sym
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
attr_reader :file_attributes
|
|
35
|
+
|
|
18
36
|
def default_measure_debt
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
unless cost
|
|
22
|
-
cost = public_send(:augment_debt) if respond_to?(:augment_debt)
|
|
23
|
-
cost = cost.to_i
|
|
24
|
-
letter_grade = rating.to_s.downcase
|
|
25
|
-
cost_per_line = DebtCeiling.configuration.grade_points[letter_grade.to_sym]
|
|
26
|
-
cost += file_attributes.linecount * cost_per_line
|
|
27
|
-
cost += debt_from_source_code_rules
|
|
28
|
-
end
|
|
29
|
-
self.debt_amount = cost
|
|
37
|
+
self.debt_amount = external_measure_debt || internal_measure_debt
|
|
30
38
|
end
|
|
31
39
|
|
|
32
|
-
def
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
.reduce(0) {|accum, (string, value)| accum + text_match_debt(string, value.to_i) }
|
|
40
|
+
def internal_measure_debt
|
|
41
|
+
external_augmented_debt +
|
|
42
|
+
cost_from_static_analysis_points +
|
|
43
|
+
debt_from_source_code_rules
|
|
37
44
|
end
|
|
38
45
|
|
|
39
|
-
def
|
|
40
|
-
|
|
46
|
+
def cost_from_static_analysis_points
|
|
47
|
+
DebtCeiling.grade_points[letter_grade] + cost_from_non_grade_scoring
|
|
41
48
|
end
|
|
42
49
|
|
|
43
|
-
def
|
|
44
|
-
|
|
45
|
-
sum + debt_from_callout(callout)
|
|
46
|
-
end
|
|
50
|
+
def cost_from_non_grade_scoring
|
|
51
|
+
flog_flay_debt + method_count_debt + smells_debt + line_count_debt
|
|
47
52
|
end
|
|
48
53
|
|
|
49
|
-
def
|
|
50
|
-
|
|
51
|
-
match_data = line.match(Regexp.new(callout + '.*'))
|
|
52
|
-
string = match_data.to_s.split(callout).last
|
|
53
|
-
amount = string.match(/\d+/).to_s if string
|
|
54
|
-
sum + amount.to_i
|
|
55
|
-
end
|
|
54
|
+
def smells_debt
|
|
55
|
+
analysed_module.smells.map(&:cost).inject(0, :+) * smells_multiplier
|
|
56
56
|
end
|
|
57
57
|
|
|
58
|
-
def
|
|
59
|
-
|
|
60
|
-
white_empty = DebtCeiling.whitelist.empty?
|
|
61
|
-
fail DoNotWhitelistAndBlacklistSimulateneously unless black_empty || white_empty
|
|
62
|
-
(black_empty && white_empty) ||
|
|
63
|
-
(black_empty && self.class.whitelist_includes?(self)) ||
|
|
64
|
-
(white_empty && !self.class.blacklist_includes?(self))
|
|
58
|
+
def method_count_debt
|
|
59
|
+
analysed_module.methods_count * method_count_multiplier
|
|
65
60
|
end
|
|
66
61
|
|
|
67
|
-
def
|
|
68
|
-
|
|
62
|
+
def flog_flay_debt
|
|
63
|
+
analysed_module.complexity * complexity_multiplier +
|
|
64
|
+
analysed_module.duplication * duplication_multiplier
|
|
69
65
|
end
|
|
70
66
|
|
|
71
|
-
def
|
|
72
|
-
|
|
67
|
+
def line_count_debt
|
|
68
|
+
excess_lines = linecount - ideal_max_line_count
|
|
69
|
+
excess_lines > 0 ? excess_lines * cost_per_line_over_ideal : 0
|
|
73
70
|
end
|
|
74
71
|
|
|
75
|
-
def
|
|
76
|
-
|
|
72
|
+
def non_grade_scoring
|
|
73
|
+
DebtCeiling
|
|
77
74
|
end
|
|
78
75
|
|
|
79
|
-
def
|
|
80
|
-
|
|
76
|
+
def self.whitelist_includes?(debt)
|
|
77
|
+
DebtCeiling.whitelist.find { |filename| debt.path.match filename }
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def self.blacklist_includes?(debt)
|
|
81
|
+
DebtCeiling.blacklist.find { |filename| debt.path.match filename }
|
|
81
82
|
end
|
|
82
83
|
end
|
|
83
84
|
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
class FileAttributes
|
|
2
|
+
attr_reader :linecount, :path, :analysed_module, :source_code
|
|
3
|
+
def initialize(analysed_file)
|
|
4
|
+
file_path = analysed_file.path
|
|
5
|
+
@linecount = `wc -l #{file_path}`.match(/\d+/)[0].to_i
|
|
6
|
+
@path = file_path
|
|
7
|
+
@analysed_module = analysed_file
|
|
8
|
+
@source_code = File.read(file_path)
|
|
9
|
+
end
|
|
10
|
+
end
|
data/lib/debt_ceiling/version.rb
CHANGED
data/spec/debt_ceiling_spec.rb
CHANGED
|
@@ -11,7 +11,13 @@ describe DebtCeiling do
|
|
|
11
11
|
it 'has failing exit status when target debt reduction is missed' do
|
|
12
12
|
DebtCeiling.configure {|c| c.reduction_target =0; c.reduction_date = Time.now.to_s }
|
|
13
13
|
expect(DebtCeiling.debt_ceiling).to eq(nil)
|
|
14
|
-
expect { DebtCeiling.calculate('.', preconfigured: true) }.to raise_error
|
|
14
|
+
expect { DebtCeiling.calculate('.', preconfigured: true) }.to raise_error(SystemExit)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it 'has failing exit status when max debt per modile is exceeded' do
|
|
18
|
+
DebtCeiling.configure {|c| c.max_debt_per_module =5 }
|
|
19
|
+
expect(DebtCeiling.debt_ceiling).to eq(nil)
|
|
20
|
+
expect { DebtCeiling.calculate('.', preconfigured: true) }.to raise_error(SystemExit)
|
|
15
21
|
end
|
|
16
22
|
|
|
17
23
|
it 'returns quantity of total debt' do
|
|
@@ -33,4 +39,9 @@ describe DebtCeiling do
|
|
|
33
39
|
expect(DebtCeiling.calculate('spec/support/manual_example.rb')).to be 150 # hardcoded in example file
|
|
34
40
|
end
|
|
35
41
|
|
|
42
|
+
it 'assigns debt for file length over ideal file size' do
|
|
43
|
+
DebtCeiling.configure {|c| c.ideal_max_line_count = 10; c.cost_per_line_over_ideal = 100 }
|
|
44
|
+
expect(DebtCeiling.calculate('spec/support/long_file_example.rb', preconfigured: true)).to be 300 # hardcoded 13 lines long example file
|
|
45
|
+
end
|
|
46
|
+
|
|
36
47
|
end
|
data/spec/spec_helper.rb
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
|
+
require 'codeclimate-test-reporter'
|
|
2
|
+
CodeClimate::TestReporter.start
|
|
1
3
|
require 'coveralls'
|
|
2
4
|
Coveralls.wear!
|
|
5
|
+
|
|
3
6
|
RSpec.configure do |config|
|
|
4
7
|
config.before { allow($stdout).to receive(:puts) }
|
|
5
|
-
config.after(:each) { DebtCeiling.configure {|c| c.debt_ceiling = nil; c.reduction_target = nil; c.reduction_date = nil } }
|
|
8
|
+
config.after(:each) { DebtCeiling.configure {|c| c.debt_ceiling = nil; c.reduction_target = nil; c.reduction_date = nil } ; DebtCeiling.clear }
|
|
9
|
+
config.after(:all) do
|
|
10
|
+
DebtCeiling.configure do |c|
|
|
11
|
+
c.whitelist = %w(app lib)
|
|
12
|
+
c.max_debt_per_module = 150
|
|
13
|
+
c.debt_ceiling = 300
|
|
14
|
+
end
|
|
15
|
+
DebtCeiling.calculate('.')
|
|
16
|
+
end
|
|
6
17
|
end
|
metadata
CHANGED
|
@@ -1,116 +1,129 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: debt_ceiling
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Brian Glusman
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2015-
|
|
11
|
+
date: 2015-03-24 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rubycritic
|
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
|
16
16
|
requirements:
|
|
17
|
-
- - ~>
|
|
17
|
+
- - "~>"
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: 1.
|
|
19
|
+
version: '1.4'
|
|
20
20
|
type: :runtime
|
|
21
21
|
prerelease: false
|
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
23
|
requirements:
|
|
24
|
-
- - ~>
|
|
24
|
+
- - "~>"
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: 1.
|
|
26
|
+
version: '1.4'
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
28
28
|
name: chronic
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
|
30
30
|
requirements:
|
|
31
|
-
- - ~>
|
|
31
|
+
- - "~>"
|
|
32
32
|
- !ruby/object:Gem::Version
|
|
33
33
|
version: '0.10'
|
|
34
34
|
type: :runtime
|
|
35
35
|
prerelease: false
|
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
37
|
requirements:
|
|
38
|
-
- - ~>
|
|
38
|
+
- - "~>"
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
40
|
version: '0.10'
|
|
41
41
|
- !ruby/object:Gem::Dependency
|
|
42
42
|
name: configurations
|
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
|
44
44
|
requirements:
|
|
45
|
-
- - ~>
|
|
45
|
+
- - "~>"
|
|
46
46
|
- !ruby/object:Gem::Version
|
|
47
|
-
version: 2.0
|
|
47
|
+
version: '2.0'
|
|
48
48
|
type: :runtime
|
|
49
49
|
prerelease: false
|
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
51
|
requirements:
|
|
52
|
-
- - ~>
|
|
52
|
+
- - "~>"
|
|
53
53
|
- !ruby/object:Gem::Version
|
|
54
|
-
version: 2.0
|
|
54
|
+
version: '2.0'
|
|
55
55
|
- !ruby/object:Gem::Dependency
|
|
56
56
|
name: pry
|
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
|
58
58
|
requirements:
|
|
59
|
-
- - ~>
|
|
59
|
+
- - "~>"
|
|
60
60
|
- !ruby/object:Gem::Version
|
|
61
61
|
version: '0.10'
|
|
62
62
|
type: :development
|
|
63
63
|
prerelease: false
|
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
|
65
65
|
requirements:
|
|
66
|
-
- - ~>
|
|
66
|
+
- - "~>"
|
|
67
67
|
- !ruby/object:Gem::Version
|
|
68
68
|
version: '0.10'
|
|
69
69
|
- !ruby/object:Gem::Dependency
|
|
70
70
|
name: rake
|
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
|
72
72
|
requirements:
|
|
73
|
-
- - ~>
|
|
73
|
+
- - "~>"
|
|
74
74
|
- !ruby/object:Gem::Version
|
|
75
75
|
version: '10.3'
|
|
76
76
|
type: :development
|
|
77
77
|
prerelease: false
|
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
|
79
79
|
requirements:
|
|
80
|
-
- - ~>
|
|
80
|
+
- - "~>"
|
|
81
81
|
- !ruby/object:Gem::Version
|
|
82
82
|
version: '10.3'
|
|
83
83
|
- !ruby/object:Gem::Dependency
|
|
84
84
|
name: rspec
|
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
|
86
86
|
requirements:
|
|
87
|
-
- - ~>
|
|
87
|
+
- - "~>"
|
|
88
88
|
- !ruby/object:Gem::Version
|
|
89
89
|
version: '3.1'
|
|
90
90
|
type: :development
|
|
91
91
|
prerelease: false
|
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
|
93
93
|
requirements:
|
|
94
|
-
- - ~>
|
|
94
|
+
- - "~>"
|
|
95
95
|
- !ruby/object:Gem::Version
|
|
96
96
|
version: '3.1'
|
|
97
97
|
- !ruby/object:Gem::Dependency
|
|
98
98
|
name: coveralls
|
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
|
100
100
|
requirements:
|
|
101
|
-
- - ~>
|
|
101
|
+
- - "~>"
|
|
102
102
|
- !ruby/object:Gem::Version
|
|
103
103
|
version: '0.7'
|
|
104
104
|
type: :development
|
|
105
105
|
prerelease: false
|
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
|
107
107
|
requirements:
|
|
108
|
-
- - ~>
|
|
108
|
+
- - "~>"
|
|
109
109
|
- !ruby/object:Gem::Version
|
|
110
110
|
version: '0.7'
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
111
|
+
- !ruby/object:Gem::Dependency
|
|
112
|
+
name: codeclimate-test-reporter
|
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
|
114
|
+
requirements:
|
|
115
|
+
- - "~>"
|
|
116
|
+
- !ruby/object:Gem::Version
|
|
117
|
+
version: '0.4'
|
|
118
|
+
type: :development
|
|
119
|
+
prerelease: false
|
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
121
|
+
requirements:
|
|
122
|
+
- - "~>"
|
|
123
|
+
- !ruby/object:Gem::Version
|
|
124
|
+
version: '0.4'
|
|
125
|
+
description: |2
|
|
126
|
+
Get a grip on your technical debt
|
|
114
127
|
email:
|
|
115
128
|
- bglusman@shutterstock.com
|
|
116
129
|
executables:
|
|
@@ -119,8 +132,8 @@ extensions:
|
|
|
119
132
|
- Rakefile
|
|
120
133
|
extra_rdoc_files: []
|
|
121
134
|
files:
|
|
122
|
-
- .gitignore
|
|
123
|
-
- .travis.yml
|
|
135
|
+
- ".gitignore"
|
|
136
|
+
- ".travis.yml"
|
|
124
137
|
- Gemfile
|
|
125
138
|
- LICENSE
|
|
126
139
|
- MIT-LICENSE.md
|
|
@@ -132,10 +145,14 @@ files:
|
|
|
132
145
|
- examples/debt.rb.example
|
|
133
146
|
- lib/debt_ceiling.rb
|
|
134
147
|
- lib/debt_ceiling/accounting.rb
|
|
148
|
+
- lib/debt_ceiling/compatibility.rb
|
|
149
|
+
- lib/debt_ceiling/custom_debt_analysis.rb
|
|
135
150
|
- lib/debt_ceiling/debt.rb
|
|
151
|
+
- lib/debt_ceiling/file_attributes.rb
|
|
136
152
|
- lib/debt_ceiling/version.rb
|
|
137
153
|
- spec/debt_ceiling_spec.rb
|
|
138
154
|
- spec/spec_helper.rb
|
|
155
|
+
- spec/support/long_file_example.rb
|
|
139
156
|
- spec/support/manual_example.rb
|
|
140
157
|
- spec/support/todo_example.rb
|
|
141
158
|
homepage: https://github.com/bglusman/debt_ceiling
|
|
@@ -147,19 +164,18 @@ require_paths:
|
|
|
147
164
|
- lib
|
|
148
165
|
required_ruby_version: !ruby/object:Gem::Requirement
|
|
149
166
|
requirements:
|
|
150
|
-
- -
|
|
167
|
+
- - ">="
|
|
151
168
|
- !ruby/object:Gem::Version
|
|
152
169
|
version: '0'
|
|
153
170
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
154
171
|
requirements:
|
|
155
|
-
- -
|
|
172
|
+
- - ">="
|
|
156
173
|
- !ruby/object:Gem::Version
|
|
157
174
|
version: '0'
|
|
158
175
|
requirements: []
|
|
159
176
|
rubyforge_project: debt_ceiling
|
|
160
|
-
rubygems_version: 2.4.
|
|
177
|
+
rubygems_version: 2.4.3
|
|
161
178
|
signing_key:
|
|
162
179
|
specification_version: 4
|
|
163
180
|
summary: DebtCeiling helps you track Tech Debt
|
|
164
181
|
test_files: []
|
|
165
|
-
has_rdoc:
|