reek 3.8.3 → 3.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -1
  3. data/CHANGELOG.md +5 -0
  4. data/Gemfile +16 -6
  5. data/README.md +1 -0
  6. data/features/command_line_interface/smells_count.feature +1 -1
  7. data/features/command_line_interface/stdin.feature +1 -1
  8. data/features/configuration_loading.feature +2 -2
  9. data/features/programmatic_access.feature +2 -2
  10. data/features/rake_task/rake_task.feature +4 -4
  11. data/features/reports/json.feature +2 -2
  12. data/features/reports/reports.feature +6 -6
  13. data/features/reports/yaml.feature +2 -2
  14. data/features/samples.feature +19 -19
  15. data/features/step_definitions/sample_file_steps.rb +1 -1
  16. data/lib/reek/ast/node.rb +12 -1
  17. data/lib/reek/ast/sexp_extensions.rb +1 -0
  18. data/lib/reek/ast/sexp_extensions/constant.rb +9 -0
  19. data/lib/reek/ast/sexp_extensions/methods.rb +1 -20
  20. data/lib/reek/ast/sexp_extensions/module.rb +2 -2
  21. data/lib/reek/ast/sexp_extensions/self.rb +12 -0
  22. data/lib/reek/ast/sexp_extensions/send.rb +4 -9
  23. data/lib/reek/ast/sexp_extensions/super.rb +1 -1
  24. data/lib/reek/ast/sexp_extensions/variables.rb +5 -0
  25. data/lib/reek/context/attribute_context.rb +12 -0
  26. data/lib/reek/context/code_context.rb +28 -26
  27. data/lib/reek/context/ghost_context.rb +54 -0
  28. data/lib/reek/context/method_context.rb +28 -1
  29. data/lib/reek/context/module_context.rb +55 -1
  30. data/lib/reek/context/root_context.rb +8 -0
  31. data/lib/reek/context/singleton_attribute_context.rb +15 -0
  32. data/lib/reek/context/singleton_method_context.rb +20 -0
  33. data/lib/reek/context/visibility_tracker.rb +25 -16
  34. data/lib/reek/context_builder.rb +61 -31
  35. data/lib/reek/examiner.rb +0 -6
  36. data/lib/reek/smells/control_parameter.rb +1 -1
  37. data/lib/reek/smells/nested_iterators.rb +1 -1
  38. data/lib/reek/smells/nil_check.rb +2 -2
  39. data/lib/reek/smells/utility_function.rb +1 -2
  40. data/lib/reek/version.rb +1 -1
  41. data/reek.gemspec +1 -12
  42. data/spec/reek/ast/node_spec.rb +51 -3
  43. data/spec/reek/ast/sexp_extensions_spec.rb +16 -3
  44. data/spec/reek/context/code_context_spec.rb +12 -31
  45. data/spec/reek/context/ghost_context_spec.rb +60 -0
  46. data/spec/reek/context/module_context_spec.rb +22 -2
  47. data/spec/reek/context_builder_spec.rb +225 -2
  48. data/spec/reek/examiner_spec.rb +1 -1
  49. data/spec/reek/smells/attribute_spec.rb +35 -0
  50. data/spec/reek/smells/duplicate_method_call_spec.rb +1 -1
  51. data/spec/reek/tree_dresser_spec.rb +0 -1
  52. data/spec/samples/checkstyle.xml +2 -2
  53. metadata +8 -152
  54. data/lib/reek/ast/sexp_formatter.rb +0 -31
  55. data/spec/reek/ast/sexp_formatter_spec.rb +0 -35
@@ -6,6 +6,26 @@ module Reek
6
6
  # A context wrapper for any singleton method definition found in a syntax tree.
7
7
  #
8
8
  class SingletonMethodContext < MethodContext
9
+ def singleton_method?
10
+ true
11
+ end
12
+
13
+ def instance_method?
14
+ false
15
+ end
16
+
17
+ def module_function?
18
+ false
19
+ end
20
+
21
+ # Was this singleton method defined with an instance method-like syntax?
22
+ def defined_as_instance_method?
23
+ type == :def
24
+ end
25
+
26
+ def apply_current_visibility(current_visibility)
27
+ super if defined_as_instance_method?
28
+ end
9
29
  end
10
30
  end
11
31
  end
@@ -3,13 +3,14 @@ require 'private_attr/everywhere'
3
3
  module Reek
4
4
  module Context
5
5
  # Responsible for tracking visibilities in regards to CodeContexts.
