reek 2.1.0 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (179) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +2 -21
  3. data/.travis.yml +1 -0
  4. data/.yardopts +3 -6
  5. data/CHANGELOG +6 -0
  6. data/CONTRIBUTING.md +8 -3
  7. data/README.md +94 -42
  8. data/config/defaults.reek +0 -1
  9. data/docs/API.md +50 -0
  10. data/docs/Attribute.md +43 -0
  11. data/docs/Basic-Smell-Options.md +44 -0
  12. data/docs/Boolean-Parameter.md +52 -0
  13. data/docs/Class-Variable.md +40 -0
  14. data/docs/Code-Smells.md +34 -0
  15. data/docs/Command-Line-Options.md +84 -0
  16. data/docs/Configuration-Files.md +38 -0
  17. data/docs/Control-Couple.md +22 -0
  18. data/docs/Control-Parameter.md +29 -0
  19. data/docs/Data-Clump.md +44 -0
  20. data/docs/Duplicate-Method-Call.md +49 -0
  21. data/docs/Feature-Envy.md +29 -0
  22. data/docs/How-reek-works-internally.md +44 -0
  23. data/docs/Irresponsible-Module.md +39 -0
  24. data/docs/Large-Class.md +20 -0
  25. data/docs/Long-Parameter-List.md +38 -0
  26. data/docs/Long-Yield-List.md +36 -0
  27. data/docs/Module-Initialize.md +62 -0
  28. data/docs/Nested-Iterators.md +38 -0
  29. data/docs/Nil-Check.md +39 -0
  30. data/docs/Prima-Donna-Method.md +53 -0
  31. data/docs/RSpec-matchers.md +133 -0
  32. data/docs/Rake-Task.md +58 -0
  33. data/docs/Reek-Driven-Development.md +45 -0
  34. data/docs/Repeated-Conditional.md +44 -0
  35. data/docs/Simulated-Polymorphism.md +16 -0
  36. data/docs/Smell-Suppression.md +32 -0
  37. data/docs/Too-Many-Instance-Variables.md +43 -0
  38. data/docs/Too-Many-Methods.md +55 -0
  39. data/docs/Too-Many-Statements.md +50 -0
  40. data/docs/Uncommunicative-Method-Name.md +24 -0
  41. data/docs/Uncommunicative-Module-Name.md +23 -0
  42. data/docs/Uncommunicative-Name.md +16 -0
  43. data/docs/Uncommunicative-Parameter-Name.md +24 -0
  44. data/docs/Uncommunicative-Variable-Name.md +24 -0
  45. data/docs/Unused-Parameters.md +27 -0
  46. data/docs/Utility-Function.md +46 -0
  47. data/docs/Versioning-Policy.md +7 -0
  48. data/docs/YAML-Reports.md +111 -0
  49. data/docs/yard_plugin.rb +14 -0
  50. data/features/command_line_interface/options.feature +1 -0
  51. data/features/programmatic_access.feature +1 -1
  52. data/features/samples.feature +3 -3
  53. data/lib/reek.rb +2 -2
  54. data/lib/reek/cli/input.rb +2 -2
  55. data/lib/reek/cli/option_interpreter.rb +2 -0
  56. data/lib/reek/cli/options.rb +10 -4
  57. data/lib/reek/cli/reek_command.rb +2 -2
  58. data/lib/reek/cli/report/report.rb +60 -0
  59. data/lib/reek/cli/silencer.rb +13 -0
  60. data/lib/reek/{source → core}/ast_node.rb +1 -1
  61. data/lib/reek/{source → core}/ast_node_class_map.rb +10 -11
  62. data/lib/reek/{source → core}/code_comment.rb +1 -1
  63. data/lib/reek/core/code_context.rb +1 -1
  64. data/lib/reek/core/examiner.rb +85 -0
  65. data/lib/reek/core/method_context.rb +1 -1
  66. data/lib/reek/core/module_context.rb +2 -2
  67. data/lib/reek/core/reference_collector.rb +31 -0
  68. data/lib/reek/core/singleton_method_context.rb +0 -4
  69. data/lib/reek/core/smell_repository.rb +4 -2
  70. data/lib/reek/{source → core}/tree_dresser.rb +1 -1
  71. data/lib/reek/{source → sexp}/sexp_extensions.rb +5 -5
  72. data/lib/reek/sexp/sexp_formatter.rb +29 -0
  73. data/lib/reek/sexp/sexp_node.rb +91 -0
  74. data/lib/reek/smells.rb +4 -2
  75. data/lib/reek/smells/attribute.rb +35 -7
  76. data/lib/reek/smells/boolean_parameter.rb +1 -1
  77. data/lib/reek/smells/class_variable.rb +1 -1
  78. data/lib/reek/smells/control_parameter.rb +1 -1
  79. data/lib/reek/smells/data_clump.rb +1 -1
  80. data/lib/reek/smells/duplicate_method_call.rb +12 -4
  81. data/lib/reek/smells/feature_envy.rb +1 -1
  82. data/lib/reek/smells/irresponsible_module.rb +3 -3
  83. data/lib/reek/smells/long_parameter_list.rb +1 -1
  84. data/lib/reek/smells/long_yield_list.rb +1 -1
  85. data/lib/reek/smells/module_initialize.rb +1 -1
  86. data/lib/reek/smells/nested_iterators.rb +1 -1
  87. data/lib/reek/smells/nil_check.rb +3 -2
  88. data/lib/reek/smells/prima_donna_method.rb +18 -11
  89. data/lib/reek/smells/repeated_conditional.rb +3 -3
  90. data/lib/reek/smells/smell_detector.rb +5 -1
  91. data/lib/reek/smells/smell_warning.rb +99 -0
  92. data/lib/reek/smells/too_many_instance_variables.rb +1 -1
  93. data/lib/reek/smells/too_many_methods.rb +1 -1
  94. data/lib/reek/smells/too_many_statements.rb +1 -1
  95. data/lib/reek/smells/uncommunicative_method_name.rb +1 -1
  96. data/lib/reek/smells/uncommunicative_module_name.rb +1 -1
  97. data/lib/reek/smells/uncommunicative_parameter_name.rb +1 -1
  98. data/lib/reek/smells/uncommunicative_variable_name.rb +1 -1
  99. data/lib/reek/smells/unused_parameters.rb +1 -1
  100. data/lib/reek/smells/utility_function.rb +3 -16
  101. data/lib/reek/source/source_code.rb +31 -13
  102. data/lib/reek/source/source_locator.rb +16 -17
  103. data/lib/reek/source/source_repository.rb +10 -11
  104. data/lib/reek/spec/should_reek.rb +2 -2
  105. data/lib/reek/spec/should_reek_of.rb +2 -2
  106. data/lib/reek/spec/should_reek_only_of.rb +2 -2
  107. data/lib/reek/version.rb +1 -1
  108. data/reek.gemspec +3 -4
  109. data/spec/factories/factories.rb +1 -1
  110. data/spec/gem/yard_spec.rb +1 -1
  111. data/spec/quality/reek_source_spec.rb +2 -2
  112. data/spec/reek/cli/html_report_spec.rb +3 -3
  113. data/spec/reek/cli/json_report_spec.rb +3 -3
  114. data/spec/reek/cli/{option_interperter_spec.rb → option_interpreter_spec.rb} +1 -1
  115. data/spec/reek/cli/options_spec.rb +19 -0
  116. data/spec/reek/cli/text_report_spec.rb +7 -7
  117. data/spec/reek/cli/xml_report_spec.rb +34 -0
  118. data/spec/reek/cli/yaml_report_spec.rb +3 -3
  119. data/spec/reek/configuration/app_configuration_spec.rb +1 -1
  120. data/spec/reek/configuration/configuration_file_finder_spec.rb +22 -1
  121. data/spec/reek/{source → core}/code_comment_spec.rb +14 -14
  122. data/spec/reek/core/code_context_spec.rb +1 -1
  123. data/spec/reek/{examiner_spec.rb → core/examiner_spec.rb} +12 -12
  124. data/spec/reek/core/method_context_spec.rb +27 -22
  125. data/spec/reek/core/module_context_spec.rb +2 -2
  126. data/spec/reek/core/object_refs_spec.rb +1 -1
  127. data/spec/reek/{source → core}/object_source_spec.rb +1 -1
  128. data/spec/reek/{source → core}/reference_collector_spec.rb +25 -16
  129. data/spec/reek/core/singleton_method_context_spec.rb +12 -2
  130. data/spec/reek/core/smell_configuration_spec.rb +1 -1
  131. data/spec/reek/core/smell_repository_spec.rb +12 -1
  132. data/spec/reek/core/stop_context_spec.rb +1 -1
  133. data/spec/reek/core/tree_dresser_spec.rb +16 -0
  134. data/spec/reek/core/tree_walker_spec.rb +3 -3
  135. data/spec/reek/core/warning_collector_spec.rb +6 -6
  136. data/spec/reek/{source → sexp}/sexp_extensions_spec.rb +8 -8
  137. data/spec/reek/{source → sexp}/sexp_formatter_spec.rb +11 -5
  138. data/spec/reek/{source → sexp}/sexp_node_spec.rb +3 -3
  139. data/spec/reek/smells/attribute_spec.rb +89 -85
  140. data/spec/reek/smells/behaves_like_variable_detector.rb +1 -1
  141. data/spec/reek/smells/boolean_parameter_spec.rb +1 -1
  142. data/spec/reek/smells/class_variable_spec.rb +1 -1
  143. data/spec/reek/smells/control_parameter_spec.rb +1 -1
  144. data/spec/reek/smells/data_clump_spec.rb +2 -2
  145. data/spec/reek/smells/duplicate_method_call_spec.rb +1 -1
  146. data/spec/reek/smells/feature_envy_spec.rb +2 -2
  147. data/spec/reek/smells/irresponsible_module_spec.rb +1 -1
  148. data/spec/reek/smells/long_parameter_list_spec.rb +2 -2
  149. data/spec/reek/smells/long_yield_list_spec.rb +1 -1
  150. data/spec/reek/smells/module_initialize_spec.rb +1 -1
  151. data/spec/reek/smells/nested_iterators_spec.rb +2 -2
  152. data/spec/reek/smells/nil_check_spec.rb +1 -1
  153. data/spec/reek/smells/prima_donna_method_spec.rb +1 -1
  154. data/spec/reek/smells/repeated_conditional_spec.rb +1 -1
  155. data/spec/reek/smells/smell_detector_shared.rb +2 -2
  156. data/spec/reek/{smell_warning_spec.rb → smells/smell_warning_spec.rb} +7 -7
  157. data/spec/reek/smells/too_many_instance_variables_spec.rb +1 -1
  158. data/spec/reek/smells/too_many_methods_spec.rb +1 -1
  159. data/spec/reek/smells/too_many_statements_spec.rb +4 -4
  160. data/spec/reek/smells/uncommunicative_method_name_spec.rb +1 -1
  161. data/spec/reek/smells/uncommunicative_module_name_spec.rb +1 -1
  162. data/spec/reek/smells/uncommunicative_parameter_name_spec.rb +1 -1
  163. data/spec/reek/smells/uncommunicative_variable_name_spec.rb +1 -1
  164. data/spec/reek/smells/unused_parameters_spec.rb +1 -1
  165. data/spec/reek/smells/utility_function_spec.rb +1 -1
  166. data/spec/reek/source/source_code_spec.rb +1 -1
  167. data/spec/reek/spec/should_reek_of_spec.rb +1 -1
  168. data/spec/reek/spec/should_reek_only_of_spec.rb +1 -1
  169. data/spec/reek/spec/should_reek_spec.rb +1 -1
  170. data/spec/samples/checkstyle.xml +2 -0
  171. data/spec/spec_helper.rb +15 -3
  172. metadata +68 -38
  173. data/.ruby-gemset +0 -1
  174. data/lib/reek/examiner.rb +0 -79
  175. data/lib/reek/smell_warning.rb +0 -87
  176. data/lib/reek/source/reference_collector.rb +0 -27
  177. data/lib/reek/source/sexp_formatter.rb +0 -22
  178. data/lib/reek/source/sexp_node.rb +0 -79
  179. data/spec/reek/source/tree_dresser_spec.rb +0 -16
