reek 1.3.1 → 1.3.2

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.
Files changed (128) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG +6 -0
  3. data/README.md +15 -9
  4. data/bin/reek +1 -1
  5. data/config/defaults.reek +71 -86
  6. data/features/command_line_interface/options.feature +0 -15
  7. data/features/reports/reports.feature +23 -0
  8. data/features/samples.feature +3 -12
  9. data/lib/reek.rb +3 -3
  10. data/lib/reek/cli/application.rb +1 -1
  11. data/lib/reek/cli/command_line.rb +10 -8
  12. data/lib/reek/cli/reek_command.rb +6 -7
  13. data/lib/reek/cli/report.rb +34 -38
  14. data/lib/reek/cli/version_command.rb +1 -1
  15. data/lib/reek/cli/yaml_command.rb +1 -1
  16. data/lib/reek/core/code_parser.rb +4 -4
  17. data/lib/reek/core/hash_extensions.rb +2 -2
  18. data/lib/reek/core/method_context.rb +2 -2
  19. data/lib/reek/core/module_context.rb +4 -4
  20. data/lib/reek/core/singleton_method_context.rb +1 -1
  21. data/lib/reek/core/smell_repository.rb +7 -6
  22. data/lib/reek/core/sniffer.rb +4 -4
  23. data/lib/reek/examiner.rb +10 -3
  24. data/lib/reek/smell_warning.rb +0 -2
  25. data/lib/reek/smells.rb +22 -21
  26. data/lib/reek/smells/attribute.rb +4 -8
  27. data/lib/reek/smells/boolean_parameter.rb +2 -2
  28. data/lib/reek/smells/class_variable.rb +3 -2
  29. data/lib/reek/smells/{control_couple.rb → control_parameter.rb} +5 -5
  30. data/lib/reek/smells/data_clump.rb +13 -29
  31. data/lib/reek/smells/{duplication.rb → duplicate_method_call.rb} +9 -11
  32. data/lib/reek/smells/feature_envy.rb +2 -2
  33. data/lib/reek/smells/irresponsible_module.rb +3 -2
  34. data/lib/reek/smells/long_parameter_list.rb +6 -10
  35. data/lib/reek/smells/long_yield_list.rb +4 -8
  36. data/lib/reek/smells/nested_iterators.rb +31 -25
  37. data/lib/reek/smells/nil_check.rb +11 -12
  38. data/lib/reek/smells/{simulated_polymorphism.rb → repeated_conditional.rb} +6 -10
  39. data/lib/reek/smells/smell_detector.rb +3 -6
  40. data/lib/reek/smells/too_many_instance_variables.rb +60 -0
  41. data/lib/reek/smells/too_many_methods.rb +62 -0
  42. data/lib/reek/smells/{long_method.rb → too_many_statements.rb} +7 -12
  43. data/lib/reek/smells/uncommunicative_method_name.rb +3 -7
  44. data/lib/reek/smells/uncommunicative_module_name.rb +3 -7
  45. data/lib/reek/smells/uncommunicative_parameter_name.rb +4 -8
  46. data/lib/reek/smells/uncommunicative_variable_name.rb +5 -9
  47. data/lib/reek/smells/unused_parameters.rb +62 -13
  48. data/lib/reek/smells/utility_function.rb +3 -7
  49. data/lib/reek/source.rb +8 -8
  50. data/lib/reek/source/core_extras.rb +1 -1
  51. data/lib/reek/source/source_code.rb +2 -2
  52. data/lib/reek/source/source_file.rb +2 -2
  53. data/lib/reek/source/source_locator.rb +1 -1
  54. data/lib/reek/source/source_repository.rb +4 -2
  55. data/lib/reek/spec.rb +9 -3
  56. data/lib/reek/spec/should_reek.rb +2 -2
  57. data/lib/reek/spec/should_reek_of.rb +1 -1
  58. data/lib/reek/spec/should_reek_only_of.rb +2 -2
  59. data/lib/reek/version.rb +1 -1
  60. data/reek.gemspec +3 -1
  61. data/spec/gem/updates_spec.rb +1 -1
  62. data/spec/gem/yard_spec.rb +1 -1
  63. data/spec/matchers/smell_of_matcher.rb +53 -19
  64. data/spec/reek/cli/help_command_spec.rb +2 -2
  65. data/spec/reek/cli/reek_command_spec.rb +6 -6
  66. data/spec/reek/cli/report_spec.rb +6 -6
  67. data/spec/reek/cli/version_command_spec.rb +2 -2
  68. data/spec/reek/cli/yaml_command_spec.rb +2 -2
  69. data/spec/reek/core/code_context_spec.rb +4 -4
  70. data/spec/reek/core/code_parser_spec.rb +2 -2
  71. data/spec/reek/core/config_spec.rb +4 -4
  72. data/spec/reek/core/method_context_spec.rb +3 -3
  73. data/spec/reek/core/module_context_spec.rb +3 -3
  74. data/spec/reek/core/object_refs_spec.rb +3 -3
  75. data/spec/reek/core/singleton_method_context_spec.rb +4 -4
  76. data/spec/reek/core/smell_configuration_spec.rb +2 -2
  77. data/spec/reek/core/stop_context_spec.rb +2 -2
  78. data/spec/reek/core/warning_collector_spec.rb +3 -3
  79. data/spec/reek/examiner_spec.rb +13 -4
  80. data/spec/reek/smell_warning_spec.rb +2 -2
  81. data/spec/reek/smells/attribute_spec.rb +4 -4
  82. data/spec/reek/smells/boolean_parameter_spec.rb +3 -3
  83. data/spec/reek/smells/class_variable_spec.rb +4 -4
  84. data/spec/reek/smells/{control_couple_spec.rb → control_parameter_spec.rb} +10 -10
  85. data/spec/reek/smells/data_clump_spec.rb +3 -3
  86. data/spec/reek/smells/{duplication_spec.rb → duplicate_method_call_spec.rb} +42 -26
  87. data/spec/reek/smells/feature_envy_spec.rb +3 -3
  88. data/spec/reek/smells/irresponsible_module_spec.rb +3 -3
  89. data/spec/reek/smells/long_parameter_list_spec.rb +3 -3
  90. data/spec/reek/smells/long_yield_list_spec.rb +3 -3
  91. data/spec/reek/smells/nested_iterators_spec.rb +42 -4
  92. data/spec/reek/smells/nil_check_spec.rb +23 -11
  93. data/spec/reek/smells/{simulated_polymorphism_spec.rb → repeated_conditional_spec.rb} +6 -6
  94. data/spec/reek/smells/smell_detector_shared.rb +2 -2
  95. data/spec/reek/smells/too_many_instance_variables_spec.rb +62 -0
  96. data/spec/reek/smells/{large_class_spec.rb → too_many_methods_spec.rb} +11 -56
  97. data/spec/reek/smells/{long_method_spec.rb → too_many_statements_spec.rb} +17 -17
  98. data/spec/reek/smells/uncommunicative_method_name_spec.rb +5 -5
  99. data/spec/reek/smells/uncommunicative_module_name_spec.rb +5 -5
  100. data/spec/reek/smells/uncommunicative_parameter_name_spec.rb +4 -4
  101. data/spec/reek/smells/uncommunicative_variable_name_spec.rb +5 -5
  102. data/spec/reek/smells/unused_parameters_spec.rb +19 -4
  103. data/spec/reek/smells/utility_function_spec.rb +3 -3
  104. data/spec/reek/source/code_comment_spec.rb +2 -2
  105. data/spec/reek/source/object_source_spec.rb +1 -1
  106. data/spec/reek/source/reference_collector_spec.rb +2 -2
  107. data/spec/reek/source/sexp_formatter_spec.rb +2 -2
  108. data/spec/reek/source/source_code_spec.rb +2 -2
  109. data/spec/reek/source/tree_dresser_spec.rb +2 -2
  110. data/spec/reek/spec/should_reek_of_spec.rb +2 -2
  111. data/spec/reek/spec/should_reek_only_of_spec.rb +2 -2
  112. data/spec/reek/spec/should_reek_spec.rb +2 -2
  113. data/spec/samples/all_but_one_masked/masked.reek +1 -1
  114. data/spec/samples/clean_due_to_masking/masked.reek +1 -1
  115. data/spec/samples/config/allow_duplication.reek +2 -2
  116. data/spec/samples/inline_config/dirty.rb +2 -2
  117. data/spec/samples/mask_some/some.reek +1 -1
  118. data/spec/samples/masked_by_dotfile/dirty.rb +8 -0
  119. data/spec/samples/not_quite_masked/smelly.rb +3 -0
  120. data/spec/samples/overrides/masked/lower.reek +1 -1
  121. data/spec/samples/overrides/upper.reek +1 -1
  122. data/spec/spec_helper.rb +4 -9
  123. data/tasks/test.rake +0 -2
  124. metadata +253 -263
  125. data/lib/reek/smells/large_class.rb +0 -87
  126. data/lib/xp.reek +0 -66
  127. data/spec/gem/manifest_spec.rb +0 -22
  128. data/spec/spec.opts +0 -1
