reek 1.3.8 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (142) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +11 -0
  3. data/README.md +22 -14
  4. data/Rakefile +2 -15
  5. data/assets/html_output.html.erb +103 -0
  6. data/features/command_line_interface/options.feature +3 -0
  7. data/features/command_line_interface/smell_selection.feature +19 -0
  8. data/features/rake_task/rake_task.feature +1 -1
  9. data/features/reports/reports.feature +16 -0
  10. data/features/reports/yaml.feature +26 -23
  11. data/features/samples.feature +2 -1
  12. data/features/step_definitions/reek_steps.rb +15 -15
  13. data/features/support/env.rb +7 -9
  14. data/lib/reek/cli/application.rb +2 -4
  15. data/lib/reek/cli/command.rb +12 -0
  16. data/lib/reek/cli/help_command.rb +3 -6
  17. data/lib/reek/cli/options.rb +147 -0
  18. data/lib/reek/cli/reek_command.rb +18 -14
  19. data/lib/reek/cli/report/formatter.rb +56 -0
  20. data/lib/reek/cli/report/report.rb +106 -0
  21. data/lib/reek/cli/report/strategy.rb +63 -0
  22. data/lib/reek/cli/version_command.rb +3 -6
  23. data/lib/reek/config_file_exception.rb +0 -1
  24. data/lib/reek/core/code_context.rb +1 -3
  25. data/lib/reek/core/code_parser.rb +13 -12
  26. data/lib/reek/core/method_context.rb +13 -2
  27. data/lib/reek/core/module_context.rb +0 -4
  28. data/lib/reek/core/object_refs.rb +2 -3
  29. data/lib/reek/core/singleton_method_context.rb +0 -2
  30. data/lib/reek/core/smell_configuration.rb +3 -5
  31. data/lib/reek/core/smell_repository.rb +7 -8
  32. data/lib/reek/core/sniffer.rb +4 -10
  33. data/lib/reek/core/stop_context.rb +2 -4
  34. data/lib/reek/core/warning_collector.rb +0 -1
  35. data/lib/reek/examiner.rb +19 -17
  36. data/lib/reek/rake/task.rb +7 -10
  37. data/lib/reek/smell_warning.rb +4 -8
  38. data/lib/reek/smells.rb +0 -1
  39. data/lib/reek/smells/attribute.rb +8 -11
  40. data/lib/reek/smells/boolean_parameter.rb +5 -7
  41. data/lib/reek/smells/class_variable.rb +6 -7
  42. data/lib/reek/smells/control_parameter.rb +78 -45
  43. data/lib/reek/smells/data_clump.rb +13 -16
  44. data/lib/reek/smells/duplicate_method_call.rb +13 -11
  45. data/lib/reek/smells/feature_envy.rb +6 -7
  46. data/lib/reek/smells/irresponsible_module.rb +4 -6
  47. data/lib/reek/smells/long_parameter_list.rb +5 -7
  48. data/lib/reek/smells/long_yield_list.rb +2 -4
  49. data/lib/reek/smells/nested_iterators.rb +12 -22
  50. data/lib/reek/smells/nil_check.rb +35 -46
  51. data/lib/reek/smells/prima_donna_method.rb +24 -16
  52. data/lib/reek/smells/repeated_conditional.rb +8 -10
  53. data/lib/reek/smells/smell_detector.rb +9 -7
  54. data/lib/reek/smells/too_many_instance_variables.rb +7 -9
  55. data/lib/reek/smells/too_many_methods.rb +6 -8
  56. data/lib/reek/smells/too_many_statements.rb +4 -6
  57. data/lib/reek/smells/uncommunicative_method_name.rb +5 -7
  58. data/lib/reek/smells/uncommunicative_module_name.rb +5 -7
  59. data/lib/reek/smells/uncommunicative_parameter_name.rb +7 -9
  60. data/lib/reek/smells/uncommunicative_variable_name.rb +15 -18
  61. data/lib/reek/smells/unused_parameters.rb +5 -45
  62. data/lib/reek/smells/utility_function.rb +9 -10
  63. data/lib/reek/source.rb +0 -1
  64. data/lib/reek/source/code_comment.rb +7 -8
  65. data/lib/reek/source/config_file.rb +2 -4
  66. data/lib/reek/source/core_extras.rb +1 -1
  67. data/lib/reek/source/reference_collector.rb +1 -2
  68. data/lib/reek/source/sexp_extensions.rb +93 -10
  69. data/lib/reek/source/sexp_formatter.rb +2 -3
  70. data/lib/reek/source/sexp_node.rb +19 -15
  71. data/lib/reek/source/source_code.rb +4 -14
  72. data/lib/reek/source/source_file.rb +3 -5
  73. data/lib/reek/source/source_locator.rb +5 -6
  74. data/lib/reek/source/source_repository.rb +3 -3
  75. data/lib/reek/source/tree_dresser.rb +2 -2
  76. data/lib/reek/spec.rb +1 -2
  77. data/lib/reek/spec/should_reek.rb +8 -5
  78. data/lib/reek/spec/should_reek_of.rb +6 -4
  79. data/lib/reek/spec/should_reek_only_of.rb +10 -6
  80. data/lib/reek/version.rb +1 -1
  81. data/reek.gemspec +34 -30
  82. data/spec/gem/updates_spec.rb +3 -4
  83. data/spec/gem/yard_spec.rb +1 -2
  84. data/spec/matchers/smell_of_matcher.rb +12 -14
  85. data/spec/quality/reek_source_spec.rb +42 -0
  86. data/spec/reek/cli/help_command_spec.rb +7 -5
  87. data/spec/reek/cli/report_spec.rb +89 -22
  88. data/spec/reek/cli/version_command_spec.rb +8 -6
  89. data/spec/reek/core/code_context_spec.rb +25 -26
  90. data/spec/reek/core/code_parser_spec.rb +6 -6
  91. data/spec/reek/core/method_context_spec.rb +18 -18
  92. data/spec/reek/core/module_context_spec.rb +5 -5
  93. data/spec/reek/core/object_refs_spec.rb +21 -22
  94. data/spec/reek/core/smell_configuration_spec.rb +22 -21
  95. data/spec/reek/core/stop_context_spec.rb +2 -2
  96. data/spec/reek/core/warning_collector_spec.rb +3 -3
  97. data/spec/reek/examiner_spec.rb +9 -9
  98. data/spec/reek/smell_warning_spec.rb +29 -29
  99. data/spec/reek/smells/attribute_spec.rb +6 -6
  100. data/spec/reek/smells/behaves_like_variable_detector.rb +6 -6
  101. data/spec/reek/smells/boolean_parameter_spec.rb +17 -17
  102. data/spec/reek/smells/class_variable_spec.rb +9 -9
  103. data/spec/reek/smells/control_parameter_spec.rb +161 -137
  104. data/spec/reek/smells/data_clump_spec.rb +22 -19
  105. data/spec/reek/smells/duplicate_method_call_spec.rb +71 -27
  106. data/spec/reek/smells/feature_envy_spec.rb +32 -32
  107. data/spec/reek/smells/irresponsible_module_spec.rb +21 -21
  108. data/spec/reek/smells/long_parameter_list_spec.rb +14 -14
  109. data/spec/reek/smells/long_yield_list_spec.rb +6 -6
  110. data/spec/reek/smells/nested_iterators_spec.rb +21 -21
  111. data/spec/reek/smells/nil_check_spec.rb +23 -15
  112. data/spec/reek/smells/prima_donna_method_spec.rb +5 -5
  113. data/spec/reek/smells/repeated_conditional_spec.rb +14 -14
  114. data/spec/reek/smells/smell_detector_shared.rb +9 -9
  115. data/spec/reek/smells/too_many_instance_variables_spec.rb +12 -12
  116. data/spec/reek/smells/too_many_methods_spec.rb +10 -10
  117. data/spec/reek/smells/too_many_statements_spec.rb +41 -41
  118. data/spec/reek/smells/uncommunicative_method_name_spec.rb +4 -4
  119. data/spec/reek/smells/uncommunicative_module_name_spec.rb +12 -12
  120. data/spec/reek/smells/uncommunicative_parameter_name_spec.rb +21 -21
  121. data/spec/reek/smells/uncommunicative_variable_name_spec.rb +49 -49
  122. data/spec/reek/smells/unused_parameters_spec.rb +26 -16
  123. data/spec/reek/smells/utility_function_spec.rb +20 -20
  124. data/spec/reek/source/code_comment_spec.rb +37 -37
  125. data/spec/reek/source/object_source_spec.rb +5 -5
  126. data/spec/reek/source/reference_collector_spec.rb +9 -9
  127. data/spec/reek/source/sexp_extensions_spec.rb +73 -52
  128. data/spec/reek/source/sexp_formatter_spec.rb +3 -4
  129. data/spec/reek/source/sexp_node_spec.rb +3 -3
  130. data/spec/reek/source/source_code_spec.rb +16 -15
  131. data/spec/reek/source/tree_dresser_spec.rb +2 -2
  132. data/spec/reek/spec/should_reek_of_spec.rb +11 -11
  133. data/spec/reek/spec/should_reek_only_of_spec.rb +11 -11
  134. data/spec/reek/spec/should_reek_spec.rb +11 -11
  135. data/spec/samples/one_smelly_file/dirty.rb +3 -0
  136. data/spec/spec_helper.rb +0 -6
  137. data/tasks/develop.rake +8 -16
  138. data/tasks/reek.rake +5 -13
  139. data/tasks/test.rake +5 -22
  140. metadata +56 -34
  141. data/lib/reek/cli/command_line.rb +0 -126
  142. data/lib/reek/cli/report.rb +0 -138
