reek 1.2.7.3 → 1.2.8

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 (76) hide show
  1. data/History.txt +17 -0
  2. data/README.md +32 -48
  3. data/config/defaults.reek +7 -1
  4. data/features/api.feature +20 -0
  5. data/features/masking_smells.feature +41 -0
  6. data/features/options.feature +4 -0
  7. data/features/rake_task.feature +14 -0
  8. data/features/yaml.feature +8 -8
  9. data/lib/reek.rb +1 -1
  10. data/lib/reek/cli/command_line.rb +9 -2
  11. data/lib/reek/cli/reek_command.rb +5 -4
  12. data/lib/reek/cli/yaml_command.rb +2 -2
  13. data/lib/reek/core/code_context.rb +10 -1
  14. data/lib/reek/core/method_context.rb +2 -2
  15. data/lib/reek/core/sniffer.rb +3 -1
  16. data/lib/reek/core/stop_context.rb +4 -0
  17. data/lib/reek/examiner.rb +2 -2
  18. data/lib/reek/rake/task.rb +16 -0
  19. data/lib/reek/smell_warning.rb +3 -2
  20. data/lib/reek/smells/attribute.rb +13 -9
  21. data/lib/reek/smells/boolean_parameter.rb +14 -9
  22. data/lib/reek/smells/class_variable.rb +16 -5
  23. data/lib/reek/smells/control_couple.rb +11 -6
  24. data/lib/reek/smells/data_clump.rb +33 -30
  25. data/lib/reek/smells/duplication.rb +39 -8
  26. data/lib/reek/smells/feature_envy.rb +7 -8
  27. data/lib/reek/smells/irresponsible_module.rb +12 -3
  28. data/lib/reek/smells/large_class.rb +31 -15
  29. data/lib/reek/smells/long_method.rb +15 -5
  30. data/lib/reek/smells/long_parameter_list.rb +14 -7
  31. data/lib/reek/smells/long_yield_list.rb +12 -9
  32. data/lib/reek/smells/nested_iterators.rb +46 -11
  33. data/lib/reek/smells/simulated_polymorphism.rb +16 -8
  34. data/lib/reek/smells/smell_detector.rb +13 -13
  35. data/lib/reek/smells/uncommunicative_method_name.rb +12 -20
  36. data/lib/reek/smells/uncommunicative_module_name.rb +17 -19
  37. data/lib/reek/smells/uncommunicative_parameter_name.rb +22 -15
  38. data/lib/reek/smells/uncommunicative_variable_name.rb +24 -18
  39. data/lib/reek/smells/utility_function.rb +6 -6
  40. data/lib/reek/source/code_comment.rb +19 -1
  41. data/lib/reek/source/tree_dresser.rb +40 -22
  42. data/reek.gemspec +6 -4
  43. data/spec/matchers/smell_of_matcher.rb +58 -0
  44. data/spec/reek/core/code_context_spec.rb +4 -2
  45. data/spec/reek/core/code_parser_spec.rb +2 -1
  46. data/spec/reek/core/method_context_spec.rb +5 -5
  47. data/spec/reek/smells/attribute_spec.rb +2 -4
  48. data/spec/reek/smells/boolean_parameter_spec.rb +32 -42
  49. data/spec/reek/smells/class_variable_spec.rb +22 -6
  50. data/spec/reek/smells/control_couple_spec.rb +15 -14
  51. data/spec/reek/smells/data_clump_spec.rb +29 -111
  52. data/spec/reek/smells/duplication_spec.rb +79 -49
  53. data/spec/reek/smells/feature_envy_spec.rb +1 -2
  54. data/spec/reek/smells/irresponsible_module_spec.rb +43 -22
  55. data/spec/reek/smells/large_class_spec.rb +34 -59
  56. data/spec/reek/smells/long_method_spec.rb +15 -10
  57. data/spec/reek/smells/long_parameter_list_spec.rb +24 -24
  58. data/spec/reek/smells/long_yield_list_spec.rb +13 -14
  59. data/spec/reek/smells/nested_iterators_spec.rb +93 -76
  60. data/spec/reek/smells/smell_detector_shared.rb +4 -2
  61. data/spec/reek/smells/uncommunicative_method_name_spec.rb +10 -27
  62. data/spec/reek/smells/uncommunicative_module_name_spec.rb +22 -23
  63. data/spec/reek/smells/uncommunicative_parameter_name_spec.rb +36 -26
  64. data/spec/reek/smells/uncommunicative_variable_name_spec.rb +45 -48
  65. data/spec/reek/smells/utility_function_spec.rb +14 -13
  66. data/spec/reek/source/code_comment_spec.rb +61 -3
  67. data/spec/reek/source/tree_dresser_spec.rb +96 -1
  68. data/spec/samples/config/allow_duplication.reek +3 -0
  69. data/spec/samples/config/deeper_nested_iterators.reek +3 -0
  70. data/spec/samples/demo/demo.rb +8 -0
  71. data/spec/samples/inline_config/dirty.rb +16 -0
  72. data/spec/samples/inline_config/masked.reek +7 -0
  73. data/spec/samples/mask_some/dirty.rb +8 -0
  74. data/spec/samples/mask_some/some.reek +8 -0
  75. data/spec/spec_helper.rb +2 -0
  76. metadata +15 -5
