reek 2.0.0 → 2.0.1

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 (68) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +5 -0
  3. data/README.md +13 -2
  4. data/Rakefile +2 -1
  5. data/features/command_line_interface/options.feature +2 -3
  6. data/features/command_line_interface/smell_selection.feature +0 -1
  7. data/features/command_line_interface/smells_count.feature +0 -2
  8. data/features/command_line_interface/stdin.feature +3 -10
  9. data/features/configuration_files/masking_smells.feature +1 -5
  10. data/features/configuration_files/overrides_defaults.feature +0 -1
  11. data/features/rake_task/rake_task.feature +1 -4
  12. data/features/reports/json.feature +73 -0
  13. data/features/reports/reports.feature +0 -3
  14. data/features/reports/yaml.feature +0 -1
  15. data/features/ruby_api/api.feature +0 -1
  16. data/features/samples.feature +0 -4
  17. data/features/step_definitions/reek_steps.rb +14 -4
  18. data/features/support/env.rb +3 -0
  19. data/lib/reek/cli/option_interpreter.rb +2 -0
  20. data/lib/reek/cli/options.rb +3 -3
  21. data/lib/reek/cli/report/formatter.rb +1 -1
  22. data/lib/reek/cli/report/location_formatter.rb +11 -0
  23. data/lib/reek/cli/report/report.rb +11 -2
  24. data/lib/reek/core/code_context.rb +49 -10
  25. data/lib/reek/core/sniffer.rb +2 -2
  26. data/lib/reek/core/{code_parser.rb → tree_walker.rb} +16 -7
  27. data/lib/reek/examiner.rb +0 -39
  28. data/lib/reek/smells.rb +3 -25
  29. data/lib/reek/smells/feature_envy.rb +0 -2
  30. data/lib/reek/smells/smell_detector.rb +0 -6
  31. data/lib/reek/source/sexp_extensions.rb +17 -6
  32. data/lib/reek/source/sexp_node.rb +1 -1
  33. data/lib/reek/spec.rb +66 -0
  34. data/lib/reek/spec/should_reek.rb +0 -6
  35. data/lib/reek/spec/should_reek_of.rb +0 -49
  36. data/lib/reek/spec/should_reek_only_of.rb +0 -11
  37. data/lib/reek/version.rb +6 -1
  38. data/reek.gemspec +3 -2
  39. data/spec/reek/cli/json_report_spec.rb +20 -0
  40. data/spec/reek/core/code_context_spec.rb +6 -0
  41. data/spec/reek/core/smell_configuration_spec.rb +3 -3
  42. data/spec/reek/core/{code_parser_spec.rb → tree_walker_spec.rb} +4 -4
  43. data/spec/reek/examiner_spec.rb +0 -19
  44. data/spec/reek/smells/attribute_spec.rb +6 -9
  45. data/spec/reek/smells/boolean_parameter_spec.rb +13 -15
  46. data/spec/reek/smells/class_variable_spec.rb +17 -17
  47. data/spec/reek/smells/control_parameter_spec.rb +44 -46
  48. data/spec/reek/smells/data_clump_spec.rb +73 -70
  49. data/spec/reek/smells/duplicate_method_call_spec.rb +39 -37
  50. data/spec/reek/smells/feature_envy_spec.rb +53 -57
  51. data/spec/reek/smells/irresponsible_module_spec.rb +12 -11
  52. data/spec/reek/smells/long_parameter_list_spec.rb +33 -26
  53. data/spec/reek/smells/long_yield_list_spec.rb +15 -19
  54. data/spec/reek/smells/module_initialize_spec.rb +4 -6
  55. data/spec/reek/smells/nested_iterators_spec.rb +28 -28
  56. data/spec/reek/smells/nil_check_spec.rb +18 -20
  57. data/spec/reek/smells/prima_donna_method_spec.rb +6 -9
  58. data/spec/reek/smells/repeated_conditional_spec.rb +41 -43
  59. data/spec/reek/smells/too_many_instance_variables_spec.rb +6 -11
  60. data/spec/reek/smells/too_many_methods_spec.rb +44 -61
  61. data/spec/reek/smells/too_many_statements_spec.rb +14 -41
  62. data/spec/reek/smells/uncommunicative_method_name_spec.rb +6 -11
  63. data/spec/reek/smells/uncommunicative_module_name_spec.rb +10 -14
  64. data/spec/reek/smells/uncommunicative_parameter_name_spec.rb +15 -16
  65. data/spec/reek/smells/uncommunicative_variable_name_spec.rb +33 -36
  66. data/spec/reek/smells/unused_parameters_spec.rb +16 -19
  67. data/spec/reek/smells/utility_function_spec.rb +7 -10
  68. metadata +22 -6