@@ -4,14 +4,12 @@ require 'rake'
4
4
  require 'rake/tasklib'
5
5
 
6
6
  module Reek
7
-
8
7
  #
9
8
  # Defines a task library for running reek.
10
9
  # (Classes here will be configured via the Rakefile, and therefore will
11
10
  # possess a :reek:attribute or two.)
12
11
  #
13
12
  module Rake
14
-
15
13
  # A Rake task that runs reek on a set of source files.
16
14
  #
17
15
  # Example:
@@ -33,7 +31,6 @@ module Reek
33
31
  # rake reek REEK_OPTS=-s # sorts the report by smell
34
32
  #
35
33
  class Task < ::Rake::TaskLib
36
-
37
34
  # Name of reek task.
38
35
  # Defaults to :reek.
39
36
  attr_accessor :name
@@ -85,7 +82,7 @@ module Reek
85
82
  define
86
83
  end
87
84
 
88
- private
85
+ private
89
86
 
90
87
  def define # :nodoc:
91
88
  desc 'Check for code smells' unless ::Rake.application.last_comment
@@ -97,7 +94,7 @@ module Reek
97
94
  return if source_file_list.empty?
98
95
  cmd = cmd_words.join(' ')
99
96
  puts cmd if @verbose
100
- raise('Smells found!') if !system(cmd) and fail_on_error
97
+ raise('Smells found!') if !system(cmd) && fail_on_error
101
98
  end