data/lib/reek/smells.rb CHANGED
@@ -1,6 +1,8 @@
1
- require 'require_all'
1
+ require 'pathname'
2
2
 
3
- require_rel 'smells'
3
+ (Pathname.new(__FILE__).dirname + 'smells').children.each do |path|
4
+ require_relative "smells/#{path.basename('.rb')}"
5
+ end
4
6
 
5
7
  module Reek
6
8
  #
@@ -1,5 +1,4 @@
1
1
  require_relative 'smell_detector'
2
- require_relative '../smell_warning'
3
2
  require_relative '../core/smell_configuration'
4
3
 
5
4
  module Reek
@@ -13,10 +12,20 @@ module Reek
13
12
  # +attr_reader+, +attr_writer+ and +attr_accessor+ -- including those
14
13
  # that are private.
15
14
  #
16
- # TODO: Eliminate private attributes
17
15
  # TODO: Catch attributes declared "by hand"
16
+ # See docs/Attribute for details.
18
17
  #
19
18
  class Attribute < SmellDetector
19
+ ATTR_DEFN_METHODS = [:attr, :attr_reader, :attr_writer, :attr_accessor]
20
+ VISIBILITY_MODIFIERS = [:private, :public, :protected]
21
+
22
+ def initialize(*args)
23
+ @visiblity_tracker = {}
24
+ @visiblity_mode = :public
25
+ @result = Set.new
26
+ super
27
+ end
28
+
20
29
  def self.contexts # :nodoc:
