reek 1.3.1 → 1.3.2

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