102
99
 
103
100
  def self.reek_script
@@ -111,16 +108,16 @@ module Reek
111
108
  def cmd_words
112
109
  [Task.ruby_exe] +
113
110
  ruby_options +
114
- [ %Q|"#{Task.reek_script}"| ] +
111
+ [%("#{Task.reek_script}")] +
115
112
  [sort_option] +
116
- config_file_list.collect { |fn| ['-c', %["#{fn}"]] }.flatten +
117
- source_file_list.collect { |fn| %["#{fn}"] }
113
+ config_file_list.map { |fn| ['-c', %("#{fn}")] }.flatten +
114
+ source_file_list.map { |fn| %("#{fn}") }
118
115
  end
119
116
 
120
117
  def config_file_list
121
118
  files = ENV['REEK_CFG'] || @config_files
122
119
  return [] unless files
123
- return FileList[files]
120
+ FileList[files]
124
121
  end
125
122
 
126
123
  def ruby_options
@@ -143,7 +140,7 @@ module Reek
143
140
  def source_file_list # :nodoc:
144
141
  files = ENV['REEK_SRC'] || @source_files
145
142
  return [] unless files
146
- return FileList[files]
143
+ FileList[files]
147
144
  end
148
145
  end
149
146
  end
@@ -1,11 +1,9 @@
1
1
  module Reek
2
-
3
2
  #
4
3
  # Reports a warning that a smell has been found.
5
4
  # This object is essentially a DTO, and therefore contains a :reek:attribute or two.
6
5
  #
7
6
  class SmellWarning
8
-
9
7
  include Comparable
10
8
 
11
9
  MESSAGE_KEY = 'message'
@@ -23,7 +21,7 @@ module Reek
23
21
  @smell = {
24
22
  CLASS_KEY => class_name,
25
23
  SUBCLASS_KEY => subclass_name,
26
- MESSAGE_KEY => message,
24
+ MESSAGE_KEY => message
27
25
  }
28
26
  @smell.merge!(parameters)