21
30
  [:class, :module]
22
31
  end
@@ -43,14 +52,33 @@ module Reek
43
52
  private
44
53
 
45
54
  def attributes_in(module_ctx)
46
- result = Set.new
47
- attr_defn_methods = [:attr, :attr_reader, :attr_writer, :attr_accessor]
48
55
  module_ctx.local_nodes(:send) do |call_node|
49
- if attr_defn_methods.include?(call_node.method_name)
50
- call_node.arg_names.each { |arg| result << [arg, call_node.line] }
56
+ if visibility_modifier?(call_node)
57
+ track_visibility(call_node)
58
+ elsif ATTR_DEFN_METHODS.include?(call_node.method_name)
59
+ call_node.arg_names.each do |arg|
60
+ @visiblity_tracker[arg] = @visiblity_mode
61
+ @result << [arg, call_node.line]
62
+ end
51
63
  end
52
64
  end
53
- result
65
+ @result.select { |args| recorded_public_methods.include?(args[0]) }
66
+ end
67
+
68
+ def visibility_modifier?(call_node)
69
+ VISIBILITY_MODIFIERS.include?(call_node.method_name)
70
+ end
71
+
72
+ def track_visibility(call_node)
73
+ if call_node.arg_names.any?
74
+ call_node.arg_names.each { |arg| @visiblity_tracker[arg] = call_node.method_name }
75
+ else
76
+ @visiblity_mode = call_node.method_name
77
+ end
78
+ end
79
+
80
+ def recorded_public_methods
81
+ @visiblity_tracker.select { |_, visbility| visbility == :public }
54
82
  end
