reek 3.7.1 → 3.8.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/.rubocop.yml +8 -1
- data/CHANGELOG.md +5 -0
- data/README.md +1 -1
- data/defaults.reek +3 -0
- data/docs/Code-Smells.md +1 -0
- data/docs/How-reek-works-internally.md +3 -2
- data/docs/Unused-Private-Method.md +47 -0
- data/features/samples.feature +22 -2
- data/features/step_definitions/sample_file_steps.rb +3 -0
- data/lib/reek/ast/node.rb +1 -2
- data/lib/reek/ast/object_refs.rb +30 -7
- data/lib/reek/code_comment.rb +25 -19
- data/lib/reek/context/class_context.rb +11 -0
- data/lib/reek/context/code_context.rb +58 -78
- data/lib/reek/context/module_context.rb +18 -0
- data/lib/reek/context/send_context.rb +17 -0
- data/lib/reek/context/singleton_method_context.rb +0 -3
- data/lib/reek/context/statement_counter.rb +32 -0
- data/lib/reek/context/visibility_tracker.rb +54 -0
- data/lib/reek/context_builder.rb +473 -0
- data/lib/reek/examiner.rb +14 -14
- data/lib/reek/smells/feature_envy.rb +3 -3
- data/lib/reek/smells/smell_detector.rb +1 -0
- data/lib/reek/smells/smell_repository.rb +11 -0
- data/lib/reek/smells/too_many_statements.rb +1 -1
- data/lib/reek/smells/unused_private_method.rb +82 -0
- data/lib/reek/smells/utility_function.rb +1 -1
- data/lib/reek/smells.rb +1 -0
- data/lib/reek/version.rb +1 -1
- data/spec/reek/ast/object_refs_spec.rb +20 -20
- data/spec/reek/cli/input_spec.rb +55 -0
- data/spec/reek/code_comment_spec.rb +10 -0
- data/spec/reek/context/code_context_spec.rb +8 -0
- data/spec/reek/context/module_context_spec.rb +10 -8
- data/spec/reek/context_builder_spec.rb +221 -0
- data/spec/reek/examiner_spec.rb +13 -0
- data/spec/reek/smells/boolean_parameter_spec.rb +2 -0
- data/spec/reek/smells/duplicate_method_call_spec.rb +1 -1
- data/spec/reek/smells/feature_envy_spec.rb +1 -1
- data/spec/reek/smells/too_many_statements_spec.rb +3 -3
- data/spec/reek/smells/unused_private_method_spec.rb +110 -0
- data/spec/spec_helper.rb +7 -0
- data/tasks/console.rake +5 -0
- metadata +13 -5
- data/lib/reek/tree_walker.rb +0 -237
- data/spec/reek/context/singleton_method_context_spec.rb +0 -16
- data/spec/reek/tree_walker_spec.rb +0 -237
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'private_attr/everywhere'
|
2
|
+
|
3
|
+
module Reek
|
4
|
+
module Context
|
5
|
+
# Responsible for tracking visibilities in regards to CodeContexts.
|
6
|
+
# :reek:Attribute
|
7
|
+
class VisibilityTracker
|
8
|
+
attr_accessor :visibility
|
9
|
+
private_attr_accessor :tracked_visibility
|
10
|
+
|
11
|
+
def initialize(visibility = :public)
|
12
|
+
@visibility = visibility
|
13
|
+
end
|
14
|
+
|
15
|
+
# Handle the effects of a visibility modifier.
|
16
|
+
#
|
17
|
+
# @example Modifying the visibility of existing children
|
18
|
+
# track_visibility children, :private, [:hide_me, :implementation_detail]
|
19
|
+
#
|
20
|
+
# @param children [Array<CodeContext>]
|
21
|
+
# @param visibility [Symbol]
|
22
|
+
# @param names [Array<Symbol>]
|
23
|
+
#
|
24
|
+
def track_visibility(children: raise, visibility: raise, names: raise)
|
25
|
+
if names.any?
|
26
|
+
children.each do |child|
|
27
|
+
child.visibility = visibility if names.include?(child.name)
|
28
|
+
end
|
29
|
+
else
|
30
|
+
self.tracked_visibility = visibility
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Sets the visibility of a child CodeContext to the tracked visibility.
|
35
|
+
#
|
36
|
+
# @param child [CodeContext]
|
37
|
+
#
|
38
|
+
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
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,473 @@
|
|
1
|
+
require_relative 'context/method_context'
|
2
|
+
require_relative 'context/module_context'
|
3
|
+
require_relative 'context/root_context'
|
4
|
+
require_relative 'context/singleton_method_context'
|
5
|
+
require_relative 'context/attribute_context'
|
6
|
+
require_relative 'context/send_context'
|
7
|
+
require_relative 'context/class_context'
|
8
|
+
require_relative 'ast/node'
|
9
|
+
|
10
|
+
module Reek
|
11
|
+
#
|
12
|
+
# Traverses an abstract syntax tree and fires events whenever it encounters
|
13
|
+
# specific node types.
|
14
|
+
#
|
15
|
+
# TODO: This class is responsible for statements and reference
|
16
|
+
# counting. Ideally `ContextBuilder` would only build up the context tree and leave the
|
17
|
+
# statement and reference counting to the contexts.
|
18
|
+
#
|
19
|
+
# :reek:TooManyMethods: { max_methods: 27 }
|
20
|
+
# :reek:UnusedPrivateMethod: { exclude: [ !ruby/regexp /process_/ ] }
|
21
|
+
class ContextBuilder
|
22
|
+
attr_reader :context_tree
|
23
|
+
private_attr_accessor :element
|
24
|
+
private_attr_reader :exp
|
25
|
+
|
26
|
+
def initialize(syntax_tree)
|
27
|
+
@exp = syntax_tree
|
28
|
+
@element = Context::RootContext.new(exp)
|
29
|
+
@context_tree = build(exp)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
# Processes the given AST, memoizes it and returns a tree of nested
|
35
|
+
# contexts.
|
36
|
+
#
|
37
|
+
# For example this ruby code:
|
38
|
+
#
|
39
|
+
# class Car; def drive; end; end
|
40
|
+
#
|
41
|
+
# would get compiled into this AST:
|
42
|
+
#
|
43
|
+
# (class
|
44
|
+
# (const nil :Car) nil
|
45
|
+
# (def :drive
|
46
|
+
# (args) nil))
|
47
|
+
#
|
48
|
+
# Processing this AST would result in a context tree where each node
|
49
|
+
# contains the outer context, the AST and the child contexts. The top
|
50
|
+
# node is always Reek::Context::RootContext. Using the example above,
|
51
|
+
# the tree would look like this:
|
52
|
+
#
|
53
|
+
# RootContext -> children: 1 ModuleContext -> children: 1 MethodContext
|
54
|
+
#
|
55
|
+
# @return [Reek::Context::RootContext] tree of nested contexts
|
56
|
+
def build(exp)
|
57
|
+
context_processor = "process_#{exp.type}"
|
58
|
+
if context_processor_exists?(context_processor)
|
59
|
+
send(context_processor, exp)
|
60
|
+
else
|
61
|
+
process exp
|
62
|
+
end
|
63
|
+
element
|
64
|
+
end
|
65
|
+
|
66
|
+
# Handles every node for which we have no context_processor.
|
67
|
+
#
|
68
|
+
def process(exp)
|
69
|
+
exp.children.grep(AST::Node).each(&method(:build))
|
70
|
+
end
|
71
|
+
|
72
|
+
# Handles `module` and `class` nodes.
|
73
|
+
#
|
74
|
+
def process_module(exp)
|
75
|
+
inside_new_context(Context::ModuleContext, exp) do
|
76
|
+
process(exp)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
alias_method :process_class, :process_module
|
81
|
+
|
82
|
+
# Handles `casgn` ("class assign") nodes.
|
83
|
+
#
|
84
|
+
# An input example that would trigger this method would be:
|
85
|
+
#
|
86
|
+
# Foo = Class.new Bar
|
87
|
+
#
|
88
|
+
def process_casgn(exp)
|
89
|
+
if exp.defines_module?
|
90
|
+
process_module(exp)
|
91
|
+
else
|
92
|
+
process(exp)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Handles `def` nodes.
|
97
|
+
#
|
98
|
+
# An input example that would trigger this method would be:
|
99
|
+
#
|
100
|
+
# def call_me; foo = 2; bar = 5; end
|
101
|
+
#
|
102
|
+
# Given the above example we would count 2 statements overall.
|
103
|
+
#
|
104
|
+
def process_def(exp)
|
105
|
+
inside_new_context(Context::MethodContext, exp) do
|
106
|
+
increase_statement_count_by(exp.body)
|
107
|
+
process(exp)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Handles `defs` nodes ("define singleton").
|
112
|
+
#
|
113
|
+
# An input example that would trigger this method would be:
|
114
|
+
#
|
115
|
+
# def self.call_me; foo = 2; bar = 5; end
|
116
|
+
#
|
117
|
+
# Given the above example we would count 2 statements overall.
|
118
|
+
#
|
119
|
+
def process_defs(exp)
|
120
|
+
inside_new_context(Context::SingletonMethodContext, exp) do
|
121
|
+
increase_statement_count_by(exp.body)
|
122
|
+
process(exp)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# Handles `send` nodes a.k.a. method calls.
|
127
|
+
#
|
128
|
+
# An input example that would trigger this method would be:
|
129
|
+
#
|
130
|
+
# call_me()
|
131
|
+
#
|
132
|
+
# Besides checking if it's a visibility modifier or an attribute writer
|
133
|
+
# we also record to what the method call is referring to
|
134
|
+
# which we later use for smell detectors like FeatureEnvy.
|
135
|
+
#
|
136
|
+
# :reek:TooManyStatements: { max_statements: 7 }
|
137
|
+
# :reek:FeatureEnvy
|
138
|
+
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)
|
148
|
+
end
|
149
|
+
element.record_call_to(exp)
|
150
|
+
process(exp)
|
151
|
+
end
|
152
|
+
|
153
|
+
# Handles `op_asgn` nodes a.k.a. Ruby's assignment operators.
|
154
|
+
#
|
155
|
+
# An input example that would trigger this method would be:
|
156
|
+
#
|
157
|
+
# x += 5
|
158
|
+
#
|
159
|
+
# or
|
160
|
+
#
|
161
|
+
# x *= 3
|
162
|
+
#
|
163
|
+
# We record one reference to `x` given the example above.
|
164
|
+
#
|
165
|
+
def process_op_asgn(exp)
|
166
|
+
element.record_call_to(exp)
|
167
|
+
process(exp)
|
168
|
+
end
|
169
|
+
|
170
|
+
# Handles `ivasgn` and `ivar` nodes a.k.a. nodes related to instance variables.
|
171
|
+
#
|
172
|
+
# An input example that would trigger this method would be:
|
173
|
+
#
|
174
|
+
# @item = 5
|
175
|
+
#
|
176
|
+
# for instance assignments (`ivasgn`) and
|
177
|
+
#
|
178
|
+
# call_me(@item)
|
179
|
+
#
|
180
|
+
# for just using instance variables (`ivar`).
|
181
|
+
#
|
182
|
+
# We record one reference to `self`.
|
183
|
+
#
|
184
|
+
def process_ivar(exp)
|
185
|
+
element.record_use_of_self
|
186
|
+
process(exp)
|
187
|
+
end
|
188
|
+
|
189
|
+
alias_method :process_ivasgn, :process_ivar
|
190
|
+
|
191
|
+
# Handles `self` nodes.
|
192
|
+
#
|
193
|
+
# An input example that would trigger this method would be:
|
194
|
+
#
|
195
|
+
# def self.foo; end
|
196
|
+
#
|
197
|
+
def process_self(_)
|
198
|
+
element.record_use_of_self
|
199
|
+
end
|
200
|
+
|
201
|
+
# Handles `zsuper` nodes a.k.a. calls to `super` without any arguments but a block possibly.
|
202
|
+
#
|
203
|
+
# An input example that would trigger this method would be:
|
204
|
+
#
|
205
|
+
# def call_me; super; end
|
206
|
+
#
|
207
|
+
# or
|
208
|
+
#
|
209
|
+
# def call_me; super do end; end
|
210
|
+
#
|
211
|
+
# but not
|
212
|
+
#
|
213
|
+
# def call_me; super(); end
|
214
|
+
#
|
215
|
+
# We record one reference to `self`.
|
216
|
+
#
|
217
|
+
def process_zsuper(_)
|
218
|
+
element.record_use_of_self
|
219
|
+
end
|
220
|
+
|
221
|
+
# Handles `block` nodes.
|
222
|
+
#
|
223
|
+
# An input example that would trigger this method would be:
|
224
|
+
#
|
225
|
+
# list.map { |element| puts element }
|
226
|
+
#
|
227
|
+
# Counts non-empty blocks as one statement.
|
228
|
+
#
|
229
|
+
def process_block(exp)
|
230
|
+
increase_statement_count_by(exp.block)
|
231
|
+
process(exp)
|
232
|
+
end
|
233
|
+
|
234
|
+
# Handles `begin` and `kwbegin` nodes. `begin` nodes are created implicitly
|
235
|
+
# e.g. when parsing method bodies (see example below), `kwbegin` nodes are created
|
236
|
+
# by explicitly using the `begin` keyword.
|
237
|
+
#
|
238
|
+
# An input example that would trigger this method would be:
|
239
|
+
#
|
240
|
+
# def foo; call_me(); @x = 5; end
|
241
|
+
#
|
242
|
+
# In this case the whole method body would be hanging below the `begin` node.
|
243
|
+
#
|
244
|
+
# Counts all statements in the method body.
|
245
|
+
#
|
246
|
+
# At the end we subtract one statement because the surrounding context was already counted
|
247
|
+
# as one (e.g. via `process_def`).
|
248
|
+
#
|
249
|
+
def process_begin(exp)
|
250
|
+
increase_statement_count_by(exp.children)
|
251
|
+
decrease_statement_count
|
252
|
+
process(exp)
|
253
|
+
end
|
254
|
+
|
255
|
+
alias_method :process_kwbegin, :process_begin
|
256
|
+
|
257
|
+
# Handles `if` nodes.
|
258
|
+
#
|
259
|
+
# An input example that would trigger this method would be:
|
260
|
+
#
|
261
|
+
# if a > 5 && b < 3
|
262
|
+
# puts 'bingo'
|
263
|
+
# else
|
264
|
+
# 3
|
265
|
+
# end
|
266
|
+
#
|
267
|
+
# Counts the `if` body as one statement and the `else` body as another statement.
|
268
|
+
#
|
269
|
+
# At the end we subtract one statement because the surrounding context was already counted
|
270
|
+
# as one (e.g. via `process_def`).
|
271
|
+
#
|
272
|
+
# `children[1]` refers to the `if` body (so `puts 'bingo'` from above) and
|
273
|
+
# `children[2]` to the `else` body (so `3` from above), which might be nil.
|
274
|
+
#
|
275
|
+
def process_if(exp)
|
276
|
+
children = exp.children
|
277
|
+
increase_statement_count_by(children[1])
|
278
|
+
increase_statement_count_by(children[2])
|
279
|
+
decrease_statement_count
|
280
|
+
process(exp)
|
281
|
+
end
|
282
|
+
|
283
|
+
# Handles `while` and `until` nodes.
|
284
|
+
#
|
285
|
+
# An input example that would trigger this method would be:
|
286
|
+
#
|
287
|
+
# while x < 5
|
288
|
+
# puts 'bingo'
|
289
|
+
# end
|
290
|
+
#
|
291
|
+
# Counts the `while` body as one statement.
|
292
|
+
#
|
293
|
+
# At the end we subtract one statement because the surrounding context was already counted
|
294
|
+
# as one (e.g. via `process_def`).
|
295
|
+
#
|
296
|
+
# `children[1]` below refers to the `while` body (so `puts 'bingo'` from above)
|
297
|
+
#
|
298
|
+
def process_while(exp)
|
299
|
+
increase_statement_count_by(exp.children[1])
|
300
|
+
decrease_statement_count
|
301
|
+
process(exp)
|
302
|
+
end
|
303
|
+
|
304
|
+
alias_method :process_until, :process_while
|
305
|
+
|
306
|
+
# Handles `for` nodes.
|
307
|
+
#
|
308
|
+
# An input example that would trigger this method would be:
|
309
|
+
#
|
310
|
+
# for i in [1,2,3,4]
|
311
|
+
# puts i
|
312
|
+
# end
|
313
|
+
#
|
314
|
+
# Counts the `for` body as one statement.
|
315
|
+
#
|
316
|
+
# At the end we subtract one statement because the surrounding context was already counted
|
317
|
+
# as one (e.g. via `process_def`).
|
318
|
+
#
|
319
|
+
# `children[2]` below refers to the `while` body (so `puts i` from above)
|
320
|
+
#
|
321
|
+
def process_for(exp)
|
322
|
+
increase_statement_count_by(exp.children[2])
|
323
|
+
decrease_statement_count
|
324
|
+
process(exp)
|
325
|
+
end
|
326
|
+
|
327
|
+
# Handles `rescue` nodes.
|
328
|
+
#
|
329
|
+
# An input example that would trigger this method would be:
|
330
|
+
#
|
331
|
+
# def simple
|
332
|
+
# raise ArgumentError, 'raising...'
|
333
|
+
# rescue => e
|
334
|
+
# puts 'rescued!'
|
335
|
+
# end
|
336
|
+
#
|
337
|
+
# Counts everything before the `rescue` body as one statement.
|
338
|
+
#
|
339
|
+
# At the end we subtract one statement because the surrounding context was already counted
|
340
|
+
# as one (e.g. via `process_def`).
|
341
|
+
#
|
342
|
+
# `exp.children.first` below refers to everything before the actual `rescue`
|
343
|
+
# which would be the
|
344
|
+
#
|
345
|
+
# raise ArgumentError, 'raising...'
|
346
|
+
#
|
347
|
+
# in the example above.
|
348
|
+
# `exp` would be the whole method body wrapped under a `rescue` node.
|
349
|
+
# See `process_resbody` for additional reference.
|
350
|
+
#
|
351
|
+
def process_rescue(exp)
|
352
|
+
increase_statement_count_by(exp.children.first)
|
353
|
+
decrease_statement_count
|
354
|
+
process(exp)
|
355
|
+
end
|
356
|
+
|
357
|
+
# Handles `resbody` nodes.
|
358
|
+
#
|
359
|
+
# An input example that would trigger this method would be:
|
360
|
+
#
|
361
|
+
# def simple
|
362
|
+
# raise ArgumentError, 'raising...'
|
363
|
+
# rescue => e
|
364
|
+
# puts 'rescued!'
|
365
|
+
# end
|
366
|
+
#
|
367
|
+
# Counts the exception capturing and every statement related to it.
|
368
|
+
#
|
369
|
+
# So `exp.children[1..-1]` from the code below would be an array with the following 2 elements:
|
370
|
+
# [
|
371
|
+
# (lvasgn :e),
|
372
|
+
# (send nil :puts (str "rescued!"))
|
373
|
+
# ]
|
374
|
+
#
|
375
|
+
# which thus counts as 2 statements.
|
376
|
+
# `exp` would be the whole `rescue` body.
|
377
|
+
# See `process_rescue` for additional reference.
|
378
|
+
#
|
379
|
+
def process_resbody(exp)
|
380
|
+
increase_statement_count_by(exp.children[1..-1].compact)
|
381
|
+
process(exp)
|
382
|
+
end
|
383
|
+
|
384
|
+
# Handles `case` nodes.
|
385
|
+
#
|
386
|
+
# An input example that would trigger this method would be:
|
387
|
+
#
|
388
|
+
# foo = 5
|
389
|
+
# case foo
|
390
|
+
# when 1..100
|
391
|
+
# puts 'In between'
|
392
|
+
# else
|
393
|
+
# puts 'Not sure what I got here'
|
394
|
+
# end
|
395
|
+
#
|
396
|
+
# Counts the `else` body.
|
397
|
+
#
|
398
|
+
# At the end we subtract one statement because the surrounding context was already counted
|
399
|
+
# as one (e.g. via `process_def`).
|
400
|
+
#
|
401
|
+
def process_case(exp)
|
402
|
+
increase_statement_count_by(exp.else_body)
|
403
|
+
decrease_statement_count
|
404
|
+
process(exp)
|
405
|
+
end
|
406
|
+
|
407
|
+
# Handles `when` nodes.
|
408
|
+
#
|
409
|
+
# An input example that would trigger this method would be:
|
410
|
+
#
|
411
|
+
# foo = 5
|
412
|
+
# case foo
|
413
|
+
# when (1..100)
|
414
|
+
# puts 'In between'
|
415
|
+
# else
|
416
|
+
# puts 'Not sure what I got here'
|
417
|
+
# end
|
418
|
+
#
|
419
|
+
# Note that input like
|
420
|
+
#
|
421
|
+
# if foo then :holla else :nope end
|
422
|
+
#
|
423
|
+
# does not trigger this method.
|
424
|
+
#
|
425
|
+
# Counts the `when` body.
|
426
|
+
#
|
427
|
+
def process_when(exp)
|
428
|
+
increase_statement_count_by(exp.body)
|
429
|
+
process(exp)
|
430
|
+
end
|
431
|
+
|
432
|
+
def context_processor_exists?(name)
|
433
|
+
self.class.private_method_defined?(name)
|
434
|
+
end
|
435
|
+
|
436
|
+
# :reek:ControlParameter
|
437
|
+
def increase_statement_count_by(sexp)
|
438
|
+
element.statement_counter.increase_by sexp
|
439
|
+
end
|
440
|
+
|
441
|
+
def decrease_statement_count
|
442
|
+
element.statement_counter.decrease_by 1
|
443
|
+
end
|
444
|
+
|
445
|
+
# Stores a reference to the current context, creates a nested new one,
|
446
|
+
# yields to the given block and then restores the previous context.
|
447
|
+
#
|
448
|
+
# @param klass [Context::*Context] - context class
|
449
|
+
# @param exp - current expression
|
450
|
+
# @yield block
|
451
|
+
#
|
452
|
+
def inside_new_context(klass, exp)
|
453
|
+
new_context = append_new_context(klass, exp)
|
454
|
+
|
455
|
+
orig, self.element = element, new_context
|
456
|
+
yield
|
457
|
+
self.element = orig
|
458
|
+
end
|
459
|
+
|
460
|
+
# Append a new child context to the current element.
|
461
|
+
#
|
462
|
+
# @param klass [Context::*Context] - context class
|
463
|
+
# @param args - arguments for the class initializer
|
464
|
+
#
|
465
|
+
# @return [Context::*Context] - the context that was appended
|
466
|
+
#
|
467
|
+
def append_new_context(klass, *args)
|
468
|
+
klass.new(element, *args).tap do |new_context|
|
469
|
+
element.append_child_context new_context
|
470
|
+
end
|
471
|
+
end
|
472
|
+
end
|
473
|
+
end
|
data/lib/reek/examiner.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
|
-
# NOTE:
|
1
|
+
# NOTE: context_builder is required first to ensure unparser is required before
|
2
2
|
# parser. This prevents a potentially incompatible version of parser from being
|
3
3
|
# loaded first. This is only relevant when running bin/reek straight from a
|
4
4
|
# checkout directory without using Bundler.
|
5
5
|
#
|
6
6
|
# See also https://github.com/troessner/reek/pull/468
|
7
|
-
require_relative '
|
7
|
+
require_relative 'context_builder'
|
8
8
|
require_relative 'source/source_code'
|
9
9
|
require_relative 'cli/warning_collector'
|
10
10
|
require_relative 'smells/smell_repository'
|
@@ -17,6 +17,7 @@ module Reek
|
|
17
17
|
#
|
18
18
|
# :reek:TooManyInstanceVariables: { max_instance_variables: 6 }
|
19
19
|
class Examiner
|
20
|
+
private_attr_reader :collector, :source, :smell_repository
|
20
21
|
#
|
21
22
|
# Creates an Examiner which scans the given +source+ for code smells.
|
22
23
|
#
|
@@ -25,7 +26,7 @@ module Reek
|
|
25
26
|
# if it is a File or IO, it is opened and Ruby source code is read from it;
|
26
27
|
#
|
27
28
|
# @param filter_by_smells [Array<String>]
|
28
|
-
# List of smell types to filter by.
|
29
|
+
# List of smell types to filter by, e.g. "DuplicateMethodCall".
|
29
30
|
#
|
30
31
|
# @param configuration [Configuration::AppConfiguration]
|
31
32
|
# The configuration for this Examiner.
|
@@ -34,11 +35,11 @@ module Reek
|
|
34
35
|
def initialize(source,
|
35
36
|
filter_by_smells = [],
|
36
37
|
configuration: Configuration::AppConfiguration.default)
|
37
|
-
@source
|
38
|
-
@
|
39
|
-
@
|
40
|
-
@
|
41
|
-
|
38
|
+
@source = Source::SourceCode.from(source)
|
39
|
+
@collector = CLI::WarningCollector.new
|
40
|
+
@smell_types = Smells::SmellRepository.eligible_smell_types(filter_by_smells)
|
41
|
+
@smell_repository = Smells::SmellRepository.new(smell_types: @smell_types,
|
42
|
+
configuration: configuration.directive_for(description))
|
42
43
|
run
|
43
44
|
end
|
44
45
|
|
@@ -77,14 +78,13 @@ module Reek
|
|
77
78
|
|
78
79
|
private
|
79
80
|
|
80
|
-
private_attr_reader :configuration, :collector, :smell_types, :source
|
81
|
-
|
82
81
|
def run
|
83
|
-
smell_repository = Smells::SmellRepository.new(
|
84
|
-
smell_types: smell_types,
|
85
|
-
configuration: configuration.directive_for(description))
|
86
82
|
syntax_tree = source.syntax_tree
|
87
|
-
|
83
|
+
return unless syntax_tree
|
84
|
+
ContextBuilder.new(syntax_tree).context_tree.each do |element|
|
85
|
+
smell_repository.examine(element)
|
86
|
+
end
|
87
|
+
|
88
88
|
smell_repository.report_on(collector)
|
89
89
|
end
|
90
90
|
end
|
@@ -47,12 +47,12 @@ module Reek
|
|
47
47
|
#
|
48
48
|
def inspect(ctx)
|
49
49
|
return [] unless ctx.references_self?
|
50
|
-
envious_receivers(ctx).map do |name,
|
50
|
+
envious_receivers(ctx).map do |name, lines|
|
51
51
|
smell_warning(
|
52
52
|
context: ctx,
|
53
|
-
lines:
|
53
|
+
lines: lines,
|
54
54
|
message: "refers to #{name} more than self (maybe move it to another class?)",
|
55
|
-
parameters: { name: name.to_s, count:
|
55
|
+
parameters: { name: name.to_s, count: lines.size })
|
56
56
|
end
|
57
57
|
end
|
58
58
|
|
@@ -14,6 +14,7 @@ module Reek
|
|
14
14
|
#
|
15
15
|
# :reek:TooManyMethods: { max_methods: 19 }
|
16
16
|
# :reek:TooManyInstanceVariables: { max_instance_variables: 5 }
|
17
|
+
# :reek:UnusedPrivateMethod: { exclude: [ inherited, smell_warning ] }
|
17
18
|
class SmellDetector
|
18
19
|
attr_reader :config
|
19
20
|
private_attr_accessor :smells_found
|
@@ -10,10 +10,21 @@ module Reek
|
|
10
10
|
class SmellRepository
|
11
11
|
private_attr_reader :configuration, :smell_types, :detectors
|
12
12
|
|
13
|
+
# @return [Array<Reek::Smells::SmellDetector>] All known SmellDetectors
|
14
|
+
# e.g. [Reek::Smells::BooleanParameter, Reek::Smells::ClassVariable].
|
13
15
|
def self.smell_types
|
14
16
|
Reek::Smells::SmellDetector.descendants.sort_by(&:name)
|
15
17
|
end
|
16
18
|
|
19
|
+
# @param filter_by_smells [Array<String>]
|
20
|
+
# List of smell types to filter by, e.g. "DuplicateMethodCall".
|
21
|
+
# More precisely it should be whatever is returned by `SmellDetector`.smell_type.
|
22
|
+
# This means that you can write the "DuplicateMethodCall" from above also like this:
|
23
|
+
# Reek::Smells::DuplicateMethodCall.smell_type
|
24
|
+
# if you want to make sure you do not fat-finger strings.
|
25
|
+
#
|
26
|
+
# @return [Array<Reek::Smells::SmellDetector>] All SmellDetectors that we want to filter for
|
27
|
+
# e.g. [Reek::Smells::Attribute].
|
17
28
|
def self.eligible_smell_types(filter_by_smells = [])
|
18
29
|
return smell_types if filter_by_smells.empty?
|
19
30
|
smell_types.select do |klass|
|