29
27
  @status = {
@@ -69,8 +67,6 @@ module Reek
69
67
  #
70
68
  attr_reader :status
71
69
 
72
- def is_active() @status[ACTIVE_KEY] end
73
-
74
70
  def hash
75
71
  sort_key.hash
76
72
  end
@@ -85,18 +81,18 @@ module Reek
85
81
 
86
82
  def contains_all?(patterns)
87
83
  rpt = sort_key.to_s
88
- return patterns.all? {|pattern| pattern === rpt}
84
+ patterns.all? { |pattern| pattern =~ rpt }
89
85
  end
90
86
 
91
87
  def matches?(klass, patterns)
92
- @smell.values.include?(klass.to_s) and contains_all?(patterns)
88
+ @smell.values.include?(klass.to_s) && contains_all?(patterns)
93
89
  end
94
90
 
95
91
  def report_on(listener)
96
92
  listener.found_smell(self)
97
93
  end
98
94
 
99
- protected
95
+ protected
100
96
 
101
97
  def sort_key
102
98
  [@location[CONTEXT_KEY], @smell[MESSAGE_KEY], @smell[CLASS_KEY]]
data/lib/reek/smells.rb CHANGED
@@ -24,7 +24,6 @@ require 'reek/smells/utility_function'
24
24
  # SMELL: Duplication -- all these should be found automagically
25
25
 
26
26
  module Reek
27
-
28
27
  #
29
28
  # This module contains the various smell detectors.
30
29
  #
@@ -4,7 +4,6 @@ require 'reek/core/smell_configuration'
4
4
 
5
5
  module Reek
6
6
  module Smells
7
-
8
7
  #
9
8
  # A class that publishes a getter or setter for an instance variable
10
9
  # invites client classes to become too intimate with its inner workings,
@@ -14,13 +13,11 @@ module Reek
14
13
  # +attr_reader+, +attr_writer+ and +attr_accessor+ -- including those
15
14
  # that are private.
16
15
  #
17
- # TODO:
18
- # * eliminate private attributes
19
- # * catch attributes declared "by hand"
16
+ # TODO: Eliminate private attributes
17
+ # TODO: Catch attributes declared "by hand"
20
18
  #
21
19
  class Attribute < SmellDetector
22
-
23
- SMELL_CLASS = self.name.split(/::/)[-1]
20
+ SMELL_CLASS = name.split(/::/)[-1]
24
21
  SMELL_SUBCLASS = SMELL_CLASS
25
22
 
26
23
  ATTRIBUTE_KEY = 'attribute'
@@ -41,21 +38,21 @@ module Reek
41
38
  def examine_context(ctx)
42
39
  attributes_in(ctx).map do |attr, line|
43
40
  smell = SmellWarning.new(SMELL_CLASS, ctx.full_name, [line],
44
- "declares the attribute #{attr}",
45
- @source, SMELL_SUBCLASS,
46
- {ATTRIBUTE_KEY => attr.to_s})
41
+ "declares the attribute #{attr}",
42
+ @source, SMELL_SUBCLASS,
43
+ ATTRIBUTE_KEY => attr.to_s)
47
44
  smell
48
45
  end
49
46
  end
50
47
 
51
- private
48
+ private
52
49
 
53
50
  def attributes_in(module_ctx)
54
51
  result = Set.new
55
52
  attr_defn_methods = [:attr, :attr_reader, :attr_writer, :attr_accessor]
56
53
  module_ctx.local_nodes(:call) do |call_node|
57
54
  if attr_defn_methods.include?(call_node.method_name)
58
- call_node.arg_names.each {|arg| result << [arg, call_node.line] }
55
+ call_node.arg_names.each { |arg| result << [arg, call_node.line] }
59
56
  end
60
57
  end
61
58
  result
@@ -3,19 +3,17 @@ require 'reek/smell_warning'
3
3
 
4
4
  module Reek
5
5
  module Smells
6
-
7
6
  #
8
7
  # A Boolean parameter effectively permits a method's caller
9
8
  # to decide which execution path to take. The
10
9
  # offending parameter is a kind of Control Couple.
11
- #
10
+ #
12
11
  # Currently Reek can only detect a Boolean parameter when it has a
13
12
  # default initializer.
14
13
  #
15
14
  class BooleanParameter < SmellDetector
16
-
17
15
  SMELL_CLASS = 'ControlCouple'