55
83
  end
56
84
  end
@@ -1,5 +1,4 @@
1
1
  require_relative 'smell_detector'
2
- require_relative '../smell_warning'
3
2
 
4
3
  module Reek
5
4
  module Smells
@@ -11,6 +10,7 @@ module Reek
11
10
  # Currently Reek can only detect a Boolean parameter when it has a
12
11
  # default initializer.
13
12
  #
13
+ # See docs/Boolean-Parameter for details.
14
14
  class BooleanParameter < SmellDetector
15
15
  def self.smell_category
16
16
  'ControlCouple'
@@ -1,6 +1,5 @@
1
1
  require 'set'
2
2
  require_relative 'smell_detector'
3
- require_relative '../smell_warning'
4
3
 
5
4
  module Reek
6
5
  module Smells
@@ -12,6 +11,7 @@ module Reek
12
11
  # In particular, class variables can make it hard to set up tests (because
13
12
  # the context of the test includes all global state).
14
13
  #
14
+ # See docs/Class-Variable for details.
15
15
  class ClassVariable < SmellDetector
16
16
  def self.contexts # :nodoc:
17
17
  [:class, :module]
@@ -1,5 +1,4 @@
1
1
  require_relative 'smell_detector'
2
- require_relative '../smell_warning'
3
2
 
