debt_ceiling 0.0.4 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +7 -4
- data/Rakefile +0 -5
- data/examples/.debt_ceiling.example +8 -2
- data/lib/debt_ceiling/accounting.rb +24 -14
- data/lib/debt_ceiling/debt.rb +17 -1
- data/lib/debt_ceiling/version.rb +1 -1
- data/lib/debt_ceiling.rb +13 -2
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bea87b2ccec435c3759009586048f178a40d963f
|
4
|
+
data.tar.gz: 9538baf4d664690d4deb022db375f1a024471a2c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9545baeb0a2fb3f1f5bb21d3642d3e63d7aae32a9124dce6bfd11a3536a8e177f5854c37917fda715439702d18f088b97a0134c75bc9d904685d497c84fe4a9f
|
7
|
+
data.tar.gz: ed0148e021b41fa1618abf56458068a7afa1d01ee959c316987ea5f4198170a812d5a15db9ac88bb1b50295f52a7055ced1130e401139087ac1ac20db569cf47
|
data/README.md
CHANGED
@@ -7,11 +7,12 @@
|
|
7
7
|
Main goal is to enforce a technical debt ceiling and tech debt reduction deadlines for your Ruby project programmatically via static analysis as part of your application's test suite. Eventually perhaps will aid in visualizing tech debt as a graph or graphs (breakind down debt into various categories and sources).
|
8
8
|
|
9
9
|
Current features include:
|
10
|
-
* configuring points per [RubyCritic](https://github.com/whitesmith/rubycritic) grade per file line
|
10
|
+
* configuring points per [RubyCritic](https://github.com/whitesmith/rubycritic) grade per file line (add FULL_ANALYSIS=true for a lengthier analysis by RubyCritic including churn and more code smells, but same grading logic, made available for use by hooks)
|
11
11
|
* Whitelisting/blacklisting files by matching path/filename
|
12
12
|
* Modifying or replacing default calculation on a per file basis
|
13
13
|
* Reporting the single greatest source of debt based on your definitions
|
14
14
|
* Reporting total debt for the git repo based on your definitions
|
15
|
+
* Adding cost for TODOs or deprecated references you specify (see .debt_ceiling.example)
|
15
16
|
* Running the binary from a test suite to fail if debt ceiling is exceeded
|
16
17
|
* Running the binary from a test suite to fail if debt deadline is missed
|
17
18
|
|
@@ -49,7 +50,7 @@ As mentioned/linked above, additional customization is supported.
|
|
49
50
|
|
50
51
|
As shown in example file, pass a path to `extension_file_path` command pointing to a file defining DebtCeiling::Debt like the one in examples directory, and define its methods for your own additional calculation per file.
|
51
52
|
|
52
|
-
Right now it lacks all tests... feel free to open a PR!
|
53
|
+
Right now it lacks all tests... feel free to open a PR!
|
53
54
|
|
54
55
|
I'll try and add test coverage where it makes sense as API matures.
|
55
56
|
|
@@ -57,7 +58,7 @@ I'll try and add test coverage where it makes sense as API matures.
|
|
57
58
|
|
58
59
|
todo_cost 500 #cost per comment matching /TODO/
|
59
60
|
|
60
|
-
debt_per_reference_to deprecated_regex, 500 # help transition away from deprecated APIs
|
61
|
+
debt_per_reference_to deprecated_regex, 500 # help transition away from deprecated APIs
|
61
62
|
|
62
63
|
points_per_regex_match REGEX, 500 # seems like an alias for above maybe, nix?
|
63
64
|
|
@@ -70,7 +71,9 @@ every line over x ideal file size is y points of debt
|
|
70
71
|
|
71
72
|
multipliers for important files
|
72
73
|
|
73
|
-
|
74
|
+
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.
|
75
|
+
|
76
|
+
include one of the JS complexity/debt analysis libraries below, or another if anyone had another suggestion:
|
74
77
|
|
75
78
|
* https://github.com/es-analysis/plato
|
76
79
|
|
data/Rakefile
CHANGED
@@ -12,8 +12,14 @@ f_cost_per_line 100
|
|
12
12
|
#load custom debt calculations (see examples/debt.rb) from this path
|
13
13
|
extension_file_path "./debt.rb"
|
14
14
|
|
15
|
-
#
|
15
|
+
#below two both same mechanic, todo just assumes capital TODO as string
|
16
|
+
cost_per_todo 50
|
17
|
+
|
18
|
+
debt_per_reference_to 'DEPRECATED_API', 20
|
19
|
+
|
20
|
+
|
21
|
+
#only count debt scores for file paths matching these strings (converted to regexes)
|
16
22
|
whitelist_matching %w(app lib)
|
17
23
|
#or
|
18
24
|
#exclude debt scores for files matching these strings (commented as mutually exclusive)
|
19
|
-
#blacklist_matching %w(schema.rb routes.rb)
|
25
|
+
#blacklist_matching %w(schema.rb routes.rb)
|
@@ -6,32 +6,42 @@ module DebtCeiling
|
|
6
6
|
TargetDeadlineMissed = Class.new(StandardError)
|
7
7
|
class << self
|
8
8
|
def calculate(path)
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
analysed_modules = Rubycritic::ModulesInitializer.init([path])
|
15
|
-
[Rubycritic::Analyser::Complexity, Rubycritic::Analyser::FlaySmells].each do |analyser|
|
16
|
-
analyser.new(analysed_modules).run
|
17
|
-
end
|
18
|
-
debts = construct_debts(analysed_modules)
|
19
|
-
max_debt = debts.max_by(&:to_i)
|
20
|
-
total_debt = debts.map(&:to_i).reduce(&:+)
|
9
|
+
analysed_modules = construct_rubycritic_modules(path)
|
10
|
+
debts = construct_debts(analysed_modules)
|
11
|
+
max_debt = debts.max_by(&:to_i)
|
12
|
+
total_debt = debts.map(&:to_i).reduce(:+)
|
21
13
|
puts "Current total tech debt: #{total_debt}"
|
22
|
-
puts "Largest source of debt is: #{max_debt.
|
14
|
+
puts "Largest source of debt is: #{max_debt.name} at #{max_debt.to_i}"
|
23
15
|
total_debt
|
24
16
|
end
|
25
17
|
|
26
18
|
def construct_debts(modules)
|
27
|
-
modules.map do |mod|
|
19
|
+
modules.map do |mod|
|
28
20
|
file_attributes = OpenStruct.new
|
29
21
|
file_attributes.linecount = `wc -l #{mod.path}`.match(/\d+/)[0].to_i
|
30
22
|
file_attributes.path = mod.path
|
31
23
|
file_attributes.analysed_module = mod
|
24
|
+
file_attributes.source_code = File.read(mod.path)
|
32
25
|
debt_rule = Debt.new(file_attributes)
|
33
26
|
end
|
34
27
|
end
|
28
|
+
|
29
|
+
def construct_rubycritic_modules(path)
|
30
|
+
if ENV['FULL_ANALYSIS']
|
31
|
+
Rubycritic::Orchestrator.new.critique([path])
|
32
|
+
else
|
33
|
+
#temporarily use Rubycritic internals until they provide an API
|
34
|
+
require "rubycritic/modules_initializer"
|
35
|
+
require "rubycritic/analysers/complexity"
|
36
|
+
require "rubycritic/analysers/smells/flay"
|
37
|
+
|
38
|
+
modules = Rubycritic::ModulesInitializer.init([path])
|
39
|
+
[Rubycritic::Analyser::Complexity, Rubycritic::Analyser::FlaySmells].each do |analyser|
|
40
|
+
analyser.new(modules).run
|
41
|
+
end
|
42
|
+
modules
|
43
|
+
end
|
44
|
+
end
|
35
45
|
end
|
36
46
|
end
|
37
47
|
end
|
data/lib/debt_ceiling/debt.rb
CHANGED
@@ -2,7 +2,7 @@ module DebtCeiling
|
|
2
2
|
class Debt
|
3
3
|
DoNotWhitelistAndBlacklistSimulateneously = Class.new(StandardError)
|
4
4
|
|
5
|
-
attr_reader :file_attributes, :path, :analysed_module, :module_name, :linecount
|
5
|
+
attr_reader :file_attributes, :path, :analysed_module, :module_name, :linecount, :source_code
|
6
6
|
attr_accessor :debt_amount
|
7
7
|
def initialize(file_attributes)
|
8
8
|
@file_attributes = file_attributes
|
@@ -10,6 +10,7 @@ module DebtCeiling
|
|
10
10
|
@analysed_module = file_attributes.analysed_module
|
11
11
|
@module_name = file_attributes.name
|
12
12
|
@linecount = file_attributes.linecount
|
13
|
+
@source_code = file_attributes.source_code
|
13
14
|
default_measure_debt if valid_debt?
|
14
15
|
end
|
15
16
|
|
@@ -26,10 +27,21 @@ module DebtCeiling
|
|
26
27
|
letter_grade = file_attributes.analysed_module.rating.to_s.downcase
|
27
28
|
cost_per_line = DebtCeiling.public_send("#{letter_grade}_current_cost_per_line")
|
28
29
|
cost += file_attributes.linecount * cost_per_line
|
30
|
+
cost += debt_from_source_code_rules
|
29
31
|
end
|
30
32
|
self.debt_amount = cost
|
31
33
|
end
|
32
34
|
|
35
|
+
def debt_from_source_code_rules
|
36
|
+
text_match_debt('TODO', DebtCeiling.current_cost_per_todo) +
|
37
|
+
DebtCeiling.deprecated_reference_pairs.map {|string, value|
|
38
|
+
text_match_debt(string, value.to_i) }.inject(&:+).to_i
|
39
|
+
end
|
40
|
+
|
41
|
+
def text_match_debt(string, cost)
|
42
|
+
source_code.scan(string).count * cost
|
43
|
+
end
|
44
|
+
|
33
45
|
def valid_debt?
|
34
46
|
black_empty = DebtCeiling.blacklist.empty?
|
35
47
|
white_empty = DebtCeiling.whitelist.empty?
|
@@ -47,6 +59,10 @@ module DebtCeiling
|
|
47
59
|
DebtCeiling.blacklist.detect {|filename| filename.match debt.path }
|
48
60
|
end
|
49
61
|
|
62
|
+
def name
|
63
|
+
file_attributes.analysed_module.name || path.to_s.split('/').last
|
64
|
+
end
|
65
|
+
|
50
66
|
def to_i
|
51
67
|
debt_amount.to_i
|
52
68
|
end
|
data/lib/debt_ceiling/version.rb
CHANGED
data/lib/debt_ceiling.rb
CHANGED
@@ -42,6 +42,14 @@ module DebtCeiling
|
|
42
42
|
@ceiling_amount = value
|
43
43
|
end
|
44
44
|
|
45
|
+
def cost_per_todo(value)
|
46
|
+
@current_cost_per_todo = value.to_i
|
47
|
+
end
|
48
|
+
|
49
|
+
def debt_per_reference_to(string, value)
|
50
|
+
deprecated_reference_pairs[string] = value
|
51
|
+
end
|
52
|
+
|
45
53
|
def debt_reduction_target_and_date(target_value, date_to_parse)
|
46
54
|
@reduction_target = target_value
|
47
55
|
@reduction_date = Chronic.parse(date_to_parse)
|
@@ -50,7 +58,7 @@ module DebtCeiling
|
|
50
58
|
def evaluate
|
51
59
|
if ceiling_amount && ceiling_amount <= debt
|
52
60
|
fail_test
|
53
|
-
elsif reduction_target && reduction_target <= debt &&
|
61
|
+
elsif reduction_target && reduction_target <= debt &&
|
54
62
|
Time.now > reduction_date
|
55
63
|
fail_test
|
56
64
|
end
|
@@ -60,9 +68,12 @@ module DebtCeiling
|
|
60
68
|
Kernel.exit 1
|
61
69
|
end
|
62
70
|
|
63
|
-
attr_reader :blacklist, :whitelist, :ceiling_amount, :reduction_date, :reduction_target,
|
71
|
+
attr_reader :blacklist, :whitelist, :ceiling_amount, :reduction_date, :reduction_target,
|
72
|
+
:debt, :current_cost_per_todo, :deprecated_reference_pairs
|
64
73
|
@blacklist = []
|
65
74
|
@whitelist = []
|
75
|
+
@current_cost_per_todo = 0
|
76
|
+
@deprecated_reference_pairs = {}
|
66
77
|
|
67
78
|
GRADE_MAP = {a: 0, b: 10, c: 20, d: 40, f: 100} #arbitrary default grades for now
|
68
79
|
GRADE_MAP.keys.each do |grade|
|
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.0.
|
4
|
+
version: 0.0.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brian Glusman
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-10-
|
11
|
+
date: 2014-10-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rubycritic
|