debt_ceiling 0.2.0 → 0.3.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 +4 -4
- data/README.md +16 -9
- data/bin/debt_ceiling +1 -1
- data/debt_ceiling.gemspec +0 -1
- data/examples/.debt_ceiling.rb.example +8 -8
- data/examples/{debt.rb.example → custom_debt.rb.example} +2 -2
- data/lib/debt_ceiling/accounting.rb +46 -51
- data/lib/debt_ceiling/audit.rb +80 -0
- data/lib/debt_ceiling/common_methods.rb +13 -0
- data/lib/debt_ceiling/{custom_debt_analysis.rb → custom_debt.rb} +27 -13
- data/lib/debt_ceiling/debt.rb +24 -52
- data/lib/debt_ceiling/static_analysis_debt.rb +58 -0
- data/lib/debt_ceiling/version.rb +1 -1
- data/lib/debt_ceiling.rb +13 -82
- data/spec/debt_ceiling_spec.rb +11 -8
- data/spec/spec_helper.rb +3 -5
- metadata +7 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1f143bb4a889c52ce57b69ee02ecaa062dde87eb
|
4
|
+
data.tar.gz: d31f7da0e0e5221bb10dbbb2db7f382cdcc6f603
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 93f9335622c57589b9d7de6a207074af558b2e111dd2056351e7db133eee7c8dfa40a99627e884ad4b408758720fbd81d1abf15933b185e052a1cc6661b84f90
|
7
|
+
data.tar.gz: 57423abb6795759193d1e27c064e4ec9bfe83a2b2efe32bddb10ed7dfedeb73aa64449e21d054f892ce887696e1a104241a1bf238fc476c25325c087af2a08b3
|
data/README.md
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
-
[](http://badge.fury.io/rb/debt_ceiling)
|
1
|
+
[](http://badge.fury.io/rb/debt_ceiling)
|
2
2
|
[](https://travis-ci.org/bglusman/debt_ceiling)
|
3
|
-
[](https://coveralls.io/r/bglusman/debt_ceiling?branch=master)
|
4
|
-
[](https://gitter.im/bglusman/debt_ceiling)
|
5
|
-
[](https://waffle.io/bglusman/debt_ceiling)
|
3
|
+
[](https://coveralls.io/r/bglusman/debt_ceiling?branch=master)
|
4
|
+
[](https://gitter.im/bglusman/debt_ceiling)
|
5
|
+
[](https://waffle.io/bglusman/debt_ceiling)
|
6
6
|
[](https://codeclimate.com/github/bglusman/debt_ceiling)
|
7
7
|
#DebtCeiling
|
8
8
|
|
@@ -21,7 +21,18 @@ Current features include:
|
|
21
21
|
* Running from a test suite to fail if debt ceiling is exceeded
|
22
22
|
* Running from a test suite to fail if debt deadline is missed (currently only supports a single deadline, could add support for multiple targets if there's interest)
|
23
23
|
|
24
|
-
To integrate in a test suite, set a value for `debt_ceiling` and/or `reduction_target` and `reduction_date` in your configuration and call `DebtCeiling.
|
24
|
+
To integrate in a test suite, set a value for `debt_ceiling` and/or `reduction_target` and `reduction_date` in your configuration and call `DebtCeiling.audit(root_dir)` from your test helper as an additional test, or drop the configuration directly in your spec helper as debt ceiling does to calculate it's own debt:
|
25
|
+
```ruby
|
26
|
+
config.after(:all) do
|
27
|
+
DebtCeiling.configure do |c|
|
28
|
+
c.whitelist = %w(app lib)
|
29
|
+
c.max_debt_per_module = 150
|
30
|
+
c.debt_ceiling = 250
|
31
|
+
end
|
32
|
+
DebtCeiling.audit('.', preconfigured: true)
|
33
|
+
end
|
34
|
+
```
|
35
|
+
It will exit with a non-zero failure if you exceed your ceiling(s) or miss your target, failing the test suite.
|
25
36
|
|
26
37
|
These features are largely demonstrated/discussed in [examples/.debt_ceiling.rb.example](https://github.com/bglusman/debt_ceiling/blob/master/examples/.debt_ceiling.rb.example) or below snippet which demonstrates configuring debt ceiling around a team or maintainer's agreed ideas about how to quantify debt automatically and/or manually in the project source code.
|
27
38
|
|
@@ -63,12 +74,8 @@ As shown in example file, set a path for `extension_path` pointing to a file def
|
|
63
74
|
|
64
75
|
* default/custom points per reek smell detected (not currently part of RubyCritic grading, but available in full analysis)
|
65
76
|
|
66
|
-
* every line over x ideal file size is y points of debt
|
67
|
-
|
68
77
|
* multipliers for important files
|
69
78
|
|
70
|
-
* `debt_ceiling_per_file` option to cap max debt for any one analyzed module
|
71
|
-
|
72
79
|
* command line options to configure options per run/without a `.debt_ceiling.rb` file (could be done with [ENVied](https://github.com/eval/envied) gem perhaps, or [commander](https://github.com/tj/commander) or [one of these](https://www.ruby-toolbox.com/categories/CLI_Option_Parsers)
|
73
80
|
|
74
81
|
* visualization/history of debt would be nice, but unclear how to best support... one possibility is running it against each commit in a repo, and using git-notes to add score data (and some metadata perhaps?) to store it for comparing/graphing, and for comparing branches etc. optionally configured could do this for every commit that doesn't already have a note attached, or for which the note's metadata/version is out of sync with current definitions.
|
data/bin/debt_ceiling
CHANGED
data/debt_ceiling.gemspec
CHANGED
@@ -3,7 +3,7 @@ DebtCeiling.configure do |c|
|
|
3
3
|
c.debt_ceiling = 500
|
4
4
|
#exceeding this target will fail after the reduction date (parsed by Chronic)
|
5
5
|
c.reduction_target = 100
|
6
|
-
c.reduction_date = 'Jan 1
|
6
|
+
c.reduction_date = 'Jan 1 2016'
|
7
7
|
#Max debt to allow in any one file/module before failing tests
|
8
8
|
c.max_debt_per_module = 200
|
9
9
|
|
@@ -16,14 +16,14 @@ DebtCeiling.configure do |c|
|
|
16
16
|
#set the base cost per file for each letter grade, these are the current defaults
|
17
17
|
c.grade_points = { a: 0, b: 3, c: 13, d: 55, f: 144 }
|
18
18
|
#default multiplier for debt factors from flog, flay, reek and method count
|
19
|
-
c.complexity_multiplier
|
20
|
-
c.method_count_multiplier
|
21
|
-
c.smells_multiplier
|
22
|
-
c.duplication_multiplier
|
23
|
-
c.ideal_max_line_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
24
|
c.cost_per_line_over_ideal = 1
|
25
|
-
#load custom debt calculations (see examples/
|
26
|
-
c.extension_path = "./
|
25
|
+
#load custom debt calculations (see examples/custom_debt.rb) from this path
|
26
|
+
c.extension_path = "./custom_debt.rb"
|
27
27
|
#below two both use same mechanic, cost_per_todo just assumes capital
|
28
28
|
#TODO as string, cost_per_todo defaults to 0
|
29
29
|
c.cost_per_todo = 50
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module DebtCeiling
|
2
|
-
class
|
2
|
+
class CustomDebt
|
3
3
|
#replace the DebtCeiling cost calculation per file with your own
|
4
4
|
|
5
5
|
|
@@ -14,7 +14,7 @@ module DebtCeiling
|
|
14
14
|
#output additional cost beyond defaults based on #file_attributes
|
15
15
|
end
|
16
16
|
|
17
|
-
#augment_debt is only called if measure_debt is undefined or returns nil/false
|
17
|
+
#augment_debt is only called if measure_debt is undefined or returns nil/false
|
18
18
|
#for this instance of debt (which is 1:1 mapping of Rubycritic::AnalysedModule at the moment)
|
19
19
|
end
|
20
20
|
end
|
@@ -2,57 +2,52 @@ module DebtCeiling
|
|
2
2
|
class Accounting
|
3
3
|
DebtCeilingExceeded = Class.new(StandardError)
|
4
4
|
TargetDeadlineMissed = Class.new(StandardError)
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
end
|
52
|
-
|
53
|
-
def construct_rubycritic_modules(path)
|
54
|
-
Rubycritic.create(mode: :ci, format: :json, paths: Array(path)).critique
|
55
|
-
end
|
5
|
+
extend Forwardable
|
6
|
+
attr_reader :result, :path, :debts, :total_debt, :max_debt
|
7
|
+
|
8
|
+
def_delegator :max_debt, :analysed_module
|
9
|
+
alias_method :max_module, :analysed_module
|
10
|
+
def initialize(path)
|
11
|
+
@path = path
|
12
|
+
calc_debt_for_modules(construct_rubycritic_modules(path))
|
13
|
+
end
|
14
|
+
|
15
|
+
def calc_debt_for_modules(analysed_modules)
|
16
|
+
@debts = analysed_modules.map {|mod| Debt.new(FileAttributes.new(mod)) }
|
17
|
+
@total_debt = get_total_debt
|
18
|
+
@max_debt = get_max_debt
|
19
|
+
end
|
20
|
+
|
21
|
+
def print_results
|
22
|
+
puts <<-RESULTS
|
23
|
+
Current total tech debt: #{total_debt}
|
24
|
+
Largest source of debt is: #{max_debt.name} at #{max_debt.to_i}
|
25
|
+
The rubycritic grade for that debt is: #{max_debt.letter_grade}
|
26
|
+
The flog complexity for that debt is: #{max_module.complexity}
|
27
|
+
Flay suspects #{max_module.duplication.to_i} areas of code duplication
|
28
|
+
There are #{method_count} methods and #{smell_count} smell(s) from reek.
|
29
|
+
The file is #{max_debt.linecount} lines long.
|
30
|
+
RESULTS
|
31
|
+
end
|
32
|
+
|
33
|
+
def get_max_debt
|
34
|
+
debts.max_by(&:to_i)
|
35
|
+
end
|
36
|
+
|
37
|
+
def get_total_debt
|
38
|
+
debts.map(&:to_i).reduce(:+)
|
39
|
+
end
|
40
|
+
|
41
|
+
def method_count
|
42
|
+
max_module.methods_count
|
43
|
+
end
|
44
|
+
|
45
|
+
def smell_count
|
46
|
+
max_module.smells.count
|
47
|
+
end
|
48
|
+
|
49
|
+
def construct_rubycritic_modules(path)
|
50
|
+
Rubycritic.create(mode: :ci, format: :json, paths: Array(path)).critique
|
56
51
|
end
|
57
52
|
end
|
58
53
|
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module DebtCeiling
|
2
|
+
class Audit
|
3
|
+
extend Forwardable
|
4
|
+
include CommonMethods
|
5
|
+
undef :+
|
6
|
+
|
7
|
+
CONFIG_FILE_NAME = ".debt_ceiling.rb"
|
8
|
+
CONFIG_LOCATIONS = ["#{Dir.pwd}/#{CONFIG_FILE_NAME}", "#{Dir.home}/#{CONFIG_FILE_NAME}"]
|
9
|
+
NO_CONFIG_FOUND = "No #{CONFIG_FILE_NAME} configuration file detected in #{Dir.pwd} or ~/, using defaults"
|
10
|
+
|
11
|
+
attr_reader :accounting, :dir, :loaded
|
12
|
+
|
13
|
+
def_delegators :configuration,
|
14
|
+
:extension_path, :debt_ceiling, :reduction_target, :reduction_date, :max_debt_per_module
|
15
|
+
|
16
|
+
def_delegator :accounting, :total_debt
|
17
|
+
|
18
|
+
def initialize(dir = '.', opts = {})
|
19
|
+
@loaded = opts[:preconfigured]
|
20
|
+
@dir = dir
|
21
|
+
@accounting = perform_accounting
|
22
|
+
accounting.print_results unless opts[:skip_report]
|
23
|
+
fail_test if failed_condition?
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def load_configuration
|
29
|
+
config_file_location ? load(config_file_location) : puts(NO_CONFIG_FOUND)
|
30
|
+
|
31
|
+
load extension_path if extension_path && File.exist?(extension_path)
|
32
|
+
@loaded = true
|
33
|
+
end
|
34
|
+
|
35
|
+
def config_file_location
|
36
|
+
CONFIG_LOCATIONS.find {|loc| File.exist?(loc) }
|
37
|
+
end
|
38
|
+
|
39
|
+
def perform_accounting
|
40
|
+
load_configuration unless loaded
|
41
|
+
Accounting.new(dir)
|
42
|
+
end
|
43
|
+
|
44
|
+
def blacklist_matching(matchers)
|
45
|
+
@blacklist = matchers.map { |matcher| Regexp.new(matcher) }
|
46
|
+
end
|
47
|
+
|
48
|
+
def whitelist_matching(matchers)
|
49
|
+
@whitelist = matchers.map { |matcher| Regexp.new(matcher) }
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
def debt_per_reference_to(string, value)
|
54
|
+
deprecated_reference_pairs[string] = value
|
55
|
+
end
|
56
|
+
|
57
|
+
def failed_condition?
|
58
|
+
exceeded_total_limit? || missed_target? || max_debt_per_module_exceeded?
|
59
|
+
end
|
60
|
+
|
61
|
+
def exceeded_total_limit?
|
62
|
+
debt_ceiling && debt_ceiling <= total_debt
|
63
|
+
end
|
64
|
+
|
65
|
+
def missed_target?
|
66
|
+
reduction_target && reduction_target <= total_debt &&
|
67
|
+
Time.now > Chronic.parse(reduction_date)
|
68
|
+
end
|
69
|
+
|
70
|
+
def max_debt_per_module_exceeded?
|
71
|
+
max_debt_per_module && max_debt_per_module <= accounting.max_debt.to_i
|
72
|
+
end
|
73
|
+
|
74
|
+
def fail_test
|
75
|
+
at_exit do
|
76
|
+
Kernel.exit 1
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -1,17 +1,40 @@
|
|
1
1
|
module DebtCeiling
|
2
|
-
|
2
|
+
class CustomDebt
|
3
|
+
extend Forwardable
|
4
|
+
include CommonMethods
|
5
|
+
|
6
|
+
def_delegators :configuration,
|
7
|
+
:deprecated_reference_pairs, :manual_callouts, :cost_per_todo
|
8
|
+
|
9
|
+
def_delegator :file_attributes, :source_code
|
10
|
+
|
11
|
+
def_delegator :debt_amount, :to_i
|
12
|
+
|
13
|
+
def initialize(file_attributes)
|
14
|
+
@file_attributes = file_attributes
|
15
|
+
@debt_amount = default_measure_debt
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
attr_reader :file_attributes, :debt_amount
|
3
21
|
|
4
22
|
def external_measure_debt
|
5
23
|
public_send(:measure_debt) if self.respond_to?(:measure_debt)
|
6
24
|
end
|
7
25
|
|
26
|
+
def default_measure_debt
|
27
|
+
external_measure_debt || debt_from_source_code_rules
|
28
|
+
end
|
29
|
+
|
8
30
|
def external_augmented_debt
|
9
31
|
(public_send(:augment_debt) if respond_to?(:augment_debt)).to_i
|
10
32
|
end
|
11
33
|
|
34
|
+
|
12
35
|
def debt_from_source_code_rules
|
13
36
|
manual_callout_debt +
|
14
|
-
text_match_debt('TODO',
|
37
|
+
text_match_debt('TODO', cost_per_todo) +
|
15
38
|
deprecated_reference_debt
|
16
39
|
end
|
17
40
|
|
@@ -20,13 +43,13 @@ module DebtCeiling
|
|
20
43
|
end
|
21
44
|
|
22
45
|
def manual_callout_debt
|
23
|
-
|
46
|
+
manual_callouts.reduce(0) do |sum, callout|
|
24
47
|
sum + debt_from_callout(callout)
|
25
48
|
end
|
26
49
|
end
|
27
50
|
|
28
51
|
def deprecated_reference_debt
|
29
|
-
|
52
|
+
deprecated_reference_pairs
|
30
53
|
.reduce(0) {|accum, (string, value)| accum + text_match_debt(string, value.to_i) }
|
31
54
|
end
|
32
55
|
|
@@ -38,14 +61,5 @@ module DebtCeiling
|
|
38
61
|
sum + amount.to_i
|
39
62
|
end
|
40
63
|
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
64
|
end
|
51
65
|
end
|
data/lib/debt_ceiling/debt.rb
CHANGED
@@ -1,28 +1,27 @@
|
|
1
|
-
require 'forwardable'
|
2
1
|
module DebtCeiling
|
3
2
|
class Debt
|
4
3
|
extend Forwardable
|
5
|
-
include
|
4
|
+
include CommonMethods
|
6
5
|
DoNotWhitelistAndBlacklistSimulateneously = Class.new(StandardError)
|
7
6
|
|
8
|
-
def_delegators :file_attributes,
|
9
|
-
|
10
|
-
|
7
|
+
def_delegators :file_attributes,
|
8
|
+
:path, :analysed_module, :module_name, :linecount, :source_code
|
9
|
+
def_delegators :configuration,
|
10
|
+
:whitelist, :blacklist
|
11
|
+
|
11
12
|
def_delegator :analysed_module, :rating
|
12
|
-
attr_accessor :debt_amount
|
13
13
|
def_delegator :debt_amount, :to_i
|
14
14
|
|
15
15
|
def initialize(file_attributes)
|
16
16
|
@file_attributes = file_attributes
|
17
|
-
|
17
|
+
if valid_debt?
|
18
|
+
debt_components = configuration.debt_types.map {|type| type.new(file_attributes) }
|
19
|
+
@debt_amount = debt_components.reduce(&:+)
|
20
|
+
end
|
18
21
|
end
|
19
22
|
|
20
23
|
def name
|
21
|
-
|
22
|
-
end
|
23
|
-
|
24
|
-
def +(other)
|
25
|
-
to_i + other.to_i
|
24
|
+
analysed_module.name || path.to_s.split('/').last
|
26
25
|
end
|
27
26
|
|
28
27
|
def letter_grade
|
@@ -31,54 +30,27 @@ module DebtCeiling
|
|
31
30
|
|
32
31
|
private
|
33
32
|
|
34
|
-
attr_reader :file_attributes
|
35
|
-
|
36
|
-
def default_measure_debt
|
37
|
-
self.debt_amount = external_measure_debt || internal_measure_debt
|
38
|
-
end
|
33
|
+
attr_reader :file_attributes, :debt_amount
|
39
34
|
|
40
35
|
def internal_measure_debt
|
41
|
-
|
42
|
-
cost_from_static_analysis_points +
|
43
|
-
debt_from_source_code_rules
|
44
|
-
end
|
45
|
-
|
46
|
-
def cost_from_static_analysis_points
|
47
|
-
DebtCeiling.grade_points[letter_grade] + cost_from_non_grade_scoring
|
48
|
-
end
|
49
|
-
|
50
|
-
def cost_from_non_grade_scoring
|
51
|
-
flog_flay_debt + method_count_debt + smells_debt + line_count_debt
|
52
|
-
end
|
53
|
-
|
54
|
-
def smells_debt
|
55
|
-
analysed_module.smells.map(&:cost).inject(0, :+) * smells_multiplier
|
56
|
-
end
|
57
|
-
|
58
|
-
def method_count_debt
|
59
|
-
analysed_module.methods_count * method_count_multiplier
|
60
|
-
end
|
61
|
-
|
62
|
-
def flog_flay_debt
|
63
|
-
analysed_module.complexity * complexity_multiplier +
|
64
|
-
analysed_module.duplication * duplication_multiplier
|
65
|
-
end
|
66
|
-
|
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
|
36
|
+
debt_types.reduce(&:+)
|
70
37
|
end
|
71
38
|
|
72
|
-
def
|
73
|
-
|
39
|
+
def whitelist_includes?(debt)
|
40
|
+
whitelist.find { |filename| debt.path.match filename }
|
74
41
|
end
|
75
42
|
|
76
|
-
def
|
77
|
-
|
43
|
+
def blacklist_includes?(debt)
|
44
|
+
blacklist.find { |filename| debt.path.match filename }
|
78
45
|
end
|
79
46
|
|
80
|
-
def
|
81
|
-
|
47
|
+
def valid_debt?
|
48
|
+
black_empty = blacklist.empty?
|
49
|
+
white_empty = whitelist.empty?
|
50
|
+
fail DoNotWhitelistAndBlacklistSimulateneously unless black_empty || white_empty
|
51
|
+
(black_empty && white_empty) ||
|
52
|
+
(black_empty && whitelist_includes?(self)) ||
|
53
|
+
(white_empty && !blacklist_includes?(self))
|
82
54
|
end
|
83
55
|
end
|
84
56
|
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module DebtCeiling
|
2
|
+
class StaticAnalysisDebt
|
3
|
+
extend Forwardable
|
4
|
+
include CommonMethods
|
5
|
+
def_delegators :configuration,
|
6
|
+
:complexity_multiplier, :duplication_multiplier, :smells_multiplier,
|
7
|
+
:grade_points, :method_count_multiplier, :ideal_max_line_count,
|
8
|
+
:cost_per_line_over_ideal
|
9
|
+
|
10
|
+
def_delegators :analysed_module,
|
11
|
+
:smells, :methods_count, :complexity, :duplication, :rating
|
12
|
+
|
13
|
+
def_delegators :file_attributes,
|
14
|
+
:analysed_module, :linecount
|
15
|
+
|
16
|
+
def_delegator :debt_amount, :to_i
|
17
|
+
|
18
|
+
def initialize(file_attributes)
|
19
|
+
@file_attributes = file_attributes
|
20
|
+
@debt_amount = cost_from_static_analysis_points
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
attr_reader :file_attributes, :debt_amount
|
26
|
+
|
27
|
+
def cost_from_static_analysis_points
|
28
|
+
grade_points[letter_grade] + cost_from_non_grade_scoring
|
29
|
+
end
|
30
|
+
|
31
|
+
def cost_from_non_grade_scoring
|
32
|
+
flog_flay_debt + method_count_debt + smells_debt + line_count_debt
|
33
|
+
end
|
34
|
+
|
35
|
+
def letter_grade
|
36
|
+
rating.to_s.downcase.to_sym
|
37
|
+
end
|
38
|
+
|
39
|
+
def smells_debt
|
40
|
+
smells.map(&:cost).inject(0, :+) * smells_multiplier
|
41
|
+
end
|
42
|
+
|
43
|
+
def method_count_debt
|
44
|
+
methods_count * method_count_multiplier
|
45
|
+
end
|
46
|
+
|
47
|
+
def flog_flay_debt
|
48
|
+
complexity * complexity_multiplier +
|
49
|
+
duplication * duplication_multiplier
|
50
|
+
end
|
51
|
+
|
52
|
+
def line_count_debt
|
53
|
+
excess_lines = linecount - ideal_max_line_count
|
54
|
+
excess_lines > 0 ? excess_lines * cost_per_line_over_ideal : 0
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
data/lib/debt_ceiling/version.rb
CHANGED
data/lib/debt_ceiling.rb
CHANGED
@@ -3,8 +3,12 @@ require 'chronic'
|
|
3
3
|
require 'rubycritic'
|
4
4
|
require 'rubycritic/cli/application'
|
5
5
|
require 'ostruct'
|
6
|
+
require 'forwardable'
|
7
|
+
require_relative 'debt_ceiling/common_methods'
|
8
|
+
require_relative 'debt_ceiling/audit'
|
6
9
|
require_relative 'debt_ceiling/accounting'
|
7
|
-
require_relative 'debt_ceiling/
|
10
|
+
require_relative 'debt_ceiling/custom_debt'
|
11
|
+
require_relative 'debt_ceiling/static_analysis_debt'
|
8
12
|
require_relative 'debt_ceiling/debt'
|
9
13
|
require_relative 'debt_ceiling/compatibility'
|
10
14
|
require_relative 'debt_ceiling/file_attributes'
|
@@ -13,18 +17,12 @@ module DebtCeiling
|
|
13
17
|
include Configurations
|
14
18
|
extend Forwardable
|
15
19
|
extend self
|
16
|
-
|
17
|
-
|
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
|
20
|
+
def_delegators :configuration,
|
21
|
+
:extension_path, :blacklist, :whitelist, :max_debt_per_module, :reduction_date,
|
22
|
+
:reduction_target, :debt_ceiling
|
25
23
|
|
26
24
|
configuration_defaults do |config|
|
27
|
-
config.extension_path = "#{Dir.pwd}/
|
25
|
+
config.extension_path = "#{Dir.pwd}/custom_debt.rb"
|
28
26
|
config.blacklist = []
|
29
27
|
config.whitelist = []
|
30
28
|
config.deprecated_reference_pairs = {}
|
@@ -36,78 +34,11 @@ module DebtCeiling
|
|
36
34
|
config.duplication_multiplier = 1.5
|
37
35
|
config.ideal_max_line_count = 100
|
38
36
|
config.cost_per_line_over_ideal = 1
|
39
|
-
|
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
|
55
|
-
end
|
56
|
-
|
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}")
|
64
|
-
else
|
65
|
-
puts "No #{config_file_name} configuration file detected in #{pwd} or ~/, using defaults"
|
66
|
-
end
|
67
|
-
|
68
|
-
load extension_path if extension_path && File.exist?(extension_path)
|
69
|
-
@loaded = true
|
70
|
-
end
|
71
|
-
|
72
|
-
def clear
|
73
|
-
@accounting_result = nil
|
74
|
-
Accounting.clear
|
75
|
-
end
|
76
|
-
|
77
|
-
private
|
78
|
-
|
79
|
-
|
80
|
-
def blacklist_matching(matchers)
|
81
|
-
@blacklist = matchers.map { |matcher| Regexp.new(matcher) }
|
82
|
-
end
|
83
|
-
|
84
|
-
def whitelist_matching(matchers)
|
85
|
-
@whitelist = matchers.map { |matcher| Regexp.new(matcher) }
|
86
|
-
end
|
87
|
-
|
88
|
-
|
89
|
-
def debt_per_reference_to(string, value)
|
90
|
-
deprecated_reference_pairs[string] = value
|
37
|
+
config.debt_types = [CustomDebt, StaticAnalysisDebt]
|
91
38
|
end
|
92
39
|
|
93
|
-
def failed_condition?
|
94
|
-
exceeded_total_limit? || missed_target? || max_debt_per_module_exceeded?
|
95
|
-
end
|
96
|
-
|
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 &&
|
103
|
-
Time.now > Chronic.parse(reduction_date)
|
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
|
108
|
-
end
|
109
40
|
|
110
|
-
def
|
111
|
-
|
41
|
+
def audit(dir, opts= {})
|
42
|
+
Audit.new(dir, opts)
|
112
43
|
end
|
113
|
-
end
|
44
|
+
end
|
data/spec/debt_ceiling_spec.rb
CHANGED
@@ -5,43 +5,46 @@ describe DebtCeiling do
|
|
5
5
|
it 'has failing exit status when debt_ceiling is exceeded' do
|
6
6
|
DebtCeiling.configure {|c| c.debt_ceiling = 0 }
|
7
7
|
expect(DebtCeiling.debt_ceiling).to eq(0)
|
8
|
-
|
8
|
+
expect_any_instance_of(DebtCeiling::Audit).to receive(:fail_test)
|
9
|
+
DebtCeiling.audit('.', preconfigured: true)
|
9
10
|
end
|
10
11
|
|
11
12
|
it 'has failing exit status when target debt reduction is missed' do
|
12
13
|
DebtCeiling.configure {|c| c.reduction_target =0; c.reduction_date = Time.now.to_s }
|
13
14
|
expect(DebtCeiling.debt_ceiling).to eq(nil)
|
14
|
-
|
15
|
+
expect_any_instance_of(DebtCeiling::Audit).to receive(:fail_test)
|
16
|
+
DebtCeiling.audit('.', preconfigured: true)
|
15
17
|
end
|
16
18
|
|
17
19
|
it 'has failing exit status when max debt per modile is exceeded' do
|
18
20
|
DebtCeiling.configure {|c| c.max_debt_per_module =5 }
|
19
21
|
expect(DebtCeiling.debt_ceiling).to eq(nil)
|
20
|
-
|
22
|
+
expect_any_instance_of(DebtCeiling::Audit).to receive(:fail_test)
|
23
|
+
DebtCeiling.audit('.', preconfigured: true)
|
21
24
|
end
|
22
25
|
|
23
26
|
it 'returns quantity of total debt' do
|
24
|
-
expect(DebtCeiling.
|
27
|
+
expect(DebtCeiling.audit('.').total_debt).to be > 5 # arbitrary non-zero amount
|
25
28
|
end
|
26
29
|
|
27
30
|
it 'adds debt for todos with specified value' do
|
28
31
|
todo_amount = 50
|
29
32
|
DebtCeiling.configure {|c| c.cost_per_todo = todo_amount }
|
30
|
-
expect(DebtCeiling.
|
33
|
+
expect(DebtCeiling.audit('spec/support/todo_example.rb').total_debt).to be todo_amount
|
31
34
|
end
|
32
35
|
|
33
36
|
it 'allows manual debt with TECH DEBT comment' do
|
34
|
-
expect(DebtCeiling.
|
37
|
+
expect(DebtCeiling.audit('spec/support/manual_example.rb').total_debt).to be 100 # hardcoded in example file
|
35
38
|
end
|
36
39
|
|
37
40
|
it 'allows manual debt with arbitrarily defined comment' do
|
38
41
|
DebtCeiling.configure {|c| c.manual_callouts += ['REFACTOR'] }
|
39
|
-
expect(DebtCeiling.
|
42
|
+
expect(DebtCeiling.audit('spec/support/manual_example.rb').total_debt).to be 150 # hardcoded in example file
|
40
43
|
end
|
41
44
|
|
42
45
|
it 'assigns debt for file length over ideal file size' do
|
43
46
|
DebtCeiling.configure {|c| c.ideal_max_line_count = 10; c.cost_per_line_over_ideal = 100 }
|
44
|
-
expect(DebtCeiling.
|
47
|
+
expect(DebtCeiling.audit('spec/support/long_file_example.rb', preconfigured: true).total_debt).to be 300 # hardcoded 13 lines long example file
|
45
48
|
end
|
46
49
|
|
47
50
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,17 +1,15 @@
|
|
1
|
-
require 'codeclimate-test-reporter'
|
2
|
-
CodeClimate::TestReporter.start
|
3
1
|
require 'coveralls'
|
4
2
|
Coveralls.wear!
|
5
3
|
|
6
4
|
RSpec.configure do |config|
|
7
5
|
config.before { allow($stdout).to receive(:puts) }
|
8
|
-
config.after(:each) { DebtCeiling.configure {|c| c.debt_ceiling = nil; c.reduction_target = nil; c.reduction_date = nil }
|
6
|
+
config.after(:each) { DebtCeiling.configure {|c| c.debt_ceiling = nil; c.reduction_target = nil; c.reduction_date = nil } }
|
9
7
|
config.after(:all) do
|
10
8
|
DebtCeiling.configure do |c|
|
11
9
|
c.whitelist = %w(app lib)
|
12
10
|
c.max_debt_per_module = 150
|
13
|
-
c.debt_ceiling =
|
11
|
+
c.debt_ceiling = 250
|
14
12
|
end
|
15
|
-
DebtCeiling.
|
13
|
+
DebtCeiling.audit('.', preconfigured: true)
|
16
14
|
end
|
17
15
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: debt_ceiling
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.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-03-
|
11
|
+
date: 2015-03-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rubycritic
|
@@ -108,20 +108,6 @@ dependencies:
|
|
108
108
|
- - "~>"
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '0.7'
|
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
111
|
description: |2
|
126
112
|
Get a grip on your technical debt
|
127
113
|
email:
|
@@ -142,13 +128,16 @@ files:
|
|
142
128
|
- bin/debt_ceiling
|
143
129
|
- debt_ceiling.gemspec
|
144
130
|
- examples/.debt_ceiling.rb.example
|
145
|
-
- examples/
|
131
|
+
- examples/custom_debt.rb.example
|
146
132
|
- lib/debt_ceiling.rb
|
147
133
|
- lib/debt_ceiling/accounting.rb
|
134
|
+
- lib/debt_ceiling/audit.rb
|
135
|
+
- lib/debt_ceiling/common_methods.rb
|
148
136
|
- lib/debt_ceiling/compatibility.rb
|
149
|
-
- lib/debt_ceiling/
|
137
|
+
- lib/debt_ceiling/custom_debt.rb
|
150
138
|
- lib/debt_ceiling/debt.rb
|
151
139
|
- lib/debt_ceiling/file_attributes.rb
|
140
|
+
- lib/debt_ceiling/static_analysis_debt.rb
|
152
141
|
- lib/debt_ceiling/version.rb
|
153
142
|
- spec/debt_ceiling_spec.rb
|
154
143
|
- spec/spec_helper.rb
|