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