@@ -27,7 +27,7 @@ module Reek
27
27
  # and if it is an Array, it is assumed to be a list of file paths,
28
28
  # each of which is opened and parsed for source code.
29
29
  #
30
- def initialize(source)
30
+ def initialize(source, config_files = [])
31
31
  sources = case source
32
32
  when Array
33
33
  @description = 'dir'
@@ -41,7 +41,7 @@ module Reek
41
41
  [src]
42
42
  end
43
43
  collector = Core::WarningCollector.new
44
- sources.each { |src| Core::Sniffer.new(src).report_on(collector) }
44
+ sources.each { |src| Core::Sniffer.new(src, config_files).report_on(collector) }
45
45
  @smells = collector.warnings
46
46
  end
47
47
 
@@ -7,6 +7,8 @@ module Reek
7
7
 
8
8
  #
9
9
  # Defines a task library for running reek.
10
+ # (Classes here will be configured via the Rakefile, and therefore will
11
+ # possess a :reek:attribute or two.)
10
12
  #
11
13
  module Rake
12
14
 
@@ -40,6 +42,11 @@ module Reek
40
42
  # Defaults to ['<the absolute path to reek's lib directory>']
41
43
  attr_accessor :libs
42
44
 
45
+ # Glob pattern to match config files.
46
+ # Setting the REEK_CFG environment variable overrides this.
47
+ # Defaults to 'config/**/*.reek'.
48
+ attr_accessor :config_files
49
+
43
50
  # Glob pattern to match source files.
44
51
  # Setting the REEK_SRC environment variable overrides this.
45
52
  # Defaults to 'lib/**/*.rb'.
@@ -65,6 +72,7 @@ module Reek
65
72
  def initialize(name = :reek)
66
73
  @name = name
67
74
  @libs = [File.expand_path(File.dirname(__FILE__) + '/../../../lib')]
75
+ @config_files = nil
68
76
  @source_files = nil
69
77
  @ruby_opts = []
70
78
  @reek_opts = ''
@@ -72,6 +80,7 @@ module Reek
72
80
  @sort = nil
73
81
 
74
82
  yield self if block_given?
83
+ @config_files ||= 'config/**/*.reek'
75
84
  @source_files ||= 'lib/**/*.rb'
76
85
  define
77
86
  end
@@ -104,9 +113,16 @@ module Reek
104
113
  ruby_options +
105
114
  [ %Q|"#{Task.reek_script}"| ] +
106
115
  [sort_option] +
116
+ config_file_list.collect { |fn| ['-c', %["#{fn}"]] }.flatten +
107
117
  source_file_list.collect { |fn| %["#{fn}"] }
108
118
  end
109
119
 
120
+ def config_file_list
121
+ files = ENV['REEK_CFG'] || @config_files
122
+ return [] unless files
123
+ return FileList[files]
124
+ end
125
+
110
126
  def ruby_options
111
127
  lib_path = @libs.join(File::PATH_SEPARATOR)
112
128
  @ruby_opts.clone << "-I\"#{lib_path}\""
@@ -3,6 +3,7 @@ module Reek
3
3
 
4
4
  #
5
5
  # Reports a warning that a smell has been found.
6
+ # This object is essentially a DTO, and therefore contains a :reek:attribute or two.
6
7
  #
7
8
  class SmellWarning
8
9
 
@@ -71,7 +72,7 @@ module Reek
71
72
 