@@ -1,5 +1,5 @@
1
- require File.join( File.dirname( File.expand_path(__FILE__)), 'smell_detector')
2
- require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'smell_warning')
1
+ require 'reek/smells/smell_detector'
2
+ require 'reek/smell_warning'
3
3
 
4
4
  module Reek
5
5
  module Smells
@@ -9,7 +9,7 @@ module Reek
9
9
  # or when two fragments of code have nearly identical effects
10
10
  # at some conceptual level.
11
11
  #
12
- # Currently +Duplication+ checks for repeated identical method calls
12
+ # +DuplicateMethodCall+ checks for repeated identical method calls
13
13
  # within any one method definition. For example, the following method
14
14
  # will report a warning:
15
15
  #
@@ -17,10 +17,11 @@ module Reek
17
17
  # @other.thing + @other.thing
18
18
  # end
19
19
  #
20
- class Duplication < SmellDetector
20
+ class DuplicateMethodCall < SmellDetector
21
+
22
+ SMELL_CLASS = 'Duplication'
23
+ SMELL_SUBCLASS = self.name.split(/::/)[-1]
21
24
 
22
- SMELL_CLASS = self.name.split(/::/)[-1]
23
- SMELL_SUBCLASS = 'DuplicateMethodCall'
24
25
  CALL_KEY = 'call'
