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
@@ -3,23 +3,22 @@ require 'reek/smell_warning'
3
3
 
4
4
  module Reek
5
5
  module Smells
6
-
7
6
  #
8
7
  # Duplication occurs when two fragments of code look nearly identical,
9
8
  # or when two fragments of code have nearly identical effects
10
9
  # at some conceptual level.
11
- #
10
+ #
12
11
  # +DuplicateMethodCall+ checks for repeated identical method calls
13
12
  # within any one method definition. For example, the following method
14
13
  # will report a warning:
15
- #
14
+ #
16
15
  # def double_thing()
17
16
  # @other.thing + @other.thing
18
17
  # end
19
18
  #
20
19
  class DuplicateMethodCall < SmellDetector
21
20
  SMELL_CLASS = 'Duplication'
22
- SMELL_SUBCLASS = self.name.split(/::/)[-1]
21
+ SMELL_SUBCLASS = name.split(/::/)[-1]
23
22
 
24
23
  CALL_KEY = 'call'
25
24
  OCCURRENCES_KEY = 'occurrences'
@@ -57,7 +56,7 @@ module Reek
57
56
  SmellWarning.new(SMELL_CLASS, ctx.full_name, found_call.lines,
58
57
  found_call.smell_message,
59
58
  @source, SMELL_SUBCLASS,
60
- {CALL_KEY => found_call.call, OCCURRENCES_KEY => found_call.occurs})
59
+ CALL_KEY => found_call.call, OCCURRENCES_KEY => found_call.occurs)
61
60
  end
62
61
  end
63
62
 
@@ -86,7 +85,7 @@ module Reek
86
85
  end
87
86
 
88
87
  def lines
89
- @occurences.map {|exp| exp.line}
88
+ @occurences.map(&:line)
90
89
  end
91
90
  end
92
91
 
@@ -101,14 +100,14 @@ module Reek
101
100
  end
102
101
 
103
102
  def calls
104
- result = Hash.new {|hash,key| hash[key] = FoundCall.new(key)}
103
+ result = Hash.new { |hash, key| hash[key] = FoundCall.new(key) }
105
104
  collect_calls(result)
106
105
  collect_assignments(result)
107
- result.values.sort_by {|found_call| found_call.call}
106
+ result.values.sort_by(&:call)
108
107
  end
109
108
 
110
109
  def smelly_calls
111
- calls.select {|found_call| smelly_call? found_call }
110
+ calls.select { |found_call| smelly_call? found_call }
112
111
  end
113
112
 
114
113
  private
@@ -125,14 +124,17 @@ module Reek
125
124
  next if !call_node.receiver && call_node.args.empty?
126
125
  result[call_node].record(call_node)
127
126
  end
127
+ context.local_nodes(:iter) do |call_node|
128
+ result[call_node].record(call_node)
129
+ end
128
130
  end
129
131
 
130
132
  def smelly_call?(found_call)
131
- found_call.occurs > @max_allowed_calls and not allow_calls?(found_call.call)
133
+ found_call.occurs > @max_allowed_calls && !allow_calls?(found_call.call)
132
134
  end
133
135
 
134
136
  def allow_calls?(method)