6
- # :reek:Attribute
7
6
  class VisibilityTracker
8
- attr_accessor :visibility
9
7
  private_attr_accessor :tracked_visibility
10
8
 
11
- def initialize(visibility = :public)
12
- @visibility = visibility
9
+ VISIBILITY_MODIFIERS = [:private, :public, :protected, :module_function]
10
+ VISIBILITY_MAP = { public_class_method: :public, private_class_method: :private }
11
+
12
+ def initialize
13
+ @tracked_visibility = :public
13
14
  end
14
15
 
15
16
  # Handle the effects of a visibility modifier.
@@ -22,6 +23,7 @@ module Reek
22
23
  # @param names [Array<Symbol>]
23
24
  #
24
25
  def track_visibility(children: raise, visibility: raise, names: raise)
26
+ return unless VISIBILITY_MODIFIERS.include? visibility
25
27
  if names.any?
26
28
  children.each do |child|
27
29
  child.visibility = visibility if names.include?(child.name)
@@ -31,23 +33,30 @@ module Reek
31
33
  end
32
34
  end
33
35
 
36
+ # Handle the effects of a singleton visibility modifier. These can only
37
+ # be used to modify existing children.
38
+ #
39
+ # @example
40
+ # track_singleton_visibility children, :private_class_method,
41
+ # [:hide_me, :implementation_detail]
42
+ #
43
+ # @param children [Array<CodeContext>]
44
+ # @param visibility [Symbol]
45
+ # @param names [Array<Symbol>]
46
+ #
47
+ def track_singleton_visibility(children: raise, visibility: raise, names: raise)
48
+ return if names.empty?
49
+ visibility = VISIBILITY_MAP[visibility]
50
+ return unless visibility
51
+ track_visibility children: children, visibility: visibility, names: names
52
+ end
53
+
34
54
  # Sets the visibility of a child CodeContext to the tracked visibility.
35
55
  #
36
56
  # @param child [CodeContext]
37
57
  #
38
58
  def set_child_visibility(child)
39
- child.visibility = tracked_visibility
40
- end
41
-
42
- # @return [Boolean] If the visibility is public or not.
43
- def non_public_visibility?
44
- visibility != :public
45
- end
46
-
47
- private
48
-
49
- def tracked_visibility
50
- @tracked_visibility ||= :public
59
+ child.apply_current_visibility tracked_visibility
51
60
  end
52
61
  end
53
62
  end
@@ -1,10 +1,12 @@
1
+ require_relative 'context/attribute_context'
2
+ require_relative 'context/class_context'
3
+ require_relative 'context/ghost_context'
1
4
  require_relative 'context/method_context'
2
5
  require_relative 'context/module_context'
3
6
  require_relative 'context/root_context'
4
- require_relative 'context/singleton_method_context'
5
- require_relative 'context/attribute_context'
6
7
  require_relative 'context/send_context'
7
- require_relative 'context/class_context'
8
+ require_relative 'context/singleton_attribute_context'
9
+ require_relative 'context/singleton_method_context'
8
10
  require_relative 'ast/node'
9
11
 
10
12
  module Reek
@@ -16,16 +18,16 @@ module Reek
16
18
  # counting. Ideally `ContextBuilder` would only build up the context tree and leave the
17
19
  # statement and reference counting to the contexts.
18
20
  #
19
- # :reek:TooManyMethods: { max_methods: 27 }
21
+ # :reek:TooManyMethods: { max_methods: 30 }
20
22
  # :reek:UnusedPrivateMethod: { exclude: [ !ruby/regexp /process_/ ] }
21
23
  class ContextBuilder
22
24
  attr_reader :context_tree
23
- private_attr_accessor :element
25
+ private_attr_accessor :current_context
24
26
  private_attr_reader :exp
25
27
 
26
28
  def initialize(syntax_tree)
27
29
  @exp = syntax_tree
28
- @element = Context::RootContext.new(exp)
30
+ @current_context = Context::RootContext.new(exp)
29
31
  @context_tree = build(exp)
30
32
  end
31
33
 
@@ -60,7 +62,7 @@ module Reek
60
62
  else
61
63
  process exp
62
64
  end
63
- element
65
+ current_context
64
66
  end
65
67
 
66
68
  # Handles every node for which we have no context_processor.
@@ -79,6 +81,19 @@ module Reek
79
81
 
80
82
  alias_method :process_class, :process_module
81
83
 
