reek 3.8.3 → 3.9.0
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.
- 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
|