72
73
  def is_active() @status[ACTIVE_KEY] end
73
74
 
74
- def hash # :nodoc:
75
+ def hash
75
76
  sort_key.hash
76
77
  end
77
78
 
@@ -85,7 +86,7 @@ module Reek
85
86
 
86
87
  def contains_all?(patterns)
87
88
  rpt = sort_key.to_s
88
- return patterns.all? {|patt| patt === rpt}
89
+ return patterns.all? {|pattern| pattern === rpt}
89
90
  end
90
91
 
91
92
  def matches?(klass, patterns)
@@ -21,6 +21,8 @@ module Reek
21
21
  class Attribute < SmellDetector
22
22
 
23
23
  SMELL_CLASS = self.name.split(/::/)[-1]
24
+ SMELL_SUBCLASS = SMELL_CLASS
25
+
24
26
  ATTRIBUTE_KEY = 'attribute'
25
27
 
26
28
  def self.contexts # :nodoc:
@@ -37,19 +39,21 @@ module Reek
37
39
 
38
40
  #
39
41
  # Checks whether the given class declares any attributes.
40
- # Remembers any smells found.
41
42
  #
42
- def examine_context(module_ctx)
43
- attributes_in(module_ctx).each do |attr, line|
44
- found(module_ctx, "declares the attribute #{attr}", '',
45
- {'attribute' => attr.to_s}, [line])
43
+ # @return [Array<SmellWarning>]
44
+ #
45
+ def examine_context(ctx)
46
+ attributes_in(ctx).map do |attr, line|
47
+ smell = SmellWarning.new(SMELL_CLASS, ctx.full_name, [line],
48
+ "declares the attribute #{attr}",
49
+ @source, SMELL_SUBCLASS,
50
+ {ATTRIBUTE_KEY => attr.to_s})
51
+ smell
46
52
  end
47
53
  end
48
54
 
49
- #
50
- # Collects the names of the class variables declared and/or used
51
- # in the given module.
52
- #
55
+ private
56
+
53
57
  def attributes_in(module_ctx)
54
58
  result = Set.new
55
59
  attr_defn_methods = [:attr, :attr_reader, :attr_writer, :attr_accessor]
@@ -14,18 +14,23 @@ module Reek
14
14
  #
15
15
  class BooleanParameter < SmellDetector
16
16
 
17
+ SMELL_CLASS = 'ControlCouple'
18
+ SMELL_SUBCLASS = self.name.split(/::/)[-1]
19
+
20
+ PARAMETER_KEY = 'parameter'
21
+
22
+ #
23
+ # Checks whether the given method has any Boolean parameters.
17
24
  #
18
- # Checks whether the given method has a Boolean parameter.
19
- # Remembers any smells found.
25
+ # @return [Array<SmellWarning>]
20
26
  #
21
27
  def examine_context(method_ctx)
22
- method_ctx.parameters.default_assignments.each do |param, value|
23
- next unless [:true, :false].include?(value[0])
24
- smell = SmellWarning.new('ControlCouple', method_ctx.full_name, [method_ctx.exp.line],
25
- "has boolean parameter '#{param.to_s}'",
26
- @source, 'BooleanParameter', {'parameter' => param.to_s})
27
- @smells_found << smell
28
- #SMELL: serious duplication
28
+ method_ctx.parameters.default_assignments.select do |param, value|
29
+ [:true, :false].include?(value[0])
30
+ end.map do |param, value|
31
+ SmellWarning.new(SMELL_CLASS, method_ctx.full_name, [method_ctx.exp.line],
32
+ "has boolean parameter '#{param.to_s}'",
33
+ @source, SMELL_SUBCLASS, {PARAMETER_KEY => param.to_s})
29
34
  end
30
35
  end
31
36
  end
@@ -15,17 +15,26 @@ module Reek
15
15
  #
16
16
  class ClassVariable < SmellDetector
17
17
 
18
+ SMELL_CLASS = self.name.split(/::/)[-1]
19
+ SMELL_SUBCLASS = SMELL_CLASS
20
+ VARIABLE_KEY = 'variable'
21
+
18
22
  def self.contexts # :nodoc:
19
23
  [:class, :module]
20
24
  end
21
25
 
22
26
  #
23
27
  # Checks whether the given class or module declares any class variables.
24
- # Remembers any smells found.
28
+ #
29
+ # @return [Array<SmellWarning>]
25
30
  #