84
+ # Handles `sclass` nodes
85
+ #
86
+ # An input example that would trigger this method would be:
87
+ #
88
+ # class << self
89
+ # end
90
+ #
91
+ def process_sclass(exp)
92
+ inside_new_context(Context::GhostContext, exp) do
93
+ process(exp)
94
+ end
95
+ end
96
+
82
97
  # Handles `casgn` ("class assign") nodes.
83
98
  #
84
99
  # An input example that would trigger this method would be:
@@ -102,7 +117,7 @@ module Reek
102
117
  # Given the above example we would count 2 statements overall.
103
118
  #
104
119
  def process_def(exp)
105
- inside_new_context(Context::MethodContext, exp) do
120
+ inside_new_context(current_context.method_context_class, exp) do
106
121
  increase_statement_count_by(exp.body)
107
122
  process(exp)
108
123
  end
@@ -133,20 +148,13 @@ module Reek
133
148
  # we also record to what the method call is referring to
134
149
  # which we later use for smell detectors like FeatureEnvy.
135
150
  #
136
- # :reek:TooManyStatements: { max_statements: 7 }
137
- # :reek:FeatureEnvy
138
151
  def process_send(exp)
139
- method_name = exp.method_name
140
- if exp.visibility_modifier?
141
- element.track_visibility(method_name, exp.arg_names)
142
- elsif exp.attribute_writer?
143
- exp.args.each do |arg|
144
- append_new_context(Context::AttributeContext, arg, exp)
145
- end
146
- else
147
- append_new_context(Context::SendContext, exp, method_name)
152
+ case current_context
153
+ when Context::ModuleContext
154
+ handle_send_for_modules exp
155
+ when Context::MethodContext
156
+ handle_send_for_methods exp
148
157
  end
149
- element.record_call_to(exp)
150
158
  process(exp)
151
159
  end
152
160
 
@@ -163,7 +171,7 @@ module Reek
163
171
  # We record one reference to `x` given the example above.
164
172
  #
165
173
  def process_op_asgn(exp)
166
- element.record_call_to(exp)
174
+ current_context.record_call_to(exp)
167
175
  process(exp)
168
176
  end
169
177
 
@@ -182,7 +190,7 @@ module Reek
182
190
  # We record one reference to `self`.
183
191
  #
184
192
  def process_ivar(exp)
185
- element.record_use_of_self
193
+ current_context.record_use_of_self
186
194
  process(exp)
187
195
  end
188
196
 
@@ -195,7 +203,7 @@ module Reek
195
203
  # def self.foo; end
196
204
  #
197
205
  def process_self(_)
198
- element.record_use_of_self
206
+ current_context.record_use_of_self
199
207
  end
200
208
 
201
209
  # Handles `zsuper` nodes a.k.a. calls to `super` without any arguments but a block possibly.
@@ -215,7 +223,7 @@ module Reek
215
223
  # We record one reference to `self`.
216
224
  #
217
225
  def process_zsuper(_)
218
- element.record_use_of_self
226
+ current_context.record_use_of_self
219
227
  end
220
228
 
221
229
  # Handles `block` nodes.
@@ -435,11 +443,11 @@ module Reek
435
443
 
436
444
  # :reek:ControlParameter
437
445
  def increase_statement_count_by(sexp)
438
- element.statement_counter.increase_by sexp
446
+ current_context.statement_counter.increase_by sexp
439
447
  end
440
448
 
441
449
  def decrease_statement_count
442
- element.statement_counter.decrease_by 1
450
+ current_context.statement_counter.decrease_by 1
443
451
  end
444
452
 
445
453
  # Stores a reference to the current context, creates a nested new one,
@@ -452,12 +460,13 @@ module Reek
452
460
  def inside_new_context(klass, exp)
453
461
  new_context = append_new_context(klass, exp)
454
462
 
455
- orig, self.element = element, new_context
463
+ orig, self.current_context = current_context, new_context
456
464
  yield
457
- self.element = orig
465
+ self.current_context = orig
458
466
  end
459
467
 
460
- # Append a new child context to the current element.
468
+ # Append a new child context to the current context but does not change the
469
+ # current context.
461
470
  #
462
471
  # @param klass [Context::*Context] - context class
463
472
  # @param args - arguments for the class initializer
@@ -465,8 +474,29 @@ module Reek
465
474
  # @return [Context::*Context] - the context that was appended
466
475
  #
467
476
  def append_new_context(klass, *args)