135
- @allow_calls.any? { |allow| /#{allow}/ === method }
137
+ @allow_calls.any? { |allow| /#{allow}/ =~ method }
136
138
  end
137
139
  end
138
140
  end
@@ -3,15 +3,14 @@ require 'reek/smell_warning'
3
3
 
4
4
  module Reek
5
5
  module Smells
6
-
7
6
  #
8
7
  # Feature Envy occurs when a code fragment references another object
9
8
  # more often than it references itself, or when several clients do
10
9
  # the same series of manipulations on a particular type of object.
11
- #
10
+ #
12
11
  # A simple example would be the following method, which "belongs"
13
12
  # on the Item class and not on the Cart class:
14
- #
13
+ #
15
14
  # class Cart
16
15
  # def price
17
16
  # @item.price + @item.tax
@@ -22,12 +21,12 @@ module Reek
22
21
  # code that "belongs" on one class but which is located in another
23
22
  # can be hard to find, and may upset the "System of Names"
24
23
  # in the host class.
25
- #
24
+ #
26
25
  # Feature Envy also affects the design's flexibility: A code fragment
27
26
  # that is in the wrong class creates couplings that may not be natural
28
27
  # within the application's domain, and creates a loss of cohesion
29
28
  # in the unwilling host class.
30
- #
29
+ #
31
30
  # Currently +FeatureEnvy+ reports any method that refers to self less
32
31
  # often than it refers to (ie. send messages to) some other object.
33
32
  #
@@ -35,7 +34,7 @@ module Reek
35
34
  include ExcludeInitialize
36
35
 
37
36
  SMELL_CLASS = 'LowCohesion'
38
- SMELL_SUBCLASS = self.name.split(/::/)[-1]
37
+ SMELL_SUBCLASS = name.split(/::/)[-1]
39
38
 
40
39
  RECEIVER_KEY = 'receiver'
41
40
  REFERENCES_KEY = 'references'
@@ -51,7 +50,7 @@ module Reek
51
50
  target = ref.format_ruby
52
51
  SmellWarning.new(SMELL_CLASS, method_ctx.full_name, [method_ctx.exp.line],
53
52
  "refers to #{target} more than self",
54
- @source, SMELL_SUBCLASS, {RECEIVER_KEY => target, REFERENCES_KEY => occurs})
53
+ @source, SMELL_SUBCLASS, RECEIVER_KEY => target, REFERENCES_KEY => occurs)
55
54
  end
56
55
  end
57
56
  end
@@ -4,14 +4,12 @@ require 'reek/source/code_comment'
4
4
 
5
5
  module Reek
6
6
  module Smells
7
-
8
7
  #
9
8
  # It is considered good practice to annotate every class and module
10
9
  # with a brief comment outlining its responsibilities.
11
10
  #
12
11
  class IrresponsibleModule < SmellDetector
13
-
14
- SMELL_CLASS = self.name.split(/::/)[-1]
12
+ SMELL_CLASS = name.split(/::/)[-1]
15
13
  SMELL_SUBCLASS = SMELL_CLASS
16
14
 
17
15
  MODULE_NAME_KEY = 'module_name'
@@ -31,10 +29,10 @@ module Reek
31
29
  #
32
30
  def examine_context(ctx)
33
31
  comment = Source::CodeComment.new(ctx.exp.comments)
34
- return [] if self.class.descriptive[ctx.full_name] ||= comment.is_descriptive?
32
+ return [] if self.class.descriptive[ctx.full_name] ||= comment.descriptive?
35
33
  smell = SmellWarning.new(SMELL_CLASS, ctx.full_name, [ctx.exp.line],
36
- 'has no descriptive comment',
37
- @source, SMELL_SUBCLASS, {MODULE_NAME_KEY => ctx.exp.text_name})
34
+ 'has no descriptive comment',
35
+ @source, SMELL_SUBCLASS, MODULE_NAME_KEY => ctx.exp.text_name)
38
36
  [smell]
39
37
  end
40
38
  end
@@ -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 Long Parameter List occurs when a method has more than one
10
9
  # or two parameters, or when a method yields more than one or
@@ -14,9 +13,8 @@ module Reek
14
13
  # many parameters.
15
14
  #
16
15
  class LongParameterList < SmellDetector
17
-
18
16
  SMELL_CLASS = 'LongParameterList'
19
- SMELL_SUBCLASS = self.name.split(/::/)[-1]
17
+ SMELL_SUBCLASS = name.split(/::/)[-1]
20
18
 
21
19
  PARAMETER_COUNT_KEY = 'parameter_count'
22
20
 
@@ -32,7 +30,7 @@ module Reek
32
30
  super.merge(
33
31
  MAX_ALLOWED_PARAMS_KEY => DEFAULT_MAX_ALLOWED_PARAMS,
34
32
  Core::SmellConfiguration::OVERRIDES_KEY => {
35
- "initialize" => {MAX_ALLOWED_PARAMS_KEY => 5}
33
+ 'initialize' => { MAX_ALLOWED_PARAMS_KEY => 5 }
36
34
  }
37
35
  )
38
36
  end
@@ -47,9 +45,9 @@ module Reek
47
45
  num_params = ctx.exp.arg_names.length
48
46
  return [] if num_params <= @max_allowed_params