18
- SMELL_SUBCLASS = self.name.split(/::/)[-1]
16
+ SMELL_SUBCLASS = name.split(/::/)[-1]
19
17
 
20
18
  PARAMETER_KEY = 'parameter'
21
19
 
@@ -25,13 +23,13 @@ module Reek
25
23
  # @return [Array<SmellWarning>]
26
24
  #
27
25
  def examine_context(method_ctx)
28
- method_ctx.parameters.default_assignments.select do |param, value|
26
+ method_ctx.parameters.default_assignments.select do |_param, value|
29
27
  [:true, :false].include?(value[0])
30
- end.map do |param, value|
28
+ end.map do |param, _value|
31
29
  param_name = param.to_s
32
30
  SmellWarning.new(SMELL_CLASS, method_ctx.full_name, [method_ctx.exp.line],
33
31
  "has boolean parameter '#{param_name}'",
34
- @source, SMELL_SUBCLASS, {PARAMETER_KEY => param_name})
32
+ @source, SMELL_SUBCLASS, PARAMETER_KEY => param_name)
35
33
  end
36
34
  end
37
35
  end
@@ -4,7 +4,6 @@ require 'reek/smell_warning'
4
4
 
5
5
  module Reek
6
6
  module Smells
7
-
8
7
  #
9
8
  # Class variables form part of the global runtime state, and as such make
10
9
  # it easy for one part of the system to accidentally or inadvertently
@@ -14,8 +13,7 @@ module Reek
14
13
  # the context of the test includes all global state).
15
14
  #
16
15
  class ClassVariable < SmellDetector
17
-
18
- SMELL_CLASS = self.name.split(/::/)[-1]
16
+ SMELL_CLASS = name.split(/::/)[-1]
19
17
  SMELL_SUBCLASS = SMELL_CLASS
20
18
 
21
19
  VARIABLE_KEY = 'variable'
@@ -31,10 +29,11 @@ module Reek
31
29
  #
32
30
  def examine_context(ctx)
33
31
  class_variables_in(ctx.exp).map do |attr_name, lines|
32
+ attr_name = attr_name.to_s
34
33
  smell = SmellWarning.new(SMELL_CLASS, ctx.full_name, lines,
35
- "declares the class variable #{attr_name.to_s}",
36
- @source, SMELL_SUBCLASS,
37
- {VARIABLE_KEY => attr_name.to_s})
34
+ "declares the class variable #{attr_name}",
35
+ @source, SMELL_SUBCLASS,
36
+ VARIABLE_KEY => attr_name)
38
37
  smell
39
38
  end
40
39
  end
@@ -44,7 +43,7 @@ module Reek
44
43
  # in the given module.
45
44
  #
46
45
  def class_variables_in(ast)
47
- result = Hash.new {|hash,key| hash[key] = []}
46
+ result = Hash.new { |hash, key| hash[key] = [] }
48
47
  collector = proc do |cvar_node|
49
48
  result[cvar_node.name].push(cvar_node.line)
50
49
  end
@@ -3,7 +3,6 @@ require 'reek/smell_warning'
3
3
 
4
4
  module Reek
5
5
  module Smells
6
-
7
6
  #
8
7
  # Control Coupling occurs when a method or block checks the value of
9
8
  # a parameter in order to decide which execution path to take. The
@@ -42,11 +41,9 @@ module Reek
42
41
  # the source code.
43
42
  #
44
43
  class ControlParameter < SmellDetector
45
-
46
44
  SMELL_CLASS = 'ControlCouple'
47
- SMELL_SUBCLASS = self.name.split(/::/)[-1]
45
+ SMELL_SUBCLASS = name.split(/::/)[-1]
48
46
  PARAMETER_KEY = 'parameter'
49
- VALUE_POSITION = 1
50
47
 
51
48
  #
52
49
  # Checks whether the given method chooses its execution path
@@ -59,7 +56,7 @@ module Reek
59
56
  SmellWarning.new(SMELL_CLASS, ctx.full_name, control_parameter.lines,
60
57
  control_parameter.smell_message,
61
58
  @source, SMELL_SUBCLASS,
62
- {PARAMETER_KEY => control_parameter.name})
59
+ PARAMETER_KEY => control_parameter.name)
63
60
  end
64
61
  end
65
62
 
@@ -67,13 +64,13 @@ module Reek
67
64
  # Collects information about a single control parameter.
68
65
  #
69
66
  class FoundControlParameter