4
3
  module Reek
5
4
  module Smells
@@ -40,6 +39,7 @@ module Reek
40
39
  # that remains next to where the caller invokes it in
41
40
  # the source code.
42
41
  #
42
+ # See docs/Control-Parameter for details.
43
43
  class ControlParameter < SmellDetector
44
44
  def self.smell_category
45
45
  'ControlCouple'
@@ -1,5 +1,4 @@
1
1
  require_relative 'smell_detector'
2
- require_relative '../smell_warning'
3
2
 
4
3
  module Reek
5
4
  module Smells
@@ -15,6 +14,7 @@ module Reek
15
14
  # Currently Reek looks for a group of two or more parameters with
16
15
  # the same names that are expected by three or more methods of a class.
17
16
  #
17
+ # See docs/Data-Clump for details.
18
18
  class DataClump < SmellDetector
19
19
  #
20
20
  # The name of the config field that sets the maximum allowed
@@ -1,5 +1,4 @@
1
1
  require_relative 'smell_detector'
2
- require_relative '../smell_warning'
3
2
 
4
3
  module Reek
5
4
  module Smells
@@ -16,6 +15,7 @@ module Reek
16
15
  # @other.thing + @other.thing
17
16
  # end
18
17
  #
18
+ # See docs/Duplicate-Method-Call for details.
19
19
  class DuplicateMethodCall < SmellDetector
20
20
  # The name of the config field that sets the maximum number of
21
21
  # identical calls to be permitted within any single method.
@@ -70,7 +70,7 @@ module Reek
70
70
  end
71
71
 
72
72
  def call
73
- @call ||= @call_node.format_ruby
73
+ @call ||= @call_node.format_to_ruby
74
74
  end
75
75
 
76
76
  def occurs
@@ -106,8 +106,8 @@ module Reek
106
106
 
107
107
  def collect_calls(result)
108
108
  context.each_node(:send, [:mlhs]) do |call_node|
109
- next if call_node.method_name == :new
110
- next if !call_node.receiver && call_node.args.empty?
109
+ next if initializer_call? call_node
110
+ next if simple_method_call? call_node
111
111
  result[call_node].record(call_node)
112
112
  end
113
113
  context.local_nodes(:block) do |call_node|
@@ -119,6 +119,14 @@ module Reek
119
119
  found_call.occurs > @max_allowed_calls && !allow_calls?(found_call.call)
120
120
  end
121
121
 
122
+ def simple_method_call?(call_node)
123
+ !call_node.receiver && call_node.args.empty?
124
+ end
125
+
126
+ def initializer_call?(call_node)
127
+ call_node.method_name == :new
128
+ end
129
+
122
130
  def allow_calls?(method)