25
26
  OCCURRENCES_KEY = 'occurrences'
26
27
 
@@ -44,10 +45,6 @@ module Reek
44
45
  )
45
46
  end
46
47
 
47
- def initialize(source, config = Duplication.default_config)
48
- super(source, config)
49
- end
50
-
51
48
  #
52
49
  # Looks for duplicate calls within the body of the method +ctx+.
53
50
  #
@@ -76,12 +73,13 @@ module Reek
76
73
  result = Hash.new {|hash,key| hash[key] = []}
77
74
  method_ctx.local_nodes(:call) do |call_node|
78
75
  next if call_node.method_name == :new
76
+ next if call_node.receiver.nil? && call_node.args.empty?
79
77
  result[call_node].push(call_node)
80
78
  end
81
79
  method_ctx.local_nodes(:attrasgn) do |asgn_node|
82
80
  result[asgn_node].push(asgn_node) unless asgn_node.args.nil?
83
81
  end
84
- result
82
+ result.to_a.sort_by {|call_exp, _| call_exp.format_ruby}
85
83
  end
86
84
 
87
85
  def allow_calls?(method)
@@ -1,5 +1,5 @@
1
- require File.join( File.dirname( File.expand_path(__FILE__)), 'smell_detector')
2
- require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'smell_warning')
1
+ require 'reek/smells/smell_detector'
2
+ require 'reek/smell_warning'
3
3
 
4
4
  module Reek
5
5
  module Smells
@@ -1,5 +1,6 @@
1
- require File.join( File.dirname( File.expand_path(__FILE__)), 'smell_detector')
2
- require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'source')
1
+ require 'reek/smells/smell_detector'
2
+ require 'reek/smell_warning'
3
+ require 'reek/source/code_comment'
3
4
 