70
- def initialize(param)
67
+ def initialize(param, occurences)
71
68
  @param = param
72
- @occurences = []
69
+ @occurences = occurences
73
70
  end
74
71
 
75
- def record(occurences)
76
- @occurences.concat occurences
72
+ def smells?
73
+ @occurences.any?
77
74
  end
78
75
 
79
76
  def smell_message
@@ -89,6 +86,73 @@ module Reek
89
86
  end
90
87
  end
91
88
 
89
+ # Finds cases of ControlParameter in a particular node for a particular parameter
90
+ class ControlParameterFinder
91
+ def initialize(node, param)
92
+ @node = node
93
+ @param = param
94
+ end
95
+
96
+ def find_matches
97
+ return [] if legitimite_uses?
98
+ nested_finders.flat_map(&:find_matches) + uses_of_param_in_condition
99
+ end
100
+
101
+ def legitimite_uses?
102
+ return true if uses_param_in_body?
103
+ return true if uses_param_in_call_in_condition?
104
+ return true if nested_finders.any?(&:legitimite_uses?)
105
+ false
106
+ end
107
+
108
+ private
109
+
110
+ def conditional_nodes
111
+ @node.body.unnested_nodes([:if, :case, :and, :or])
112
+ end
113
+
114
+ def nested_finders
115
+ @nested_finders ||= conditional_nodes.flat_map do |node|
116
+ self.class.new(node, @param)
117
+ end
118
+ end
119
+
120
+ def uses_param_in_call_in_condition?
121
+ return false unless (condition = @node.condition)
122
+ condition.each_node(:call) do |inner|
123
+ next unless regular_call_involving_param? inner
124
+ return true
125
+ end
126
+ false
127
+ end
128
+
129
+ def uses_of_param_in_condition
130
+ return [] unless (condition = @node.condition)
131
+ condition.each_node(:lvar).select { |inner| inner.var_name == @param }
132
+ end
133
+
134
+ def regular_call_involving_param?(call_node)
135
+ call_involving_param?(call_node) && !comparison_call?(call_node)
136
+ end
137
+
138
+ def comparison_call?(call_node)
139
+ comparison_method_names.include? call_node.method_name
140
+ end
141
+
142
+ def comparison_method_names
143
+ [:==, :!=]
144
+ end
145
+
146
+ def call_involving_param?(call_node)
147
+ call_node.participants.any? { |it| it.var_name == @param }
148
+ end
149
+
150
+ def uses_param_in_body?
151
+ nodes = @node.body.each_node(:lvar, [:if, :case, :and, :or])
152
+ nodes.any? { |lvar_node| lvar_node.var_name == @param }
153
+ end
154
+ end
155
+
92
156
  #
93
157
  # Collects all control parameters in a given context.
94
158
  #
@@ -98,50 +162,19 @@ module Reek
98
162
  end
99
163
 
100
164
  def control_parameters
101
- result = Hash.new {|hash, key| hash[key] = FoundControlParameter.new(key)}
102
- potential_parameters.each do |param|
103
- matches = find_matches(param)
104
- result[param].record(matches) if matches.any?
105
- end
106
- result.values
165
+ potential_parameters.
166
+ map { |param| FoundControlParameter.new(param, find_matches(param)) }.
167
+ select(&:smells?)
107
168
  end
108
169
 
109
170
  private
110
171
 
111
- # Returns parameters that aren't used outside of a conditional statements and that
112
- # could be good candidates for being a control parameter.
113
172
  def potential_parameters
114
- @context.exp.parameter_names.select {|param| !used_outside_conditional?(param)}
115
- end
116
-
117
- # Returns wether the parameter is used outside of the conditional statement.
118
- def used_outside_conditional?(param)
119
- nodes = @context.exp.each_node(:lvar, [:if, :case, :and, :or, :args])
120
- nodes.any? {|node| node.value == param}
173
+ @context.exp.parameter_names
121
174
  end
122
175
 
123
- # Find the use of the param that match the definition of a control parameter.
124
176
  def find_matches(param)