@@ -1,4 +1,5 @@
1
1
  require 'rainbow'
2
+ require 'json'
2
3
 
3
4
  module Reek
4
5
  module Cli
@@ -58,12 +59,11 @@ module Reek
58
59
  private
59
60
 
60
61
  def display_summary
61
- print smells.reject(&:empty?).join("\n")
62
+ smells.reject(&:empty?).each { |smell| puts smell }
62
63
  end
63
64
 
64
65
  def display_total_smell_count
65
66
  return unless @examiners.size > 1
66
- print "\n"
67
67
  print total_smell_count_message
68
68
  end
69
69
 
@@ -102,6 +102,15 @@ module Reek
102
102
  end
103
103
  end
104
104
 
105
+ #
106
+ # Displays a list of smells in JSON format
107
+ # JSON with empty array for 0 smells
108
+ class JsonReport < Base
109
+ def show
110
+ print ::JSON.generate(smells.map(&:yaml_hash))
111
+ end
112
+ end
113
+
105
114
  #
106
115
  # Saves the report as a HTML file
107
116
  #
@@ -10,9 +10,45 @@ module Reek
10
10
  class CodeContext
11
11
  attr_reader :exp
12
12
 
13
- def initialize(outer, exp)
14
- @outer = outer
15
- @exp = exp
13
+ # Initializes a new CodeContext.
14
+ #
15
+ # context - *_context from the `core` namespace
16
+ # exp - Reek::Source::AstNode
17
+ #
18
+ # Examples:
19
+ #
20
+ # Given something like:
21
+ #
22
+ # class Omg; def foo(x); puts x; end; end
23
+ #
24
+ # the first time this is instantianted from TreeWalker `context` is a StopContext:
25
+ #
26
+ # #<Reek::Core::StopContext:0x00000002231098 @name="">
27
+ #
28
+ # and `exp` looks like this:
29
+ #
30
+ # (class
31
+ # (const nil :Omg) nil
32
+ # (def :foo
33
+ # (args
34
+ # (arg :x))
35
+ # (send nil :puts
36
+ # (lvar :x))))
37
+ #
38
+ # The next time we instantiate a CodeContext via TreeWalker `context` would be:
39
+ #
40
+ # Reek::Core::ModuleContext
41
+ #
42
+ # and `exp` is:
43
+ #
44
+ # (def :foo
45
+ # (args
46
+ # (arg :x))
47
+ # (send nil :puts
48
+ # (lvar :x)))
49
+ def initialize(context, exp)
50
+ @context = context
51
+ @exp = exp
16
52
  end
17
53
 
18
54
  def name
@@ -29,7 +65,10 @@ module Reek
29
65
 
30
66
  def matches?(candidates)
31
67
  my_fq_name = full_name