49
47
  smell = SmellWarning.new(SMELL_CLASS, ctx.full_name, [ctx.exp.line],
50
- "has #{num_params} parameters",
51
- @source, SMELL_SUBCLASS,
52
- {PARAMETER_COUNT_KEY => num_params})
48
+ "has #{num_params} parameters",
49
+ @source, SMELL_SUBCLASS,
50
+ PARAMETER_COUNT_KEY => num_params)
53
51
  [smell]
54
52
  end
55
53
  end
@@ -3,15 +3,13 @@ require 'reek/smell_warning'
3
3
 
4
4
  module Reek
5
5
  module Smells
6
-
7
6
  #
8
7
  # A variant on LongParameterList that checks the number of items
9
8
  # passed to a block by a +yield+ call.
10
9
  #
11
10
  class LongYieldList < SmellDetector
12
-
13
11
  SMELL_CLASS = 'LongParameterList'
14
- SMELL_SUBCLASS = self.name.split(/::/)[-1]
12
+ SMELL_SUBCLASS = name.split(/::/)[-1]
15
13
 
16
14
  # The name of the config field that sets the maximum number of
17
15
  # parameters permitted in any method or block.
@@ -42,7 +40,7 @@ module Reek
42
40
  num_params = yield_node.args.length
43
41
  SmellWarning.new(SMELL_CLASS, method_ctx.full_name, [yield_node.line],
44
42
  "yields #{num_params} parameters",
45
- @source, SMELL_SUBCLASS, {PARAMETER_COUNT_KEY => num_params})
43
+ @source, SMELL_SUBCLASS, PARAMETER_COUNT_KEY => num_params)
46
44
  end
47
45
  end
48
46
  end
@@ -3,15 +3,13 @@ require 'reek/smell_warning'
3
3
 
4
4
  module Reek
5
5
  module Smells
6
-
7
6
  #
8
7
  # A Nested Iterator occurs when a block contains another block.
9
8
  #
10
9
  # +NestedIterators+ reports failing methods only once.
11
10
  #
12
11
  class NestedIterators < SmellDetector
13
-
14
- SMELL_CLASS = self.name.split(/::/)[-1]
12
+ SMELL_CLASS = name.split(/::/)[-1]
15
13
  SMELL_SUBCLASS = SMELL_CLASS
16
14
  # SMELL: should be a subclass of UnnecessaryComplexity
17
15
  NESTING_DEPTH_KEY = 'depth'
@@ -45,9 +43,9 @@ module Reek
45
43
 
46
44
  if depth && depth > value(MAX_ALLOWED_NESTING_KEY, ctx, DEFAULT_MAX_ALLOWED_NESTING)
47
45
  smell = SmellWarning.new(SMELL_CLASS, ctx.full_name, [exp.line],
48
- "contains iterators nested #{depth} deep",
49
- @source, SMELL_SUBCLASS,
50
- {NESTING_DEPTH_KEY => depth})
46
+ "contains iterators nested #{depth} deep",
47
+ @source, SMELL_SUBCLASS,
48
+ NESTING_DEPTH_KEY => depth)
51
49
  [smell]
52
50
  else
53
51
  []
@@ -55,39 +53,31 @@ module Reek
55
53
  # BUG: no longer reports nesting outside methods (eg. in Optparse)
56
54
  end
57
55
 
58
- private
56
+ private
59
57
 
60
58
  def find_deepest_iterator(ctx)
61
59
  @ignore_iterators = value(IGNORE_ITERATORS_KEY, ctx, DEFAULT_IGNORE_ITERATORS)
62
60
 
63
- find_iters(ctx.exp, 1).sort_by {|item| item[1]}.last
61
+ find_iters(ctx.exp, 1).sort_by { |item| item[1] }.last
64
62
  end
65
63
 
66
64
  def find_iters(exp, depth)
67
- exp.map do |elem|
68
- next unless Sexp === elem
69
- case elem.first
70
- when :iter
71
- find_iters_for_iter_node(elem, depth)
72
- when :class, :defn, :defs, :module
73
- next
74
- else
75
- find_iters(elem, depth)
76
- end
77
- end.flatten(1).compact
65
+ exp.unnested_nodes([:iter]).flat_map do |elem|
66
+ find_iters_for_iter_node(elem, depth)
67
+ end
78
68
  end