4
5
  module Reek
5
6
  module Smells
@@ -1,6 +1,6 @@
1
- require File.join( File.dirname( File.expand_path(__FILE__)), 'smell_detector')
2
- require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'smell_warning')
3
- require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'core', 'smell_configuration')
1
+ require 'reek/smells/smell_detector'
2
+ require 'reek/smell_warning'
3
+ require 'reek/core/smell_configuration'
4
4
 
5
5
  module Reek
6
6
  module Smells
@@ -15,8 +15,8 @@ module Reek
15
15
  #
16
16
  class LongParameterList < SmellDetector
17
17
 
18
- SMELL_CLASS = self.name.split(/::/)[-1]
19
- SMELL_SUBCLASS = 'LongParameterList'
18
+ SMELL_CLASS = 'LongParameterList'
19
+ SMELL_SUBCLASS = self.name.split(/::/)[-1]
20
20
 
21
21
  PARAMETER_COUNT_KEY = 'parameter_count'
22
22
 
@@ -33,14 +33,10 @@ module Reek
33
33
  MAX_ALLOWED_PARAMS_KEY => DEFAULT_MAX_ALLOWED_PARAMS,
34
34
  Core::SmellConfiguration::OVERRIDES_KEY => {
35
35
  "initialize" => {MAX_ALLOWED_PARAMS_KEY => 5}
36
- }
36
+ }
37
37
  )
38
38
  end
39
39
 
40
- def initialize(source, config = LongParameterList.default_config)
41
- super(source, config)
42
- end
43
-
44
40
  #
45
41
  # Checks the number of parameters in the given method.
46
42
  #
@@ -1,5 +1,5 @@
1
- require File.join( File.dirname( File.expand_path(__FILE__)), 'smell_detector')
2
- require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'smell_warning')
1
+ require 'reek/smells/smell_detector'
2
+ require 'reek/smell_warning'
3
3
 
4
4
  module Reek
5
5
  module Smells
@@ -10,8 +10,8 @@ module Reek
10
10
  #
11
11
  class LongYieldList < SmellDetector
12
12
 
13
- SMELL_SUBCLASS = self.name.split(/::/)[-1]
14
13
  SMELL_CLASS = 'LongParameterList'
14
+ SMELL_SUBCLASS = self.name.split(/::/)[-1]
15
15
 
16
16
  # The name of the config field that sets the maximum number of
17
17
  # parameters permitted in any method or block.
@@ -25,14 +25,10 @@ module Reek
25
25
 
26
26
  def self.default_config
27
27
  super.adopt(
28
- MAX_ALLOWED_PARAMS_KEY => DEFAULT_MAX_ALLOWED_PARAMS
28
+ MAX_ALLOWED_PARAMS_KEY => DEFAULT_MAX_ALLOWED_PARAMS
29
29
  )
30
30
  end
31
31
 
32
- def initialize(source, config = LongYieldList.default_config)
33
- super(source, config)
34
- end
35
-
36
32
  #
37
33
  # Checks the number of parameters in the given scope.
38
34
  #
@@ -1,5 +1,5 @@
1
- require File.join( File.dirname( File.expand_path(__FILE__)), 'smell_detector')
2
- require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'smell_warning')
1
+ require 'reek/smells/smell_detector'
2
+ require 'reek/smell_warning'
3
3
 
4
4
  module Reek
5
5
  module Smells
@@ -35,53 +35,59 @@ module Reek
35
35
  )
36
36
  end
37
37
 
38
- def initialize(source, config = NestedIterators.default_config)
39
- super(source, config)
40
- end
41
-
42
38
  #
43
39
  # Checks whether the given +block+ is inside another.
44
40
  #
45
41
  # @return [Array<SmellWarning>]
46
42
  #
47
43
  def examine_context(ctx)