125
- matches = []
126
- [:if, :case, :and, :or].each do |keyword|
127
- @context.local_nodes(keyword).each do |node|
128
- return [] if used_besides_in_condition?(node, param)
129
- node.each_node(:lvar, []) {|inner| matches.push(inner) if inner.value == param}
130
- end
131
- end
132
- matches
133
- end
134
-
135
- # Returns wether the parameter is used somewhere besides in the condition of the
136
- # conditional statement.
137
- def used_besides_in_condition?(node, param)
138
- times_in_conditional, times_total = 0, 0
139
- node.each_node(:lvar, [:if, :case]) {|lvar| times_total +=1 if lvar.value == param}
140
- if node.condition
141
- times_in_conditional += 1 if node.condition[VALUE_POSITION] == param
142
- times_in_conditional += node.condition.count {|inner| inner.class == Sexp && inner[VALUE_POSITION] == param}
143
- end
144
- return times_total > times_in_conditional
177
+ ControlParameterFinder.new(@context.exp, param).find_matches
145
178
  end
146
179
  end
147
180
  end
@@ -4,7 +4,6 @@ require 'reek/source'
4
4
 
5
5
  module Reek
6
6
  module Smells
7
-
8
7
  #
9
8
  # A Data Clump occurs when the same two or three items frequently
10
9
  # appear together in classes and parameter lists, or when a group
@@ -18,9 +17,8 @@ module Reek
18
17
  # the same names that are expected by three or more methods of a class.
19
18
  #
20
19
  class DataClump < SmellDetector
21
-
22
- SMELL_CLASS = self.name.split(/::/)[-1]
23
- SMELL_SUBCLASS = self.name.split(/::/)[-1]
20
+ SMELL_CLASS = name.split(/::/)[-1]
21
+ SMELL_SUBCLASS = name.split(/::/)[-1]
24
22
 
25
23
  METHODS_KEY = 'methods'
26
24
  OCCURRENCES_KEY = 'occurrences'
@@ -65,19 +63,18 @@ module Reek
65
63
  @min_clump_size = value(MIN_CLUMP_SIZE_KEY, ctx, DEFAULT_MIN_CLUMP_SIZE)
66
64
  MethodGroup.new(ctx, @min_clump_size, @max_copies).clumps.map do |clump, methods|
67
65
  SmellWarning.new(SMELL_CLASS, ctx.full_name,
68
- methods.map {|meth| meth.line},
66
+ methods.map(&:line),
69
67
  "takes parameters #{DataClump.print_clump(clump)} to #{methods.length} methods",
70
- @source, SMELL_SUBCLASS, {
71
- PARAMETERS_KEY => clump.map {|name| name.to_s},
72
- OCCURRENCES_KEY => methods.length,
73
- METHODS_KEY => methods.map {|meth| meth.name}
74
- })
68
+ @source, SMELL_SUBCLASS,
69
+ PARAMETERS_KEY => clump.map(&:to_s),
70
+ OCCURRENCES_KEY => methods.length,
71
+ METHODS_KEY => methods.map(&:name))
75
72
  end
76
73
  end
77
74
 
78
75
  # @private
79
76
  def self.print_clump(clump)
80
- "[#{clump.map {|name| name.to_s}.join(', ')}]"
77
+ "[#{clump.map(&:to_s).join(', ')}]"
81
78
  end
82
79
  end
83
80
  end
@@ -85,12 +82,12 @@ module Reek
85
82
  # Represents a group of methods
86
83
  # @private
87
84
  class MethodGroup
88
-
89
85
  def initialize(ctx, min_clump_size, max_copies)
90
86
  @min_clump_size = min_clump_size
91
87
  @max_copies = max_copies
92
- @candidate_methods = ctx.local_nodes(:defn).map {|defn_node|
93
- CandidateMethod.new(defn_node)}
88
+ @candidate_methods = ctx.local_nodes(:defn).map do |defn_node|
89
+ CandidateMethod.new(defn_node)
90
+ end
94
91
  end
95
92
 
96
93
  def candidate_clumps
@@ -102,7 +99,7 @@ module Reek
102
99
  end
103
100
 
104
101
  def common_argument_names_for(methods)
105
- methods.collect(&:arg_names).inject(:&)
102
+ methods.map(&:arg_names).inject(:&)
106
103
  end
107
104
 
108
105
  def methods_containing_clump(clump)
@@ -121,7 +118,7 @@ module Reek
121
118
  class CandidateMethod
122
119
  def initialize(defn_node)
123
120
  @defn = defn_node
124
- @params = defn_node.arg_names.clone.sort {|first, second| first.to_s <=> second.to_s}
121
+ @params = defn_node.arg_names.clone.sort { |first, second| first.to_s <=> second.to_s }
125
122
  end
126
123
 
127
124
  def arg_names