reek 2.0.0 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
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