26
31
  def examine_context(ctx)
27
- class_variables_in(ctx.exp).each do |cvar_name|
28
- found(ctx, "declares the class variable #{cvar_name}", '', {'variable' => cvar_name.to_s})
32
+ class_variables_in(ctx.exp).map do |attr_name, lines|
33
+ smell = SmellWarning.new(SMELL_CLASS, ctx.full_name, lines,
34
+ "declares the class variable #{attr_name.to_s}",
35
+ @source, SMELL_SUBCLASS,
36
+ {VARIABLE_KEY => attr_name.to_s})
37
+ smell
29
38
  end
30
39
  end
31
40
 
@@ -34,8 +43,10 @@ module Reek
34
43
  # in the given module.
35
44
  #
36
45
  def class_variables_in(ast)
37
- result = Set.new
38
- collector = proc { |cvar_node| result << cvar_node.name }
46
+ result = Hash.new {|hash,key| hash[key] = []}
47
+ collector = proc do |cvar_node|
48
+ result[cvar_node.name].push(cvar_node.line)
49
+ end
39
50
  [:cvar, :cvasgn, :cvdecl].each do |stmt_type|
40
51
  ast.each_node(stmt_type, [:class, :module], &collector)
41
52
  end
@@ -45,18 +45,23 @@ module Reek
45
45
 
46
46
  SMELL_CLASS = self.name.split(/::/)[-1]
47
47
  SMELL_SUBCLASS = 'ControlParameter'
48
+ PARAMETER_KEY = 'parameter'
48
49
 
49
50
  #
50
51
  # Checks whether the given method chooses its execution path
51
52
  # by testing the value of one of its parameters.
52
- # Remembers any smells found.
53
53
  #
54
- def examine_context(method_ctx)
55
- control_parameters(method_ctx).each do |cond, occurs|
56
- param = cond.format
54
+ # @return [Array<SmellWarning>]
55
+ #
56
+ def examine_context(ctx)
57
+ control_parameters(ctx).map do |cond, occurs|
58
+ param = cond.format_ruby
57
59
  lines = occurs.map {|exp| exp.line}
58
- found(method_ctx, "is controlled by argument #{param}",
59
- SMELL_SUBCLASS, {'parameter' => param}, lines)
60
+ smell = SmellWarning.new(SMELL_CLASS, ctx.full_name, lines,
61
+ "is controlled by argument #{param}",
62
+ @source, SMELL_SUBCLASS,
63
+ {PARAMETER_KEY => param})
64
+ smell
60
65
  end
61
66
  end
62
67
 
@@ -34,23 +34,32 @@ module Reek
34
34
  OCCURRENCES_KEY = 'occurrences'
35
35
  PARAMETERS_KEY = 'parameters'
36
36
 
37
- def self.contexts # :nodoc:
37
+ # @private
38
+ def self.contexts
38
39
  [:class, :module]
39
40
  end
40
41
 
42
+ #
41
43
  # The name of the config field that sets the maximum allowed
42
- # copies of any clump.
44
+ # copies of any clump. No group of common parameters will be
45
+ # reported as a DataClump unless there are more than this many
46
+ # methods containing those parameters.
47
+ #
43
48
  MAX_COPIES_KEY = 'max_copies'
44
-
45
49
  DEFAULT_MAX_COPIES = 2
46
50
 
51
+ #
52
+ # The name of the config field that sets the minimum clump
53
+ # size. No group of common parameters will be reported as
54
+ # a DataClump unless it contains at least this many parameters.
55
+ #
47
56
  MIN_CLUMP_SIZE_KEY = 'min_clump_size'
48
57
  DEFAULT_MIN_CLUMP_SIZE = 2
49
58
 
50
59
  def self.default_config
51
60
  super.adopt(
52
- MAX_COPIES_KEY => DEFAULT_MAX_COPIES,
53
- MIN_CLUMP_SIZE_KEY => DEFAULT_MIN_CLUMP_SIZE
61
+ MAX_COPIES_KEY => DEFAULT_MAX_COPIES,
62
+ MIN_CLUMP_SIZE_KEY => DEFAULT_MIN_CLUMP_SIZE
54
63
  )
55
64
  end
56
65
 
@@ -60,26 +69,25 @@ module Reek
60
69
 
61
70
  #
62
71
  # Checks the given class or module for multiple identical parameter sets.
