reek 3.8.3 → 3.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -1
- data/CHANGELOG.md +5 -0
- data/Gemfile +16 -6
- data/README.md +1 -0
- data/features/command_line_interface/smells_count.feature +1 -1
- data/features/command_line_interface/stdin.feature +1 -1
- data/features/configuration_loading.feature +2 -2
- data/features/programmatic_access.feature +2 -2
- data/features/rake_task/rake_task.feature +4 -4
- data/features/reports/json.feature +2 -2
- data/features/reports/reports.feature +6 -6
- data/features/reports/yaml.feature +2 -2
- data/features/samples.feature +19 -19
- data/features/step_definitions/sample_file_steps.rb +1 -1
- data/lib/reek/ast/node.rb +12 -1
- data/lib/reek/ast/sexp_extensions.rb +1 -0
- data/lib/reek/ast/sexp_extensions/constant.rb +9 -0
- data/lib/reek/ast/sexp_extensions/methods.rb +1 -20
- data/lib/reek/ast/sexp_extensions/module.rb +2 -2
- data/lib/reek/ast/sexp_extensions/self.rb +12 -0
- data/lib/reek/ast/sexp_extensions/send.rb +4 -9
- data/lib/reek/ast/sexp_extensions/super.rb +1 -1
- data/lib/reek/ast/sexp_extensions/variables.rb +5 -0
- data/lib/reek/context/attribute_context.rb +12 -0
- data/lib/reek/context/code_context.rb +28 -26
- data/lib/reek/context/ghost_context.rb +54 -0
- data/lib/reek/context/method_context.rb +28 -1
- data/lib/reek/context/module_context.rb +55 -1
- data/lib/reek/context/root_context.rb +8 -0
- data/lib/reek/context/singleton_attribute_context.rb +15 -0
- data/lib/reek/context/singleton_method_context.rb +20 -0
- data/lib/reek/context/visibility_tracker.rb +25 -16
- data/lib/reek/context_builder.rb +61 -31
- data/lib/reek/examiner.rb +0 -6
- data/lib/reek/smells/control_parameter.rb +1 -1
- data/lib/reek/smells/nested_iterators.rb +1 -1
- data/lib/reek/smells/nil_check.rb +2 -2
- data/lib/reek/smells/utility_function.rb +1 -2
- data/lib/reek/version.rb +1 -1
- data/reek.gemspec +1 -12
- data/spec/reek/ast/node_spec.rb +51 -3
- data/spec/reek/ast/sexp_extensions_spec.rb +16 -3
- data/spec/reek/context/code_context_spec.rb +12 -31
- data/spec/reek/context/ghost_context_spec.rb +60 -0
- data/spec/reek/context/module_context_spec.rb +22 -2
- data/spec/reek/context_builder_spec.rb +225 -2
- data/spec/reek/examiner_spec.rb +1 -1
- data/spec/reek/smells/attribute_spec.rb +35 -0
- data/spec/reek/smells/duplicate_method_call_spec.rb +1 -1
- data/spec/reek/tree_dresser_spec.rb +0 -1
- data/spec/samples/checkstyle.xml +2 -2
- metadata +8 -152
- data/lib/reek/ast/sexp_formatter.rb +0 -31
- 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
|
-
|
12
|
-
|
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.
|
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
|
data/lib/reek/context_builder.rb
CHANGED
@@ -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/
|
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:
|
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 :
|
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
|
-
@
|
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
|
-
|
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(
|
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
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
exp
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
446
|
+
current_context.statement_counter.increase_by sexp
|
439
447
|
end
|
440
448
|
|
441
449
|
def decrease_statement_count
|
442
|
-
|
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.
|
463
|
+
orig, self.current_context = current_context, new_context
|
456
464
|
yield
|
457
|
-
self.
|
465
|
+
self.current_context = orig
|
458
466
|
end
|
459
467
|
|
460
|
-
# Append a new child context to the current
|
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(
|
469
|
-
|
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
|
data/lib/reek/examiner.rb
CHANGED
@@ -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'
|
@@ -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.
|
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.
|
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.
|
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)
|
data/lib/reek/version.rb
CHANGED
data/reek.gemspec
CHANGED
@@ -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.
|
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
|
data/spec/reek/ast/node_spec.rb
CHANGED
@@ -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
|
-
|
6
|
-
it '
|
7
|
-
|
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
|
-
|
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
|
-
|
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
|