debt_ceiling 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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