63
- # Remembers any smells found.
72
+ #
73
+ # @return [Array<SmellWarning>]
64
74
  #
65
75
  def examine_context(ctx)
66
- max_copies = value(MAX_COPIES_KEY, ctx, DEFAULT_MAX_COPIES)
67
- min_clump_size = value(MIN_CLUMP_SIZE_KEY, ctx, DEFAULT_MIN_CLUMP_SIZE)
68
- MethodGroup.new(ctx, min_clump_size, max_copies).clumps.each do |clump, methods|
69
- smell = SmellWarning.new('DataClump', ctx.full_name,
70
- methods.map {|meth| meth.line},
71
- "takes parameters #{DataClump.print_clump(clump)} to #{methods.length} methods",
72
- @source, 'DataClump', {
73
- PARAMETERS_KEY => clump.map {|name| name.to_s},
74
- OCCURRENCES_KEY => methods.length,
75
- METHODS_KEY => methods.map {|meth| meth.name}
76
- })
77
- @smells_found << smell
78
- #SMELL: serious duplication
79
- # SMELL: name.to_s is becoming a nuisance
76
+ @max_copies = value(MAX_COPIES_KEY, ctx, DEFAULT_MAX_COPIES)
77
+ @min_clump_size = value(MIN_CLUMP_SIZE_KEY, ctx, DEFAULT_MIN_CLUMP_SIZE)
78
+ MethodGroup.new(ctx, @min_clump_size, @max_copies).clumps.map do |clump, methods|
79
+ SmellWarning.new('DataClump', ctx.full_name,
80
+ methods.map {|meth| meth.line},
81
+ "takes parameters #{DataClump.print_clump(clump)} to #{methods.length} methods",
82
+ @source, 'DataClump', {
83
+ PARAMETERS_KEY => clump.map {|name| name.to_s},
84
+ OCCURRENCES_KEY => methods.length,
85
+ METHODS_KEY => methods.map {|meth| meth.name}
86
+ })
80
87
  end
81
88
  end
82
89
 
90
+ # @private
83
91
  def self.print_clump(clump)
84
92
  "[#{clump.map {|name| name.to_s}.join(', ')}]"
85
93
  end
@@ -108,14 +116,12 @@ module Reek
108
116
  methods.each do |other_method|
109
117
  clump = [method.arg_names, other_method.arg_names].intersection
110
118
  if clump.length >= @min_clump_size
111
- others = methods.select do |other| # BUG: early ones have already been eliminated
112
- clump - other.arg_names == []
113
- end
119
+ others = methods.select { |other| clump - other.arg_names == [] }
114
120
  results[clump] += [method] + others
115
121
  end
116
122
  end
117
123
  end
118
-
124
+
119
125
  def collect_clumps_in(methods, results)
120
126
  return if methods.length <= @max_copies
121
127
  tail = methods[1..-1]
@@ -126,9 +132,7 @@ module Reek
126
132
  def clumps
127
133
  results = Hash.new([])
128
134
  collect_clumps_in(@candidate_methods, results)
129
- results.each_key do |key|
130
- results[key].uniq!
131
- end
135
+ results.each_key { |key| results[key].uniq! }
132
136
  results
133
137
  end
134
138
 
@@ -148,13 +152,12 @@ module Reek
148
152
  end
149
153
  end
150
154
 
151
- #
152
155
  # A method definition and a copy of its parameters
153
- #
156
+ # @private
154
157
  class CandidateMethod
155
158
  def initialize(defn_node)
156
159
  @defn = defn_node
157
- @params = defn_node.arg_names.clone.sort {|a,b| a.to_s <=> b.to_s}
160
+ @params = defn_node.arg_names.clone.sort {|first, second| first.to_s <=> second.to_s}
158
161
  end
159
162
 
160
163
  def arg_names
@@ -19,32 +19,59 @@ module Reek
19
19
  #
20
20
  class Duplication < SmellDetector
21
21
 
22
+ SMELL_CLASS = self.name.split(/::/)[-1]
23
+ SMELL_SUBCLASS = 'DuplicateMethodCall'
24
+ CALL_KEY = 'call'
25
+ OCCURRENCES_KEY = 'occurrences'
26
+
22
27
  # The name of the config field that sets the maximum number of
23
28
  # identical calls to be permitted within any single method.
24
29
  MAX_ALLOWED_CALLS_KEY = 'max_calls'
