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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 50413717fff100682e4d62f350b6b0625c92f5bf
4
- data.tar.gz: 17ad97252626d36a70b221725fd7213962951dd6
3
+ metadata.gz: 1f143bb4a889c52ce57b69ee02ecaa062dde87eb
4
+ data.tar.gz: d31f7da0e0e5221bb10dbbb2db7f382cdcc6f603
5
5
  SHA512:
6
- metadata.gz: 9cb9b3b28fa5aa7f3b09e832b7b279412181d71157e7ac4cbd9f8bc4e5fb2d3d7cc759a76806aad8193bb9f997e15d9ec732a617d65619ff620081fd6131d4ae
7
- data.tar.gz: 76afab03d0406fe9a1988ec25e648629afea95702e9356645073253b325d00a28b6cde6ea74169fd36dcc43805a97c6216e1cbeb14a97f5695083b24190da677
6
+ metadata.gz: 93f9335622c57589b9d7de6a207074af558b2e111dd2056351e7db133eee7c8dfa40a99627e884ad4b408758720fbd81d1abf15933b185e052a1cc6661b84f90
7
+ data.tar.gz: 57423abb6795759193d1e27c064e4ec9bfe83a2b2efe32bddb10ed7dfedeb73aa64449e21d054f892ce887696e1a104241a1bf238fc476c25325c087af2a08b3
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
- [![Gem Version](https://badge.fury.io/rb/debt_ceiling.svg)](http://badge.fury.io/rb/debt_ceiling)
1
+ [![Gem Version](https://badge.fury.io/rb/debt_ceiling.svg)](http://badge.fury.io/rb/debt_ceiling)
2
2
  [![Build Status](https://travis-ci.org/bglusman/debt_ceiling.svg?branch=master)](https://travis-ci.org/bglusman/debt_ceiling)
3
- [![Coverage Status](https://img.shields.io/coveralls/bglusman/debt_ceiling.svg)](https://coveralls.io/r/bglusman/debt_ceiling?branch=master)
4
- [![Debt Ceiling Chat](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/bglusman/debt_ceiling)
5
- [![Stories in Ready](https://badge.waffle.io/bglusman/debt_ceiling.png?label=ready&title=Ready)](https://waffle.io/bglusman/debt_ceiling)
3
+ [![Coverage Status](https://img.shields.io/coveralls/bglusman/debt_ceiling.svg)](https://coveralls.io/r/bglusman/debt_ceiling?branch=master)
4
+ [![Debt Ceiling Chat](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/bglusman/debt_ceiling)
5
+ [![Stories in Ready](https://badge.waffle.io/bglusman/debt_ceiling.png?label=ready&title=Ready)](https://waffle.io/bglusman/debt_ceiling)
6
6
  [![Code Climate](https://codeclimate.com/github/bglusman/debt_ceiling/badges/gpa.svg)](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.calculate(root_dir)` from your test helper as an additional test. It will exit with a non-zero failure if you exceed your ceiling or miss your target, failing the test suite.
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
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env ruby
2
2
  require_relative '../lib/debt_ceiling'
3
- DebtCeiling.calculate(ARGV[0] ? ARGV[0] : '.')
3
+ DebtCeiling.audit(ARGV[0] ? ARGV[0] : '.')
data/debt_ceiling.gemspec CHANGED
@@ -30,5 +30,4 @@ Gem::Specification.new do |s|
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'
34
33
  end
@@ -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 2015'
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 = 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
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/debt.rb) from this path
26
- c.extension_path = "./debt.rb"
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 Debt
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
- class << self
6
- attr_reader :result, :path
7
- def calculate(path)
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
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
@@ -0,0 +1,13 @@
1
+ module DebtCeiling
2
+ module CommonMethods
3
+
4
+ def configuration
5
+ DebtCeiling.configuration
6
+ end
7
+
8
+ def +(other)
9
+ self.to_i + other.to_i
10
+ end
11
+
12
+ end
13
+ end
@@ -1,17 +1,40 @@
1
1
  module DebtCeiling
2
- module CustomDebtAnalysis
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', DebtCeiling.cost_per_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
- DebtCeiling.manual_callouts.reduce(0) do |sum, callout|
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
- DebtCeiling.deprecated_reference_pairs
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
@@ -1,28 +1,27 @@
1
- require 'forwardable'
2
1
  module DebtCeiling
3
2
  class Debt
4
3
  extend Forwardable
5
- include CustomDebtAnalysis
4
+ include CommonMethods
6
5
  DoNotWhitelistAndBlacklistSimulateneously = Class.new(StandardError)
7
6
 
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
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
- default_measure_debt if valid_debt?
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
- file_attributes.analysed_module.name || path.to_s.split('/').last
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
- external_augmented_debt +
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 non_grade_scoring
73
- DebtCeiling
39
+ def whitelist_includes?(debt)
40
+ whitelist.find { |filename| debt.path.match filename }
74
41
  end
75
42
 
76
- def self.whitelist_includes?(debt)
77
- DebtCeiling.whitelist.find { |filename| debt.path.match filename }
43
+ def blacklist_includes?(debt)
44
+ blacklist.find { |filename| debt.path.match filename }
78
45
  end
79
46
 
80
- def self.blacklist_includes?(debt)
81
- DebtCeiling.blacklist.find { |filename| debt.path.match filename }
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
@@ -1,3 +1,3 @@
1
1
  module DebtCeiling
2
- VERSION = '0.2.0'
2
+ VERSION = '0.3.0'
3
3
  end
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/custom_debt_analysis'
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
- 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
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}/debt.rb"
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
- #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
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 fail_test
111
- Kernel.exit 1
41
+ def audit(dir, opts= {})
42
+ Audit.new(dir, opts)
112
43
  end
113
- end
44
+ end
@@ -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
- expect { DebtCeiling.calculate('.', preconfigured: true) }.to raise_error
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
- expect { DebtCeiling.calculate('.', preconfigured: true) }.to raise_error(SystemExit)
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
- expect { DebtCeiling.calculate('.', preconfigured: true) }.to raise_error(SystemExit)
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.calculate('.')).to be > 5 # arbitrary non-zero amount
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.calculate('spec/support/todo_example.rb')).to be todo_amount
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.calculate('spec/support/manual_example.rb')).to be 100 # hardcoded in example file
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.calculate('spec/support/manual_example.rb')).to be 150 # hardcoded in example file
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.calculate('spec/support/long_file_example.rb', preconfigured: true)).to be 300 # hardcoded 13 lines long example file
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 } ; DebtCeiling.clear }
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 = 300
11
+ c.debt_ceiling = 250
14
12
  end
15
- DebtCeiling.calculate('.')
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.2.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-24 00:00:00.000000000 Z
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/debt.rb.example
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/custom_debt_analysis.rb
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