123
131
  @allow_calls.any? { |allow| /#{allow}/ =~ method }
124
132
  end
@@ -1,5 +1,4 @@
1
1
  require_relative 'smell_detector'
2
- require_relative '../smell_warning'
3
2
 
4
3
  module Reek
5
4
  module Smells
@@ -33,6 +32,7 @@ module Reek
33
32
  # If the method doesn't reference self at all, +UtilityFunction+ is
34
33
  # reported instead.
35
34
  #
35
+ # See docs/Feature-Envy for details.
36
36
  class FeatureEnvy < SmellDetector
37
37
  def self.smell_category
38
38
  'LowCohesion'
@@ -1,6 +1,5 @@
1
1
  require_relative 'smell_detector'
2
- require_relative '../smell_warning'
3
- require_relative '../source/code_comment'
2
+ require_relative '../core/code_comment'
4
3
 
5
4
  module Reek
6
5
  module Smells
@@ -8,6 +7,7 @@ module Reek
8
7
  # It is considered good practice to annotate every class and module
9
8
  # with a brief comment outlining its responsibilities.
10
9
  #
10
+ # See docs/Irresponsible-Module for details.
11
11
  class IrresponsibleModule < SmellDetector
12
12
  def self.contexts # :nodoc:
13
13
  [:class]
@@ -23,7 +23,7 @@ module Reek
23
23
  # @return [Array<SmellWarning>]
24
24
  #
25
25
  def examine_context(ctx)
26
- comment = Source::CodeComment.new(ctx.exp.comments)
26
+ comment = Core::CodeComment.new(ctx.exp.comments)
27
27
  return [] if self.class.descriptive[ctx.full_name] ||= comment.descriptive?
28
28
  [SmellWarning.new(self,
29
29
  context: ctx.full_name,
@@ -1,5 +1,4 @@
1
1
  require_relative 'smell_detector'
2
- require_relative '../smell_warning'
3
2
  require_relative '../core/smell_configuration'
4
3
 
5
4
  module Reek
@@ -12,6 +11,7 @@ module Reek
12
11
  # Currently +LongParameterList+ reports any method or block with too
13
12
  # many parameters.
14
13
  #
14
+ # See docs/Long-Parameter-List for details.
15
15
  class LongParameterList < SmellDetector
16
16
  # The name of the config field that sets the maximum number of
17
17
  # parameters permitted in any method or block.
@@ -1,5 +1,4 @@
1
1
  require_relative 'smell_detector'
2
- require_relative '../smell_warning'
3
2
 
4
3
  module Reek
5
4
  module Smells
@@ -7,6 +6,7 @@ module Reek
7
6
  # A variant on LongParameterList that checks the number of items
8
7
  # passed to a block by a +yield+ call.
9
8
  #
9
+ # See docs/Long-Yield-List for details.
10
10
  class LongYieldList < SmellDetector
11
11
  # The name of the config field that sets the maximum number of
12
12
  # parameters permitted in any method or block.
@@ -1,5 +1,4 @@
1
1
  require_relative 'smell_detector'
2
- require_relative '../smell_warning'
3
2
 
4
3
  module Reek
5
4
  module Smells
@@ -8,6 +7,7 @@ module Reek
8
7
  # hard to tell initialization order and parameters so having 'initialize'
9
8
  # in a module is usually a bad idea
10
9
  #
10
+ # See docs/Module-Initialize for details.
11
11
  class ModuleInitialize < SmellDetector
12
12
  def self.contexts # :nodoc:
13
13
  [:module]
@@ -1,5 +1,4 @@
1
1
  require_relative 'smell_detector'
2
- require_relative '../smell_warning'
3
2
 
4
3
  module Reek
5
4
  module Smells
@@ -8,6 +7,7 @@ module Reek
8
7
  #
9
8
  # +NestedIterators+ reports failing methods only once.
10
9
  #
10
+ # See docs/Nested-Iterators for details.
11
11
  class NestedIterators < SmellDetector
12
12
  # The name of the config field that sets the maximum depth
13
13
  # of nested iterators to be permitted within any single method.
@@ -1,10 +1,11 @@
1
1
  require_relative 'smell_detector'
2
- require_relative '../smell_warning'
3
2
 
4
3
  module Reek
5
4
  module Smells
6
5
  # Checking for nil is a special kind of type check, and therefore a case of
7
6
  # SimulatedPolymorphism.
7
+ #
8
+ # See docs/Nil-Check for details.
8
9
  class NilCheck < SmellDetector
9
10
  def self.smell_category
10
11
  'SimulatedPolymorphism'
@@ -19,7 +20,7 @@ module Reek
19
20
  SmellWarning.new self,
20
21
  context: ctx.full_name,
21
22
  lines: [node.line],
22
- message: 'performs a nil-check.'
23
+ message: 'performs a nil-check'
23
24
  end
24
25
  end
25
26
 
@@ -1,5 +1,4 @@
1
1
  require_relative 'smell_detector'
2
- require_relative '../smell_warning'
3
2
 
4
3
  module Reek
5
4
  module Smells
@@ -22,6 +21,7 @@ module Reek
22
21
  #
23
22
  # Such a method is called PrimaDonnaMethod and is reported as a smell.
24
23
  #
24
+ # See docs/Prima-Donna-Method for details.
25
25
  class PrimaDonnaMethod < SmellDetector
26
26
  def self.contexts # :nodoc:
27
27
  [:class]
@@ -29,18 +29,25 @@ module Reek
29
29
 
30
30
  def examine_context(ctx)
31
31
  ctx.node_instance_methods.map do |method_sexp|
32
- next unless method_sexp.ends_with_bang?
32
+ check_for_smells(method_sexp, ctx)
33
+ end.compact
34
+ end
33
35
 
34
- version_without_bang = ctx.node_instance_methods.find do |sexp_item|
35
- sexp_item.name.to_s == method_sexp.name_without_bang
36
- end
37
- next if version_without_bang
36
+ private
38
37
 
39
- SmellWarning.new self,
40
- context: ctx.full_name,
41
- lines: [ctx.exp.line],
42
- message: "has prima donna method `#{method_sexp.name}`"
43
- end.compact
38
+ def check_for_smells(method_sexp, ctx)
39
+ return unless method_sexp.ends_with_bang?
40
+
41
+ version_without_bang = ctx.node_instance_methods.find do |sexp_item|
42
+ sexp_item.name.to_s == method_sexp.name_without_bang
43
+ end
44
+
45
+ return if version_without_bang
46
+
47
+ SmellWarning.new self,
48
+ context: ctx.full_name,
49
+ lines: [ctx.exp.line],
50
+ message: "has prima donna method `#{method_sexp.name}`"
44
51
  end
45
52
  end
46
53
  end
@@ -1,6 +1,5 @@
1
1
  require_relative 'smell_detector'
2
- require_relative '../smell_warning'
3
- require_relative '../source/ast_node'
2
+ require_relative '../core/ast_node'
4
3
 
5
4
  module Reek
6
5
  module Smells
@@ -23,6 +22,7 @@ module Reek
23
22
  # +RepeatedConditional+ checks for multiple conditionals
24
23
  # testing the same value throughout a single class.
25
24
  #
25
+ # See docs/Repeated-Conditional for details.
26
26
  class RepeatedConditional < SmellDetector
27
27
  # The name of the config field that sets the maximum number of
28
28
  # identical conditionals permitted within any single class.
@@ -54,7 +54,7 @@ module Reek
54
54
  lines.length > @max_identical_ifs
55
55
  end.map do |key, lines|
56
56
  occurs = lines.length
57
- expression = key.format_ruby
57
+ expression = key.format_to_ruby
58
58
  SmellWarning.new self,
59
59
  context: ctx.full_name,
60
60
  lines: lines,
@@ -1,5 +1,4 @@
1
1
  require 'set'
2
- require_relative '../smell_warning'
3
2
  require_relative '../core/smell_configuration'
4
3
 
5
4
  module Reek
@@ -7,6 +6,11 @@ module Reek
7
6
  #
8
7
  # Shared responsibilities of all smell detectors.
9
8
  #
9
+ # See
10
+ # - docs/Basic-Smell-Options
11
+ # - docs/Code-Smells
12
+ # - docs/Configuration-Files
13
+ # for details.
10
14
  class SmellDetector
11
15
  attr_reader :source
12
16
 
@@ -0,0 +1,99 @@
1
+ require 'forwardable'
2
+
3
+ module Reek
4
+ module Smells
5
+ #
6
+ # Reports a warning that a smell has been found.
7
+ #
8
+ class SmellWarning
9
+ include Comparable
10
+ extend Forwardable
11
+ attr_accessor :smell_detector, :context, :lines, :message, :parameters
12
+ def_delegators :smell_detector, :smell_category, :smell_type, :source
13
+
14
+ def initialize(smell_detector, options = {})
15
+ self.smell_detector = smell_detector
16
+ self.context = options.fetch(:context, '').to_s
17
+ self.lines = options.fetch(:lines)
18
+ self.message = options.fetch(:message)
19
+ self.parameters = options.fetch(:parameters, {})
20
+ end
21
+
22
+ def smell_classes
23
+ [smell_detector.smell_category, smell_detector.smell_type]
24
+ end
25
+
26
+ def hash
27
+ sort_key.hash
28
+ end
29
+
30
+ def <=>(other)
31
+ sort_key <=> other.sort_key
32
+ end
33
+
34
+ def eql?(other)
35
+ (self <=> other) == 0
36
+ end
37
+
38
+ def matches?(klass, other_parameters = {})
39
+ smell_classes.include?(klass.to_s) && common_parameters_equal?(other_parameters)
40
+ end
41
+
42
+ def report_on(listener)
43
+ listener.found_smell(self)
44
+ end
45
+
46
+ def yaml_hash(warning_formatter = nil)
47
+ stringified_params = Hash[parameters.map { |key, val| [key.to_s, val] }]
48
+ core_yaml_hash.
49
+ merge(stringified_params).
50
+ merge(wiki_link_hash(warning_formatter))
51
+ end
52
+
53
+ protected
54
+
55
+ def sort_key
56
+ [context, message, smell_category]
57
+ end
58
+
59
+ private
60
+
61
+ def common_parameters_equal?(other_parameters)
62
+ other_parameters.keys.each do |key|
63
+ unless parameters.key?(key)
64
+ raise ArgumentError, "The parameter #{key} you want to check for doesn't exist"
65
+ end
66
+ end
67
+
68
+ # Why not check for strict parameter equality instead of just the common ones?
69
+ #
70
+ # In `self`, `parameters` might look like this: {:name=>"@other.thing", :count=>2}
71
+ # Coming from specs, 'other_parameters' might look like this, e.g.:
72
+ # {:name=>"@other.thing"}
73
+ # So in this spec we are just specifying the "name" parameter but not the "count".
74
+ # In order to allow for this kind of leniency we just test for common parameter equality,
75
+ # not for a strict one.
76
+ parameters.values_at(*other_parameters.keys) == other_parameters.values
77
+ end
78
+
79
+ def core_yaml_hash
80
+ {
81
+ 'context' => context,
82
+ 'lines' => lines,
83
+ 'message' => message,
84
+ 'smell_category' => smell_detector.smell_category,
85
+ 'smell_type' => smell_detector.smell_type,
86
+ 'source' => smell_detector.source
87
+ }
88
+ end
89
+
90
+ def wiki_link_hash(warning_formatter)
91
+ if warning_formatter.respond_to?(:explanatory_link)
92
+ { 'wiki_link' => warning_formatter.explanatory_link(smell_detector) }
93
+ else
94
+ {}
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end