32
- candidates.any? { |str| /#{str}/ =~ my_fq_name }
68
+ candidates.any? do |candidate|
69
+ candidate = Regexp.quote(candidate) if candidate.is_a?(String)
70
+ /#{candidate}/ =~ my_fq_name
71
+ end
33
72
  end
34
73
 
35
74
  #
@@ -37,7 +76,7 @@ module Reek
37
76
  # that knows how to deal with the request.
38
77
  #
39
78
  def method_missing(method, *args)
40
- @outer.send(method, *args)
79
+ @context.send(method, *args)
41
80
  end
42
81
 
43
82
  def num_methods
@@ -45,12 +84,12 @@ module Reek
45
84
  end
46
85
 
47
86
  def full_name
48
- outer = @outer ? @outer.full_name : ''
49
- exp.full_name(outer)
87
+ context = @context ? @context.full_name : ''
88
+ exp.full_name(context)
50
89
  end
51
90
 
52
91
  def config_for(detector_class)
53
- outer_config_for(detector_class).merge(
92
+ context_config_for(detector_class).merge(
54
93
  config[detector_class.smell_type] || {})
55
94
  end
56
95
 
@@ -64,8 +103,8 @@ module Reek
64
103
  end
65
104
  end
66
105
 
67
- def outer_config_for(detector_class)
68
- @outer ? @outer.config_for(detector_class) : {}
106
+ def context_config_for(detector_class)
107
+ @context ? @context.config_for(detector_class) : {}
69
108
  end
70
109
  end
71
110
  end
@@ -1,4 +1,4 @@
1
- require 'reek/core/code_parser'
1
+ require 'reek/core/tree_walker'
2
2
  require 'reek/core/smell_repository'
3
3
  require 'reek/configuration/app_configuration'
4
4
 
@@ -17,7 +17,7 @@ module Reek
17
17
  end
18
18
 
19
19
  def report_on(listener)
20
- CodeParser.new(@smell_repository).process(syntax_tree) if syntax_tree
20
+ TreeWalker.new(@smell_repository).process(syntax_tree) if syntax_tree
21
21
  @smell_repository.report_on(listener)
22
22
  end
23
23
 
@@ -12,21 +12,26 @@ module Reek
12
12
  # SMELL: This class is responsible for counting statements and for feeding
13
13
  # each context to the smell repository.
14
14
  # SMELL: This class has a name that doesn't match its responsibility.
15
- class CodeParser
16
- def initialize(smell_repository, ctx = StopContext.new)
15
+ class TreeWalker
16
+ def initialize(smell_repository)
17
17
  @smell_repository = smell_repository
18
- @element = ctx
18
+ @element = StopContext.new
19
19
  end
20
20
 
21
21
  def process(exp)
22
- meth = "process_#{exp.type}"
23
- meth = :process_default unless self.respond_to?(meth)
24
- send(meth, exp)
22
+ context_processor = "process_#{exp.type}"
23
+ if context_processor_exists?(context_processor)
24
+ send(context_processor, exp)
25
+ else
26
+ process_default exp
27
+ end
25
28
  @element
26
29
  end
27
30
 
28
31
  def process_default(exp)
29
- exp.children.each { |sub| process(sub) if sub.is_a? AST::Node }
32
+ exp.children.each do |child|
33
+ process(child) if child.is_a? AST::Node
34
+ end
30
35
  end
31
36
 
32
37
  def process_module(exp)
@@ -140,6 +145,10 @@ module Reek
140
145
 
141
146
  private
142
147
 
148
+ def context_processor_exists?(name)
149
+ respond_to?(name)
150
+ end
151
+
143
152
  def count_clause(sexp)
144
153
  @element.count_statements(1) if sexp
145
154
  end
data/lib/reek/examiner.rb CHANGED
@@ -16,9 +16,6 @@ module Reek
16
16
  #
17
17
  # Creates an Examiner which scans the given +source+ for code smells.
18
18
  #
19
- # The smells reported against any source file can be "masked" by
20
- # creating *.reek files. See TBS for details.
21
- #
22
19
  # @param [Source::SourceCode, Array<String>, #to_reek_source]
23
20
  # If +source+ is a String it is assumed to be Ruby source code;
24
21
  # if it is a File, the file is opened and parsed for Ruby source code;
@@ -64,41 +61,5 @@ module Reek
64
61
  def smelly?
65
62
  !smells.empty?
66
63
  end
67
-
68
- #
69
- # Returns an Array of SmellWarning objects, one for each non-masked smell
70
- # in the source.
71
- #
72
- # @deprecated Use #smells instead.
73
- #
74
- alias_method :all_active_smells, :smells
75
-
76
- #
77
- # Returns an Array of SmellWarning objects, one for each smell
78
- # in the source; includes active smells and masked smells.
79
- #
80
- # @return [Array<SmellWarning>]
81
- #
82
- # @deprecated Use #smells instead.
83
- #
84
- alias_method :all_smells, :smells
85
-
86
- #
87
- # Returns the number of non-masked smells in the source.
88
- #
89
- # @deprecated Use #smells_count instead.
90
- #
91
- def num_active_smells
92
- smells.length
93
- end
94
-
95
- #
96
- # Returns the number of masked smells in the source.
97
- #
98
- # @deprecated Masked smells are no longer reported; this method always returns 0.
99
- #
100
- def num_masked_smells
101
- 0
102
- end
103
64
  end
104
65
  end
data/lib/reek/smells.rb CHANGED
@@ -1,28 +1,6 @@
1
- require 'reek/smells/attribute'
2
- require 'reek/smells/boolean_parameter'
3
- require 'reek/smells/class_variable'
4
- require 'reek/smells/control_parameter'
5
- require 'reek/smells/data_clump'
6
- require 'reek/smells/duplicate_method_call'
7
- require 'reek/smells/feature_envy'
8
- require 'reek/smells/irresponsible_module'
9
- require 'reek/smells/long_parameter_list'
10
- require 'reek/smells/long_yield_list'
11
- require 'reek/smells/module_initialize'
12
- require 'reek/smells/nested_iterators'
13
- require 'reek/smells/nil_check'
14
- require 'reek/smells/prima_donna_method'
15
- require 'reek/smells/repeated_conditional'
16
- require 'reek/smells/too_many_instance_variables'
17
- require 'reek/smells/too_many_methods'
18
- require 'reek/smells/too_many_statements'
19
- require 'reek/smells/uncommunicative_method_name'
20
- require 'reek/smells/uncommunicative_module_name'
21
- require 'reek/smells/uncommunicative_parameter_name'
22
- require 'reek/smells/uncommunicative_variable_name'
23
- require 'reek/smells/unused_parameters'
24
- require 'reek/smells/utility_function'
25
- # SMELL: Duplication -- all these should be found automagically
1
+ require 'require_all'
2
+
3
+ require_rel 'smells'
26
4
 
27
5
  module Reek
28
6
  #
@@ -31,8 +31,6 @@ module Reek
31
31
  # often than it refers to (ie. send messages to) some other object.
32
32
  #
33
33
  class FeatureEnvy < SmellDetector
34
- include ExcludeInitialize
35
-
36
34
  def self.smell_category
37
35
  'LowCohesion'
38
36
  end
@@ -4,12 +4,6 @@ require 'reek/core/smell_configuration'
4
4
 
5
5
  module Reek
6
6
  module Smells
7
- module ExcludeInitialize
8
- def self.default_config
9
- super.merge(EXCLUDE_KEY => ['initialize'])
10
- end
11
- end
12
-
13
7
  #
14
8
  # Shared responsibilities of all smell detectors.
15
9
  #
@@ -2,6 +2,10 @@ require 'reek/source/sexp_node'
2
2
 
3
3
  module Reek
4
4
  module Source
5
+ #
6
+ # Extension modules providing utility methods to AstNode objects, depending
7
+ # on their type.
8
+ #
5
9
  module SexpExtensions
6
10
  # Base module for utility methods for argument nodes.
7
11
  module ArgNodeBase
@@ -96,21 +100,22 @@ module Reek
96
100
  end
97
101
  end
98
102
 
103
+ # Utility methods for :and nodes.
99
104
  module AndNode
100
105
  include LogicOperatorBase
101
106
  end
102
107
 
103
- # Utility methods for :or nodes
108
+ # Utility methods for :or nodes.
104
109
  module OrNode
105
110
  include LogicOperatorBase
106
111
  end
107
112
 
108
- # Utility methods for :attrasgn nodes
113
+ # Utility methods for :attrasgn nodes.
109
114
  module AttrasgnNode
110
115
  def args() self[3] end
111
116
  end
112
117
 
113
- # Utility methods for :case nodes
118
+ # Utility methods for :case nodes.
114
119
  module CaseNode
115
120
  def condition() self[1] end
116
121
 
@@ -123,7 +128,7 @@ module Reek
123
128
  end
124
129
  end
125
130
 
126
- # Utility methods for :when nodes
131
+ # Utility methods for :when nodes.
127
132
  module WhenNode
128
133
  def condition_list
129
134
  children[0..-2]
@@ -134,7 +139,7 @@ module Reek
134
139
  end
135
140
  end
136
141
 
137
- # Utility methods for :send nodes
142
+ # Utility methods for :send nodes.
138
143
  module SendNode
139
144
  def receiver() self[1] end
140
145
  def method_name() self[2] end
@@ -172,6 +177,7 @@ module Reek
172
177
  include VariableBase
173
178
  end
174
179
 
180
+ # Utility methods for :lvar nodes.
175
181
  module LvarNode
176
182
  def var_name() self[1] end
177
183
  end
@@ -226,6 +232,7 @@ module Reek
226
232
  end
227
233
  end
228
234
 
235
+ # Utility methods for :defs nodes.
229
236
  module DefsNode
230
237
  def receiver() self[1] end
231
238
  def name() self[2] end
@@ -242,7 +249,7 @@ module Reek
242
249
  end
243
250
  end
244
251
 
245
- # Utility methods for :if nodes
252
+ # Utility methods for :if nodes.
246
253
  module IfNode
247
254
  def condition() self[1] end
248
255
 
@@ -263,6 +270,7 @@ module Reek
263
270
  end
264
271
  end
265
272
 
273
+ # Utility methods for :lit nodes.
266
274
  module LitNode
267
275
  def value() self[1] end
268
276
  end
@@ -292,11 +300,13 @@ module Reek
292
300
  end
293
301
  end
294
302
 
303
+ # Utility methods for :class nodes.
295
304
  module ClassNode
296
305
  include ModuleNode
297
306
  def superclass() self[2] end
298
307
  end
299
308
 
309
+ # Utility methods for :yield nodes.
300
310
  module YieldNode
301
311
  def args() self[1..-1] end
302
312
 
@@ -305,6 +315,7 @@ module Reek
305
315
  end
306
316
  end
307
317
 
318
+ # Utility methods for :zsuper nodes.
308
319
  module ZsuperNode
309
320
  def method_name
310
321
  :super
@@ -1,7 +1,7 @@
1
1
  module Reek
2
2
  module Source
3
3
  #
4
- # Extensions to +Sexp+ to allow +CodeParser+ to navigate the abstract
4
+ # Extensions to +Sexp+ to allow +TreeWalker+ to navigate the abstract
5
5
  # syntax tree more easily.
6
6
  #
7
7
  module SexpNode
data/lib/reek/spec.rb CHANGED
@@ -40,6 +40,72 @@ module Reek
40
40
  # ruby.should_not reek_of(:FeatureEnvy)
41
41
  #
42
42
  module Spec
43
+ #
44
+ # Checks the target source code for instances of "smell category"
45
+ # and returns true only if it can find one of them that matches.
46
+ #
47
+ # Remember that this includes our "smell types" as well. So it could be the
48
+ # "smell type" UtilityFunction, which is represented as a concrete class
49
+ # in reek but it could also be "Duplication" which is a "smell categgory".
50
+ #
51
+ # In theory you could pass many different types of input here:
52
+ # - :UtilityFunction
53
+ # - "UtilityFunction"
54
+ # - UtilityFunction (this works in our specs because we tend to do "include Reek:Smells")
55
+ # - Reek::Smells::UtilityFunction (the right way if you really want to pass a class)
56
+ # - "Duplication" or :Duplication which is an abstract "smell category"
57
+ #
58
+ # It is recommended to pass this as a symbol like :UtilityFunction. However we don't
59
+ # enforce this.
60
+ #
61
+ # Additionally you can be more specific and pass in "smell_details" you
62
+ # want to check for as well e.g. "name" or "count" (see the examples below).
63
+ # The parameters you can check for are depending on the smell you are checking for.
64
+ # For instance "count" doesn't make sense everywhere whereas "name" does in most cases.
65
+ # If you pass in a parameter that doesn't exist (e.g. you make a typo like "namme") reek will
66
+ # raise an ArgumentError to give you a hint that you passed something that doesn't make
67
+ # much sense.
68
+ #
69
+ # smell_category - The "smell category" or "smell_type" we check for.
70
+ # smells_details - A hash containing "smell warning" parameters
71
+ #
72
+ # Examples
73
+ #
74
+ # Without smell_details:
75
+ #
76
+ # reek_of(:FeatureEnvy)
77
+ # reek_of(Reek::Smells::UtilityFunction)
78
+ #
79
+ # With smell_details:
80
+ #
81
+ # reek_of(UncommunicativeParameterName, name: 'x2')
82
+ # reek_of(DataClump, count: 3)
83
+ #
84
+ # Examples from a real spec
85
+ #
86
+ # expect(src).to reek_of(Reek::Smells::DuplicateMethodCall, name: '@other.thing')
87
+ #
88
+ def reek_of(smell_category, smell_details = {})
89
+ ShouldReekOf.new(smell_category, smell_details)
90
+ end
91
+
92
+ #
93
+ # See the documentaton for "reek_of".
94
+ #
95
+ # Notable differences to reek_of:
96
+ # 1.) "reek_of" doesn't mind if there are other smells of a different category.
97
+ # "reek_only_of" will fail in that case.
98
+ # 2.) "reek_only_of" doesn't support the additional smell_details hash.
99
+ def reek_only_of(smell_category)
100
+ ShouldReekOnlyOf.new(smell_category)
101
+ end
102
+
103
+ #
104
+ # Returns +true+ if and only if the target source code contains smells.
105
+ #
106
+ def reek
107
+ ShouldReek.new
108
+ end
43
109
  end
44
110
  end
45
111