reek 2.1.0 → 2.2.0

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 (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