25
30
 
26
31
  DEFAULT_MAX_CALLS = 1
27
32
 
33
+ # The name of the config field that sets the names of any
34
+ # methods for which identical calls should be to be permitted
35
+ # within any single method.
36
+ ALLOW_CALLS_KEY = 'allow_calls'
37
+
38
+ DEFAULT_ALLOW_CALLS = []
39
+
28
40
  def self.default_config
29
- super.adopt(MAX_ALLOWED_CALLS_KEY => DEFAULT_MAX_CALLS)
41
+ super.adopt(
42
+ MAX_ALLOWED_CALLS_KEY => DEFAULT_MAX_CALLS,
43
+ ALLOW_CALLS_KEY => DEFAULT_ALLOW_CALLS
44
+ )
30
45
  end
31
46
 
32
47
  def initialize(source, config = Duplication.default_config)
33
48
  super(source, config)
34
49
  end
35
50
 
36
- def examine_context(method_ctx)
37
- calls(method_ctx).each do |call_exp, copies|
51
+ #
52
+ # Looks for duplicate calls within the body of the method +ctx+.
53
+ #
54
+ # @return [Array<SmellWarning>]
55
+ #
56
+ def examine_context(ctx)
57
+ @max_allowed_calls = value(MAX_ALLOWED_CALLS_KEY, ctx, DEFAULT_MAX_CALLS)
58
+ @allow_calls = value(ALLOW_CALLS_KEY, ctx, DEFAULT_ALLOW_CALLS)
59
+ calls(ctx).select do |call_exp, copies|
60
+ copies.length > @max_allowed_calls and not allow_calls?(call_exp.format_ruby)
61
+ end.map do |call_exp, copies|
38
62
  occurs = copies.length
39
- next if occurs <= value(MAX_ALLOWED_CALLS_KEY, method_ctx, DEFAULT_MAX_CALLS)
40
- call = call_exp.format
63
+ call = call_exp.format_ruby
41
64
  multiple = occurs == 2 ? 'twice' : "#{occurs} times"
42
- found(method_ctx, "calls #{call} #{multiple}",
43
- 'DuplicateMethodCall', {'call' => call, 'occurrences' => occurs},
44
- copies.map {|exp| exp.line})
65
+ smell = SmellWarning.new(SMELL_CLASS, ctx.full_name, copies.map {|exp| exp.line},
66
+ "calls #{call} #{multiple}",
67
+ @source, SMELL_SUBCLASS,
68
+ {CALL_KEY => call, OCCURRENCES_KEY => occurs})
69
+ smell
45
70
  end
46
71
  end
47
72
 
73
+ private
74
+
48
75
  def calls(method_ctx)
49
76
  result = Hash.new {|hash,key| hash[key] = []}
50
77
  method_ctx.local_nodes(:call) do |call_node|
@@ -56,6 +83,10 @@ module Reek
56
83
  end
57
84
  result
58
85
  end
86
+
87
+ def allow_calls?(method)
88
+ @allow_calls.any? { |allow| /#{allow}/ === method }
89
+ end
59
90
  end
60
91
  end
61
92
  end
@@ -43,16 +43,15 @@ module Reek
43
43
  #
44
44
  # Checks whether the given +context+ includes any code fragment that
45
45
  # might "belong" on another class.
46
- # Remembers any smells found.
46
+ #
47
+ # @return [Array<SmellWarning>]
47
48
  #
48
49
  def examine_context(method_ctx)
49
- method_ctx.envious_receivers.each do |ref, occurs|
50
- target = ref.format
51
- smell = SmellWarning.new(SMELL_CLASS, method_ctx.full_name, [method_ctx.exp.line],
52
- "refers to #{target} more than self",
53
- @source, SMELL_SUBCLASS, {RECEIVER_KEY => target, REFERENCES_KEY => occurs})
54
- @smells_found << smell
55
- #SMELL: serious duplication
50
+ method_ctx.envious_receivers.map do |ref, occurs|
51
+ target = ref.format_ruby
52
+ SmellWarning.new(SMELL_CLASS, method_ctx.full_name, [method_ctx.exp.line],
53
+ "refers to #{target} more than self",
54
+ @source, SMELL_SUBCLASS, {RECEIVER_KEY => target, REFERENCES_KEY => occurs})
56
55
  end
57
56
  end
58
57
  end