48
- @ignore_iterators = value(IGNORE_ITERATORS_KEY, ctx, DEFAULT_IGNORE_ITERATORS)
49
- @max_allowed_nesting = value(MAX_ALLOWED_NESTING_KEY, ctx, DEFAULT_MAX_ALLOWED_NESTING)
50
- find_deepest_iterators(ctx).map do |iter|
51
- depth = iter[1]
52
- SmellWarning.new(SMELL_CLASS, ctx.full_name, [iter[0].line],
44
+ exp, depth = *find_deepest_iterator(ctx)
45
+
46
+ if depth && depth > value(MAX_ALLOWED_NESTING_KEY, ctx, DEFAULT_MAX_ALLOWED_NESTING)
47
+ smell = SmellWarning.new(SMELL_CLASS, ctx.full_name, [exp.line],
53
48
  "contains iterators nested #{depth} deep",
54
49
  @source, SMELL_SUBCLASS,
55
50
  {NESTING_DEPTH_KEY => depth})
51
+ [smell]
52
+ else
53
+ []
56
54
  end
57
55
  # BUG: no longer reports nesting outside methods (eg. in Optparse)
58
56
  end
59
57
 
60
58
  private
61
59
 
62
- def find_deepest_iterators(ctx)
63
- result = []
64
- find_iters(ctx.exp, 1, result)
65
- result.select {|item| item[1] > @max_allowed_nesting}
60
+ def find_deepest_iterator(ctx)
61
+ @ignore_iterators = value(IGNORE_ITERATORS_KEY, ctx, DEFAULT_IGNORE_ITERATORS)
62
+
63
+ find_iters(ctx.exp, 1).sort_by {|item| item[1]}.last
66
64
  end
67
65
 
68
- def find_iters(exp, depth, result)
69
- exp.each do |elem|
66
+ def find_iters(exp, depth)
67
+ exp.map do |elem|
70
68
  next unless Sexp === elem
71
69
  case elem.first
72
70
  when :iter
73
- find_iters([elem.call], depth, result)
74
- current = result.length
75
- call = Source::SexpFormatter.format(elem.call)
76
- ignored = @ignore_iterators.any? { |ignore| /#{ignore}/ === call }
77
- find_iters([elem.block], depth + (ignored ? 0 : 1), result)
78
- result << [elem, depth] if result.length == current unless ignored
71
+ find_iters_for_iter_node(elem, depth)
79
72
  when :class, :defn, :defs, :module
80
73
  next
81
74
  else
82
- find_iters(elem, depth, result)
75
+ find_iters(elem, depth)
83
76
  end
84
- end
77
+ end.flatten(1).compact
78
+ end
79
+
80
+ def find_iters_for_iter_node(exp, depth)
81
+ ignored = ignored_iterator? exp
82
+ result = find_iters([exp.call], depth) +
83
+ find_iters([exp.block], depth + (ignored ? 0 : 1))
84
+ result << [exp, depth] unless ignored
85
+ result
86
+ end
87
+
88
+ def ignored_iterator?(exp)
89
+ name = exp.call.method_name.to_s
90
+ @ignore_iterators.any? { |pattern| /#{pattern}/ === name }
85
91
  end
86
92
  end
87
93
  end
@@ -1,17 +1,15 @@
1
- require File.join( File.dirname( File.expand_path(__FILE__)), 'smell_detector')
2
- require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'smell_warning')
1
+ require 'reek/smells/smell_detector'
2
+ require 'reek/smell_warning'
3
3
 
4
4
  module Reek
5
5
  module Smells
6
6
 
7
+ # Checking for nil is a special kind of type check, and therefore a case of
8
+ # SimulatedPolymorphism.
7
9
  class NilCheck < SmellDetector
8
10
 
9
- SMELL_CLASS = 'NilCheck'
10
- SMELL_SUBCLASS = SMELL_CLASS
11
-
12
- def initialize(source, config = NilCheck.default_config)
13
- super(source, config)
14
- end
11
+ SMELL_CLASS = 'SimulatedPolymorphism'
12
+ SMELL_SUBCLASS = self.name.split(/::/)[-1]
15
13
 
16
14
  def examine_context(ctx)
17
15
 
@@ -23,9 +21,9 @@ module Reek
23
21
  smelly_nodes = smelly_calls + smelly_cases
24
22
 
25
23
  smelly_nodes.map do |node|
26
- SmellWarning.new(SMELL_CLASS, ctx.full_name, node.line,
24
+ SmellWarning.new(SMELL_CLASS, ctx.full_name, Array(node.line),
27
25
  "performs a nil-check.",
28
- @source, SMELL_SUBCLASS )
26
+ @source, SMELL_SUBCLASS)
29
27
  end
30
28
  end
31
29
 
@@ -66,13 +64,14 @@ module Reek
66
64
 
67
65
  class CaseNodeFinder < NodeFinder
68
66
  CASE_NIL_NODE = Sexp.new(:array, SEXP_NIL)
67
+
69
68
  def initialize(ctx)
70
69
  super(ctx, :when)
71
- end
70
+ end
72
71
 
73
72
  def smelly
74
73
  @nodes.select{ |when_node|
75
- nil_chk?(when_node)
74
+ nil_chk?(when_node)
76
75
  }
77
76
  end
78
77
 
@@ -1,5 +1,5 @@
1
- require File.join( File.dirname( File.expand_path(__FILE__)), 'smell_detector')
2
- require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'smell_warning')
1
+ require 'reek/smells/smell_detector'
2
+ require 'reek/smell_warning'
3
3
 
4
4
  module Reek
5
5
  module Smells
@@ -18,13 +18,13 @@ module Reek
18
18
  # classes to change. Tests for the type of an object may indicate that the
19
19
  # abstraction represented by that type is not completely defined (or understood).
20
20
  #
21
- # In the current implementation, Reek only checks for multiple conditionals
21
+ # +RepeatedConditional+ checks for multiple conditionals
22
22
  # testing the same value throughout a single class.
23
23
  #
24
- class SimulatedPolymorphism < SmellDetector
24
+ class RepeatedConditional < SmellDetector
25
25
 
26
- SMELL_CLASS = self.name.split(/::/)[-1]
27
- SMELL_SUBCLASS = 'RepeatedConditional'
26
+ SMELL_CLASS = 'SimulatedPolymorphism'
27
+ SMELL_SUBCLASS = self.name.split(/::/)[-1]
28
28
 
29
29
  def self.contexts # :nodoc:
30
30
  [:class]
@@ -40,10 +40,6 @@ module Reek
40
40
  super.adopt(MAX_IDENTICAL_IFS_KEY => DEFAULT_MAX_IFS)
41
41
  end
42
42
 
43
- def initialize(source, config = SimulatedPolymorphism.default_config)
44
- super(source, config)
45
- end
46
-
47
43
  #
48
44
  # Checks the given class for multiple identical conditional tests.
49
45
  #
@@ -1,6 +1,6 @@
1
1
  require 'set'
2
- require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'smell_warning')
3
- require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'core', 'smell_configuration')
2
+ require 'reek/smell_warning'
3
+ require 'reek/core/smell_configuration'
4
4
 