79
69
 
80
70
  def find_iters_for_iter_node(exp, depth)
81
71
  ignored = ignored_iterator? exp
82
- result = find_iters([exp.call], depth) +
83
- find_iters([exp.block], depth + (ignored ? 0 : 1))
72
+ result = find_iters(exp.call, depth) +
73
+ find_iters(exp.block, depth + (ignored ? 0 : 1))
84
74
  result << [exp, depth] unless ignored
85
75
  result
86
76
  end
87
77
 
88
78
  def ignored_iterator?(exp)
89
79
  name = exp.call.method_name.to_s
90
- @ignore_iterators.any? { |pattern| /#{pattern}/ === name }
80
+ @ignore_iterators.any? { |pattern| /#{pattern}/ =~ name }
91
81
  end
92
82
  end
93
83
  end
@@ -3,22 +3,20 @@ require 'reek/smell_warning'
3
3
 
4
4
  module Reek
5
5
  module Smells
6
-
7
6
  # Checking for nil is a special kind of type check, and therefore a case of
8
7
  # SimulatedPolymorphism.
9
8
  class NilCheck < SmellDetector
10
-
11
9
  SMELL_CLASS = 'SimulatedPolymorphism'
12
- SMELL_SUBCLASS = self.name.split(/::/)[-1]
10
+ SMELL_SUBCLASS = name.split(/::/)[-1]
13
11
 
14
12
  def examine_context(ctx)
15
- call_nodes = CallNodeFinder.new(ctx)
16
- case_nodes = CaseNodeFinder.new(ctx)
17
- smelly_nodes = call_nodes.smelly + case_nodes.smelly
13
+ call_node_finder = NodeFinder.new(ctx, :call, NilCallNodeDetector)
14
+ case_node_finder = NodeFinder.new(ctx, :when, NilWhenNodeDetector)
15
+ smelly_nodes = call_node_finder.smelly_nodes + case_node_finder.smelly_nodes
18
16
 
19
17
  smelly_nodes.map do |node|
20
18
  SmellWarning.new(SMELL_CLASS, ctx.full_name, Array(node.line),
21
- "performs a nil-check.",
19
+ 'performs a nil-check.',
22
20
  @source, SMELL_SUBCLASS)
23
21
  end
24
22
  end
@@ -27,64 +25,55 @@ module Reek
27
25
  # A base class that allows to work on all nodes of a certain type.
28
26
  #
29
27
  class NodeFinder
30
- SEXP_NIL = Sexp.new(:nil)
31
- def initialize(ctx, type)
32
- @nodes = Array(ctx.local_nodes(type))
28
+ def initialize(ctx, type, detector = nil)
29
+ @nodes = ctx.local_nodes(type)
30
+ @detector = detector
33
31
  end
34
- end
35
32
 
36
- #
37
- # Find call nodes which perform a nil check.
38
- #
39
- class CallNodeFinder < NodeFinder
40
- def initialize(ctx)
41
- super(ctx, :call)
33
+ def smelly_nodes
34
+ @nodes.select do |when_node|
35
+ @detector.detect(when_node)
36
+ end
42
37
  end
38
+ end
43
39
 
44
- def smelly
45
- @nodes.select{ |call|
46
- nil_chk?(call)
47
- }
48
- end
40
+ # Detect 'call' nodes which perform a nil check.
41
+ module NilCallNodeDetector
42
+ module_function
49
43
 
50
- def nil_chk?(call)
51
- nilQ_use?(call) || eq_nil_use?(call)
44
+ def detect(node)
45
+ nil_query?(node) || nil_comparison?(node)
52
46
  end
53
47
 
54
- def nilQ_use?(call)
55
- call.last == :nil?
48
+ def nil_query?(call)
49
+ call.method_name == :nil?
56
50
  end
57
51
 
58
- def eq_nil_use?(call)
59
- include_eq?(call) && call.include?(SEXP_NIL)
52
+ def nil_comparison?(call)
53
+ is_comparison_call?(call) && involves_nil?(call)
60
54
  end
61
55
 
62
- def include_eq?(call)
63
- [:==, :===].any? { |operator| call.include?(operator) }
56
+ def is_comparison_call?(call)
57
+ comparison_methods.include? call.method_name
64
58
  end
65
- end
66
-
67
- #
68
- # Finds when statements that perform a nil check.
69
- #
70
- class CaseNodeFinder < NodeFinder
71
- CASE_NIL_NODE = Sexp.new(:array, SEXP_NIL)
72
59
 
73
- def initialize(ctx)
74
- super(ctx, :when)
60
+ def involves_nil?(call)
61
+ call.receiver.nil_node? || call.args.any?(&:nil_node?)
75
62
  end
76
63
 
77
- def smelly
78
- @nodes.select{ |when_node|
79
- nil_chk?(when_node)
80
- }
64
+ def comparison_methods
65
+ [:==, :===]
81
66
  end
67
+ end
68
+
69
+ # Detect 'when' statements that perform a nil check.
70
+ module NilWhenNodeDetector
71
+ module_function
82
72
 
83
- def nil_chk?(when_node)
84
- when_node.include?(CASE_NIL_NODE)
73
+ def detect(node)
74
+ node.condition_list.any?(&:nil_node?)
85
75
  end
86
76
  end
87
-
88
77
  end
89
78
  end
90
79
  end
@@ -6,19 +6,22 @@ module Reek
6
6
  # Excerpt from:
7
7
  # http://dablog.rubypal.com/2007/8/15/bang-methods-or-danger-will-rubyist
8
8
  # since this sums it up really well:
9
- # ------------------------------
10
- # The ! in method names that end with ! means, This method is dangerous
11
- # or, more precisely, this method is the dangerous version of an
12
- # equivalent method, with the same name minus the !.Danger is relative;
13
- # the ! doesnt mean anything at all unless the method name it’s in
14
- # corresponds to a similar but bang-less method name. Don’t add ! to your
15
- # destructive (receiver-changing) methods’ names, unless you consider the
16
- # changing to be “dangerous” and you have a “non-dangerous” equivalent
17
- # method without the !. If some arbitrary subset of destructive methods end
18
- # with !, then the whole point of ! gets distorted and diluted, and ! ceases
19
- # to convey any information whatsoever
20
- # ------------------------------
9
+ #
10
+ # The ! in method names that end with ! means, "This method is dangerous"
11
+ # -- or, more precisely, this method is the "dangerous" version of an
12
+ # equivalent method, with the same name minus the !. "Danger" is
13
+ # relative; the ! doesn't mean anything at all unless the method name
14
+ # it's in corresponds to a similar but bang-less method name.
15
+ #
16
+ # Don't add ! to your destructive (receiver-changing) methods' names,
17
+ # unless you consider the changing to be "dangerous" and you have a
18
+ # "non-dangerous" equivalent method without the !. If some arbitrary
19
+ # subset of destructive methods end with !, then the whole point of !
20
+ # gets distorted and diluted, and ! ceases to convey any information
21
+ # whatsoever.
22
+ #
21
23
  # Such a method is called PrimaDonnaMethod and is reported as a smell.
24
+ #
22
25
  class PrimaDonnaMethod < SmellDetector
23
26
  SMELL_CLASS = smell_class_name
24
27
  SMELL_SUBCLASS = smell_class_name
@@ -29,11 +32,16 @@ module Reek
29
32
 
30
33
  def examine_context(ctx)
31
34
  ctx.node_instance_methods.map do |method_sexp|
32
- if method_sexp.ends_with_bang?
33
- SmellWarning.new(SMELL_CLASS, ctx.full_name, [ctx.exp.line],
34
- %Q!has prima donna method `#{method_sexp.name}`!,
35
- @source, SMELL_SUBCLASS) unless ctx.node_instance_methods.detect {|sexp_item| sexp_item.name.to_s == method_sexp.name_without_bang }
35
+ next unless method_sexp.ends_with_bang?
36
+
37
+ version_without_bang = ctx.node_instance_methods.find do |sexp_item|
38
+ sexp_item.name.to_s == method_sexp.name_without_bang
36
39
  end
40
+ next if version_without_bang
41
+
42
+ SmellWarning.new(SMELL_CLASS, ctx.full_name, [ctx.exp.line],
43
+ "has prima donna method `#{method_sexp.name}`",
44
+ @source, SMELL_SUBCLASS)
37
45
  end.compact
38
46
  end
39
47
  end