468
- klass.new(element, *args).tap do |new_context|
469
- element.append_child_context new_context
477
+ klass.new(current_context, *args).tap do |new_context|
478
+ new_context.register_with_parent(current_context)
479
+ end
480
+ end
481
+
482
+ def handle_send_for_modules(exp)
483
+ method_name = exp.name
484
+ arg_names = exp.arg_names
485
+ current_context.track_visibility(method_name, arg_names)
486
+ current_context.track_singleton_visibility(method_name, arg_names)
487
+ register_attributes(exp)
488
+ end
489
+
490
+ def handle_send_for_methods(exp)
491
+ append_new_context(Context::SendContext, exp, exp.name)
492
+ current_context.record_call_to(exp)
493
+ end
494
+
495
+ def register_attributes(exp)
496
+ return unless exp.attribute_writer?
497
+ klass = current_context.attribute_context_class
498
+ exp.args.each do |arg|
499
+ append_new_context(klass, arg, exp)
470
500
  end
471
501
  end
472
502
  end
@@ -1,9 +1,3 @@
1
- # NOTE: context_builder is required first to ensure unparser is required before
2
- # parser. This prevents a potentially incompatible version of parser from being
3
- # loaded first. This is only relevant when running bin/reek straight from a
4
- # checkout directory without using Bundler.
5
- #
6
- # See also https://github.com/troessner/reek/pull/468
7
1
  require_relative 'context_builder'
8
2
  require_relative 'source/source_code'
9
3
  require_relative 'cli/warning_collector'
@@ -149,7 +149,7 @@ module Reek
149
149
  end
150
150
 
151
151
  def comparison_call?(call_node)
152
- comparison_method_names.include? call_node.method_name
152
+ comparison_method_names.include? call_node.name
153
153
  end
154
154
 
155
155
  def comparison_method_names
@@ -120,7 +120,7 @@ module Reek
120
120
 
121
121
  # :reek:FeatureEnvy
122
122
  def ignored_iterator?(exp)