5
5
  module Reek
6
6
  module Smells
@@ -9,9 +9,6 @@ module Reek
9
9
  def self.default_config
10
10
  super.adopt(EXCLUDE_KEY => ['initialize'])
11
11
  end
12
- def initialize(source, config = self.class.default_config)
13
- super(source, config)
14
- end
15
12
  end
16
13
 
17
14
  #
@@ -36,7 +33,7 @@ module Reek
36
33
  def default_config
37
34
  {
38
35
  Core::SmellConfiguration::ENABLED_KEY => true,
39
- EXCLUDE_KEY => DEFAULT_EXCLUDE_SET
36
+ EXCLUDE_KEY => DEFAULT_EXCLUDE_SET.dup
40
37
  }
41
38
  end
42
39
  end
@@ -0,0 +1,60 @@
1
+ require 'reek/smells/smell_detector'
2
+ require 'reek/smell_warning'
3
+
4
+ module Reek
5
+ module Smells
6
+
7
+ #
8
+ # A Large Class is a class or module that has a large number of
9
+ # instance variables, methods or lines of code.
10
+ #
11
+ # +TooManyInstanceVariables' reports classes having more than a
12
+ # configurable number of instance variables.
13
+ #
14
+ class TooManyInstanceVariables < SmellDetector
15
+
16
+ SMELL_CLASS = 'LargeClass'
17
+ SMELL_SUBCLASS = self.name.split(/::/)[-1]
18
+ IVAR_COUNT_KEY = 'ivar_count'
19
+
20
+ # The name of the config field that sets the maximum number of instance
21
+ # variables permitted in a class.
22
+ MAX_ALLOWED_IVARS_KEY = 'max_instance_variables'
23
+
24
+ DEFAULT_MAX_IVARS = 9
25
+
26
+ def self.contexts # :nodoc:
27
+ [:class]
28
+ end
29
+
30
+ def self.default_config
31
+ super.adopt(
32
+ MAX_ALLOWED_IVARS_KEY => DEFAULT_MAX_IVARS,
33
+ EXCLUDE_KEY => []
34
+ )
35
+ end
36
+
37
+ #
38
+ # Checks +klass+ for too many instance variables.
39
+ #
40
+ # @return [Array<SmellWarning>]
41
+ #
42
+ def examine_context(ctx)
43
+ @max_allowed_ivars = value(MAX_ALLOWED_IVARS_KEY, ctx, DEFAULT_MAX_IVARS)
44
+ check_num_ivars(ctx)
45
+ end
46
+
47
+ private
48
+
49
+ def check_num_ivars(ctx) # :nodoc:
50
+ count = ctx.local_nodes(:iasgn).map {|iasgn| iasgn[1]}.uniq.length
51
+ return [] if count <= @max_allowed_ivars
52
+ smell = SmellWarning.new(SMELL_CLASS, ctx.full_name, [ctx.exp.line],
53
+ "has at least #{count} instance variables",
54
+ @source, SMELL_SUBCLASS,
55
+ {IVAR_COUNT_KEY => count})
56
+ [smell]
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,62 @@
1
+ require 'reek/smells/smell_detector'
2
+ require 'reek/smell_warning'
3
+
4
+ module Reek
5
+ module Smells
6
+
7
+ #
8
+ # A Large Class is a class or module that has a large number of
9
+ # instance variables, methods or lines of code.
10
+ #
11
+ # +TooManyMethods+ reports classes having more than a configurable number
12
+ # of methods. The method count includes public, protected and private
13
+ # methods, and excludes methods inherited from superclasses or included
14
+ # modules.
15
+ #
16
+ class TooManyMethods < SmellDetector
17
+
18
+ SMELL_CLASS = 'LargeClass'
19
+ SMELL_SUBCLASS = self.name.split(/::/)[-1]
20
+ METHOD_COUNT_KEY = 'method_count'
21
+
22
+ # The name of the config field that sets the maximum number of methods
23
+ # permitted in a class.
24
+ MAX_ALLOWED_METHODS_KEY = 'max_methods'
25
+
26
+ DEFAULT_MAX_METHODS = 25
27
+
28
+ def self.contexts # :nodoc:
29
+ [:class]
30
+ end
31
+
32
+ def self.default_config
33
+ super.adopt(
34
+ MAX_ALLOWED_METHODS_KEY => DEFAULT_MAX_METHODS,
35
+ EXCLUDE_KEY => []
36
+ )
37
+ end
38
+
39
+ #
40
+ # Checks +ctx+ for too many methods
41
+ #
42
+ # @return [Array<SmellWarning>]
43
+ #
44
+ def examine_context(ctx)
45
+ @max_allowed_methods = value(MAX_ALLOWED_METHODS_KEY, ctx, DEFAULT_MAX_METHODS)
46
+ check_num_methods(ctx)
47
+ end
48
+
49
+ private
50
+
51
+ def check_num_methods(ctx) # :nodoc:
52
+ actual = ctx.local_nodes(:defn).length
53
+ return [] if actual <= @max_allowed_methods
54
+ smell = SmellWarning.new(SMELL_CLASS, ctx.full_name, [ctx.exp.line],
55
+ "has at least #{actual} methods",
56
+ @source, SMELL_SUBCLASS,
57
+ {METHOD_COUNT_KEY => actual})
58
+ [smell]
59
+ end
60
+ end
61
+ end
62
+ end