123
- ignore_iterators.any? { |pattern| /#{pattern}/ =~ exp.call.method_name } ||
123
+ ignore_iterators.any? { |pattern| /#{pattern}/ =~ exp.call.name } ||
124
124
  exp.without_block_arguments?
125
125
  end
126
126
  end
@@ -54,7 +54,7 @@ module Reek
54
54
  end
55
55
 
56
56
  def nil_query?(call)
57
- call.method_name == :nil?
57
+ call.name == :nil?
58
58
  end
59
59
 
60
60
  def nil_comparison?(call)
@@ -62,7 +62,7 @@ module Reek
62
62
  end
63
63
 
64
64
  def comparison_call?(call)
65
- comparison_methods.include? call.method_name
65
+ comparison_methods.include? call.name
66
66
  end
67
67
 
68
68
  def involves_nil?(call)
@@ -58,8 +58,7 @@ module Reek
58
58
  # :reek:FeatureEnvy
59
59
  # :reek:TooManyStatements: { max_statements: 6 }
60
60
  def inspect(ctx)
61
- return [] if ctx.singleton_method?
62
- return [] if ctx.number_of_statements == 0
61
+ return [] if ctx.singleton_method? || ctx.module_function?
63
62
  return [] if ctx.references_self?
64
63
  return [] if num_helper_methods(ctx).zero?
65
64
  return [] if ignore_method?(ctx)
@@ -6,6 +6,6 @@ module Reek
6
6
  # @public
7
7
  module Version
8
8
  # @public
9
- STRING = '3.8.3'
9
+ STRING = '3.9.0'
10
10
  end
11
11
  end
@@ -23,18 +23,7 @@ Gem::Specification.new do |s|
23
23
  s.summary = 'Code smell detector for Ruby'
24
24
 
25
25
  s.add_runtime_dependency 'codeclimate-engine-rb', '~> 0.1.0'
26
- s.add_runtime_dependency 'parser', '~> 2.2', '>= 2.2.2.5'
26
+ s.add_runtime_dependency 'parser', '~> 2.3'
27
27
  s.add_runtime_dependency 'private_attr', '~> 1.1'
28
28
  s.add_runtime_dependency 'rainbow', '~> 2.0'
29
- s.add_runtime_dependency 'unparser', '~> 0.2.2'
30
-
31
- s.add_development_dependency 'activesupport', '~> 4.2'
32
- s.add_development_dependency 'aruba', '~> 0.10.0'
33
- s.add_development_dependency 'ataru', '~> 0.2.0'
34
- s.add_development_dependency 'bundler', '~> 1.1'
35
- s.add_development_dependency 'cucumber', '~> 2.0'
36
- s.add_development_dependency 'factory_girl', '~> 4.0'
37
- s.add_development_dependency 'rake', '~> 10.0'
38
- s.add_development_dependency 'rspec', '~> 3.0'
39
- s.add_development_dependency 'rubocop', '~> 0.34.0'
40
29
  end
@@ -2,9 +2,57 @@ require_relative '../../spec_helper'
2
2
  require_lib 'reek/ast/node'
3
3
 
4
4
  RSpec.describe Reek::AST::Node do
5
- context 'format' do
6
- it 'formats self' do
7
- expect(sexp(:self).format_to_ruby).to eq('self')
5
+ describe '#format_to_ruby' do
6
+ it 'returns #to_s if location is not present' do
7
+ ast = sexp(:self)
8
+ expect(ast.format_to_ruby).to eq ast.to_s
9
+ end
10
+
11
+ it 'gives the right result for self' do
12
+ ast = Reek::Source::SourceCode.from('self').syntax_tree
13
+ expect(ast.format_to_ruby).to eq 'self'
14
+ end
15
+
16
+ it 'gives the right result for a simple expression' do
17
+ ast = Reek::Source::SourceCode.from('foo').syntax_tree
18
+ expect(ast.format_to_ruby).to eq 'foo'
19
+ end
20
+
21
+ it 'gives the right result for a more complex expression' do
22
+ ast = Reek::Source::SourceCode.from('foo(bar)').syntax_tree
23
+ result = ast.format_to_ruby
24
+ expect(result).to eq 'foo(bar)'
25
+ end
26
+
27
+ it 'gives te right result for send with a receiver' do
28
+ ast = Reek::Source::SourceCode.from('baz.foo(bar)').syntax_tree
29
+ expect(ast.format_to_ruby).to eq 'baz.foo(bar)'
30
+ end
31
+
32
+ it 'gives the right result for if' do
33
+ source = <<-SRC
34
+ if foo
35
+ bar
36
+ else
37
+ baz
38
+ qux
39
+ end
40
+ SRC
41
+
42
+ ast = Reek::Source::SourceCode.from(source).syntax_tree
43
+ expect(ast.format_to_ruby).to eq 'if foo ... end'
44
+ end
45
+
46
+ it 'gives the right result for def with no body' do
47
+ source = "def my_method\nend"
48
+ ast = Reek::Source::SourceCode.from(source).syntax_tree
49
+ expect(ast.format_to_ruby).to eq 'def my_method; end'
50
+ end
51
+
52
+ it 'gives the right result for ivar' do
53
+ source = '@foo'
54
+ ast = Reek::Source::SourceCode.from(source).syntax_tree
55
+ expect(ast.format_to_ruby).to eq '@foo'
8
56
  end
9
57
  end
10
58
 
@@ -319,8 +319,7 @@ end
319
319
  RSpec.describe Reek::AST::SexpExtensions::ModuleNode do
320
320
  context 'with a simple name' do
321
321
  subject do
322
- mod = sexp(:module, :Fred, nil)
323
- mod
322
+ Reek::Source::SourceCode.from('module Fred; end').syntax_tree
324
323
  end
325
324
 
326
325
  it 'has the correct #name' do
@@ -342,7 +341,7 @@ RSpec.describe Reek::AST::SexpExtensions::ModuleNode do
342
341
 
343
342
  context 'with a scoped name' do
344
343
  subject do
345
- sexp(:module, sexp(:const, sexp(:const, nil, :Foo), :Bar), nil)
344
+ Reek::Source::SourceCode.from('module Foo::Bar; end').syntax_tree
346
345
  end
347
346
 
348
347
  it 'has the correct #name' do
@@ -361,6 +360,20 @@ RSpec.describe Reek::AST::SexpExtensions::ModuleNode do
361
360
  expect(subject.full_name('Blimey::O::Reilly')).to eq 'Blimey::O::Reilly::Foo::Bar'
362
361
  end
363
362
  end
363
+
364
+ context 'with a name scoped in a namespace that is not a constant' do
365
+ subject do
366
+ Reek::Source::SourceCode.from('module foo::Bar; end').syntax_tree
367
+ end
368
+
369
+ it 'has the correct #name' do
370
+ expect(subject.name).to eq 'foo::Bar'
371
+ end
372
+
373
+ it 'has the correct #simple_name' do
374
+ expect(subject.simple_name).to eq 'Bar'
375
+ end
376
+ end
364
377
  end
365
378
 
366
379
  RSpec.describe Reek::AST::SexpExtensions::CasgnNode do