reek 3.4.1 → 3.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/docs/API.md +1 -1
- data/docs/Nested-Iterators.md +15 -0
- data/features/configuration_files/directory_specific_directives.feature +9 -9
- data/features/samples.feature +4 -8
- data/features/support/env.rb +3 -1
- data/lib/reek/ast/node.rb +0 -14
- data/lib/reek/ast/sexp_extensions.rb +26 -22
- data/lib/reek/configuration/app_configuration.rb +7 -4
- data/lib/reek/configuration/configuration_validator.rb +20 -3
- data/lib/reek/configuration/default_directive.rb +3 -1
- data/lib/reek/configuration/directory_directives.rb +1 -1
- data/lib/reek/smells/boolean_parameter.rb +1 -1
- data/lib/reek/smells/nested_iterators.rb +3 -3
- data/lib/reek/smells/too_many_instance_variables.rb +1 -1
- data/lib/reek/smells/uncommunicative_variable_name.rb +3 -3
- data/lib/reek/tree_walker.rb +7 -6
- data/lib/reek/version.rb +1 -1
- data/spec/factories/factories.rb +11 -0
- data/spec/reek/configuration/app_configuration_spec.rb +32 -13
- data/spec/reek/context/code_context_spec.rb +1 -1
- data/spec/reek/smells/nested_iterators_spec.rb +70 -23
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 777f1ba21321886d04fe6cb9be80e175250f8490
|
4
|
+
data.tar.gz: 574babd716780ea8013ceb2b485dda1e3eaa521d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4d5a1ea491b81408ae7c6ee5dc8699b21e7df7ecdfa888d3826556acb677dabf6a2a145677c89e783f5aaaf58b914ab3138ce444e393a19e476e3c6940ff6bbe
|
7
|
+
data.tar.gz: cd1ab7b709b02c9d2756fc716aee8418295bebf48757fdaff1dab3fca689bb84a8d5b359fc6026e4f3d8450d80128b36e00ba12138f904596a9752ad200f69e4
|
data/CHANGELOG.md
CHANGED
data/docs/API.md
CHANGED
@@ -76,7 +76,7 @@ IrresponsibleModule:
|
|
76
76
|
You can now use either
|
77
77
|
|
78
78
|
```Ruby
|
79
|
-
Reek::Configuration::AppConfiguration.from_path Pathname.new('config.reek
|
79
|
+
Reek::Configuration::AppConfiguration.from_path Pathname.new('config.reek')
|
80
80
|
```
|
81
81
|
|
82
82
|
but you can also pass a hash via `Reek::Configuration::AppConfiguration.from_map`.
|
data/docs/Nested-Iterators.md
CHANGED
@@ -33,6 +33,21 @@ test.rb -- 1 warning:
|
|
33
33
|
|
34
34
|
Nested Iterators reports failing methods only once.
|
35
35
|
`Object#tap` is ignored by default and thus does not count as iterator.
|
36
|
+
Furthermore iterators without block arguments are not counted, e.g.:
|
37
|
+
|
38
|
+
|
39
|
+
```Ruby
|
40
|
+
def foo
|
41
|
+
before do
|
42
|
+
item.each do |part|
|
43
|
+
puts part
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
```
|
48
|
+
|
49
|
+
would not smell of NestedIterators (given a maximum allowed nesting of 1) since the
|
50
|
+
`before` would not be counted (because it doesn't pass any arguments to the block).
|
36
51
|
|
37
52
|
## Configuration
|
38
53
|
|
@@ -24,8 +24,8 @@ Feature: Directory directives
|
|
24
24
|
class UsersController < ApplicationController
|
25
25
|
def show
|
26
26
|
respond_with do |format|
|
27
|
-
format.json { @user.to_custom_json }
|
28
|
-
format.
|
27
|
+
format.json { |json| @user.to_custom_json }
|
28
|
+
format.xml { |xml| @user.to_fancy_xml }
|
29
29
|
end
|
30
30
|
end
|
31
31
|
end
|
@@ -94,7 +94,7 @@ Feature: Directory directives
|
|
94
94
|
class UsersController < ApplicationController
|
95
95
|
def show
|
96
96
|
respond_with do |format|
|
97
|
-
format.json { @user.to_custom_json }
|
97
|
+
format.json { |json| @user.to_custom_json }
|
98
98
|
end
|
99
99
|
end
|
100
100
|
end
|
@@ -145,7 +145,7 @@ Feature: Directory directives
|
|
145
145
|
class UsersController < ApplicationController
|
146
146
|
def show
|
147
147
|
respond_with do |format|
|
148
|
-
format.json { @user.to_custom_json }
|
148
|
+
format.json { |json| @user.to_custom_json }
|
149
149
|
end
|
150
150
|
end
|
151
151
|
end
|
@@ -155,7 +155,7 @@ Feature: Directory directives
|
|
155
155
|
class ProjectController < ApplicationController
|
156
156
|
def show
|
157
157
|
respond_with do |format|
|
158
|
-
format.json { @project.to_custom_json }
|
158
|
+
format.json { |json| @project.to_custom_json }
|
159
159
|
end
|
160
160
|
end
|
161
161
|
end
|
@@ -251,8 +251,8 @@ Feature: Directory directives
|
|
251
251
|
"""
|
252
252
|
class Klass
|
253
253
|
def meth
|
254
|
-
respond_to do
|
255
|
-
answer_to do
|
254
|
+
respond_to do |arg|
|
255
|
+
answer_to do |arg|
|
256
256
|
end
|
257
257
|
end
|
258
258
|
end
|
@@ -262,8 +262,8 @@ Feature: Directory directives
|
|
262
262
|
"""
|
263
263
|
class Klazz
|
264
264
|
def meth
|
265
|
-
respond_to do
|
266
|
-
answer_to do
|
265
|
+
respond_to do |arg|
|
266
|
+
answer_to do |arg|
|
267
267
|
end
|
268
268
|
end
|
269
269
|
end
|
data/features/samples.feature
CHANGED
@@ -59,7 +59,7 @@ Feature: Basic smell detection
|
|
59
59
|
Module#inline calls Inline.const_get(lang) 2 times (DuplicateMethodCall)
|
60
60
|
Module#inline calls options[:testing] 2 times (DuplicateMethodCall)
|
61
61
|
Module#inline has approx 12 statements (TooManyStatements)
|
62
|
-
optparse.rb --
|
62
|
+
optparse.rb -- 117 warnings:
|
63
63
|
OptionParser has at least 42 methods (TooManyMethods)
|
64
64
|
OptionParser has at least 6 instance variables (TooManyInstanceVariables)
|
65
65
|
OptionParser has the variable name 'f' (UncommunicativeVariableName)
|
@@ -73,7 +73,6 @@ Feature: Basic smell detection
|
|
73
73
|
OptionParser tests not_style at least 3 times (RepeatedConditional)
|
74
74
|
OptionParser tests s at least 7 times (RepeatedConditional)
|
75
75
|
OptionParser#banner is a writable attribute (Attribute)
|
76
|
-
OptionParser#complete contains iterators nested 2 deep (NestedIterators)
|
77
76
|
OptionParser#complete has 4 parameters (LongParameterList)
|
78
77
|
OptionParser#complete has approx 6 statements (TooManyStatements)
|
79
78
|
OptionParser#complete has boolean parameter 'icase' (BooleanParameter)
|
@@ -93,7 +92,7 @@ Feature: Basic smell detection
|
|
93
92
|
OptionParser#make_switch calls sdesc << "-#{q}" 2 times (DuplicateMethodCall)
|
94
93
|
OptionParser#make_switch calls search(:atype, FalseClass) 2 times (DuplicateMethodCall)
|
95
94
|
OptionParser#make_switch calls search(:atype, o) 6 times (DuplicateMethodCall)
|
96
|
-
OptionParser#make_switch contains iterators nested
|
95
|
+
OptionParser#make_switch contains iterators nested 2 deep (NestedIterators)
|
97
96
|
OptionParser#make_switch has approx 72 statements (TooManyStatements)
|
98
97
|
OptionParser#make_switch has the variable name 'a' (UncommunicativeVariableName)
|
99
98
|
OptionParser#make_switch has the variable name 'c' (UncommunicativeVariableName)
|
@@ -114,7 +113,6 @@ Feature: Basic smell detection
|
|
114
113
|
OptionParser#parse_in_order calls setter.call(sw.switch_name, val) 2 times (DuplicateMethodCall)
|
115
114
|
OptionParser#parse_in_order calls sw.block 2 times (DuplicateMethodCall)
|
116
115
|
OptionParser#parse_in_order calls sw.switch_name 2 times (DuplicateMethodCall)
|
117
|
-
OptionParser#parse_in_order contains iterators nested 3 deep (NestedIterators)
|
118
116
|
OptionParser#parse_in_order has approx 35 statements (TooManyStatements)
|
119
117
|
OptionParser#permute calls argv[0] 2 times (DuplicateMethodCall)
|
120
118
|
OptionParser#permute refers to argv more than self (maybe move it to another class?) (FeatureEnvy)
|
@@ -133,7 +131,6 @@ Feature: Basic smell detection
|
|
133
131
|
OptionParser::Arguable has initialize method (ModuleInitialize)
|
134
132
|
OptionParser::Arguable#options has approx 6 statements (TooManyStatements)
|
135
133
|
OptionParser::Arguable#options= is controlled by argument opt (ControlParameter)
|
136
|
-
OptionParser::CompletingHash#match contains iterators nested 2 deep (NestedIterators)
|
137
134
|
OptionParser::Completion#complete calls candidates.size 2 times (DuplicateMethodCall)
|
138
135
|
OptionParser::Completion#complete calls k.id2name 2 times (DuplicateMethodCall)
|
139
136
|
OptionParser::Completion#complete has approx 23 statements (TooManyStatements)
|
@@ -172,7 +169,6 @@ Feature: Basic smell detection
|
|
172
169
|
OptionParser::Switch#summarize calls left.shift 2 times (DuplicateMethodCall)
|
173
170
|
OptionParser::Switch#summarize calls left[(-1)] 3 times (DuplicateMethodCall)
|
174
171
|
OptionParser::Switch#summarize calls s.length 3 times (DuplicateMethodCall)
|
175
|
-
OptionParser::Switch#summarize contains iterators nested 2 deep (NestedIterators)
|
176
172
|
OptionParser::Switch#summarize has 5 parameters (LongParameterList)
|
177
173
|
OptionParser::Switch#summarize has approx 28 statements (TooManyStatements)
|
178
174
|
OptionParser::Switch#summarize has the variable name 'l' (UncommunicativeVariableName)
|
@@ -215,7 +211,7 @@ Feature: Basic smell detection
|
|
215
211
|
RedCloth#blocks is controlled by argument deep_code (ControlParameter)
|
216
212
|
RedCloth#blocks refers to blk more than self (maybe move it to another class?) (FeatureEnvy)
|
217
213
|
RedCloth#clean_html calls tags[tag] 2 times (DuplicateMethodCall)
|
218
|
-
RedCloth#clean_html contains iterators nested
|
214
|
+
RedCloth#clean_html contains iterators nested 2 deep (NestedIterators)
|
219
215
|
RedCloth#clean_html doesn't depend on instance state (maybe move it to another class?) (UtilityFunction)
|
220
216
|
RedCloth#clean_html has approx 15 statements (TooManyStatements)
|
221
217
|
RedCloth#clean_html has the variable name 'q' (UncommunicativeVariableName)
|
@@ -283,5 +279,5 @@ Feature: Basic smell detection
|
|
283
279
|
RedCloth#textile_popup_help has the parameter name 'windowW' (UncommunicativeParameterName)
|
284
280
|
RedCloth#to_html has approx 26 statements (TooManyStatements)
|
285
281
|
RedCloth#v_align doesn't depend on instance state (maybe move it to another class?) (UtilityFunction)
|
286
|
-
|
282
|
+
265 total warnings
|
287
283
|
"""
|
data/features/support/env.rb
CHANGED
data/lib/reek/ast/node.rb
CHANGED
@@ -31,20 +31,10 @@ module Reek
|
|
31
31
|
comment_lines.map(&:text).join("\n")
|
32
32
|
end
|
33
33
|
|
34
|
-
# @deprecated
|
35
|
-
def [](index)
|
36
|
-
elements[index]
|
37
|
-
end
|
38
|
-
|
39
34
|
def line
|
40
35
|
loc && loc.line
|
41
36
|
end
|
42
37
|
|
43
|
-
# @deprecated
|
44
|
-
def first
|
45
|
-
type
|
46
|
-
end
|
47
|
-
|
48
38
|
#
|
49
39
|
# Carries out a depth-first traversal of this syntax tree, yielding
|
50
40
|
# every Sexp of type `target_type`. The traversal ignores any node
|
@@ -129,10 +119,6 @@ module Reek
|
|
129
119
|
def each_sexp
|
130
120
|
children.each { |elem| yield elem if elem.is_a? ::Parser::AST::Node }
|
131
121
|
end
|
132
|
-
|
133
|
-
def elements
|
134
|
-
[type, *children]
|
135
|
-
end
|
136
122
|
end
|
137
123
|
end
|
138
124
|
end
|
@@ -120,10 +120,10 @@ module Reek
|
|
120
120
|
|
121
121
|
# Base module for utility methods for :and and :or nodes.
|
122
122
|
module LogicOperatorBase
|
123
|
-
def condition()
|
123
|
+
def condition() children.first end
|
124
124
|
|
125
125
|
def body_nodes(type, ignoring = [])
|
126
|
-
|
126
|
+
children[1].find_nodes type, ignoring
|
127
127
|
end
|
128
128
|
end
|
129
129
|
|
@@ -139,12 +139,12 @@ module Reek
|
|
139
139
|
|
140
140
|
# Utility methods for :attrasgn nodes.
|
141
141
|
module AttrasgnNode
|
142
|
-
def args()
|
142
|
+
def args() children[2] end
|
143
143
|
end
|
144
144
|
|
145
145
|
# Utility methods for :case nodes.
|
146
146
|
module CaseNode
|
147
|
-
def condition()
|
147
|
+
def condition() children.first end
|
148
148
|
|
149
149
|
def body_nodes(type, ignoring = [])
|
150
150
|
children[1..-1].compact.flat_map { |child| child.find_nodes(type, ignoring) }
|
@@ -177,7 +177,7 @@ module Reek
|
|
177
177
|
end
|
178
178
|
|
179
179
|
def arg_names
|
180
|
-
args.map { |arg| arg
|
180
|
+
args.map { |arg| arg.children.first }
|
181
181
|
end
|
182
182
|
|
183
183
|
def module_creation_call?
|
@@ -215,7 +215,7 @@ module Reek
|
|
215
215
|
|
216
216
|
# Base module for utility methods for nodes representing variables.
|
217
217
|
module VariableBase
|
218
|
-
def name()
|
218
|
+
def name() children.first end
|
219
219
|
end
|
220
220
|
|
221
221
|
# Utility methods for :cvar nodes.
|
@@ -301,11 +301,11 @@ module Reek
|
|
301
301
|
|
302
302
|
# Utility methods for :def nodes.
|
303
303
|
module DefNode
|
304
|
-
def name()
|
305
|
-
def argslist()
|
304
|
+
def name() children.first end
|
305
|
+
def argslist() children[1] end
|
306
306
|
|
307
307
|
def body
|
308
|
-
|
308
|
+
children[2]
|
309
309
|
end
|
310
310
|
|
311
311
|
def full_name(outer)
|
@@ -323,12 +323,12 @@ module Reek
|
|
323
323
|
|
324
324
|
# Utility methods for :defs nodes.
|
325
325
|
module DefsNode
|
326
|
-
def receiver()
|
327
|
-
def name()
|
328
|
-
def argslist()
|
326
|
+
def receiver() children.first end
|
327
|
+
def name() children[1] end
|
328
|
+
def argslist() children[2] end
|
329
329
|
|
330
330
|
def body
|
331
|
-
|
331
|
+
children[3]
|
332
332
|
end
|
333
333
|
|
334
334
|
include MethodNodeBase
|
@@ -345,7 +345,7 @@ module Reek
|
|
345
345
|
|
346
346
|
# Utility methods for :if nodes.
|
347
347
|
module IfNode
|
348
|
-
def condition()
|
348
|
+
def condition() children.first end
|
349
349
|
|
350
350
|
def body_nodes(type, ignoring = [])
|
351
351
|
children[1..-1].compact.flat_map { |child| child.find_nodes(type, ignoring) }
|
@@ -354,23 +354,27 @@ module Reek
|
|
354
354
|
|
355
355
|
# Utility methods for :block nodes.
|
356
356
|
module BlockNode
|
357
|
-
def call()
|
358
|
-
def args()
|
359
|
-
def block()
|
360
|
-
def parameters()
|
357
|
+
def call() children.first end
|
358
|
+
def args() children[1] end
|
359
|
+
def block() children[2] end
|
360
|
+
def parameters() children[1] || [] end
|
361
361
|
|
362
362
|
def parameter_names
|
363
|
-
parameters
|
363
|
+
parameters.children
|
364
364
|
end
|
365
365
|
|
366
366
|
def simple_name
|
367
367
|
:block
|
368
368
|
end
|
369
|
+
|
370
|
+
def without_block_arguments?
|
371
|
+
args.components.empty?
|
372
|
+
end
|
369
373
|
end
|
370
374
|
|
371
375
|
# Utility methods for :lit nodes.
|
372
376
|
module LitNode
|
373
|
-
def value()
|
377
|
+
def value() children.first end
|
374
378
|
end
|
375
379
|
|
376
380
|
# Utility methods for :const nodes.
|
@@ -425,7 +429,7 @@ module Reek
|
|
425
429
|
# Utility methods for :class nodes.
|
426
430
|
module ClassNode
|
427
431
|
include ModuleNode
|
428
|
-
def superclass()
|
432
|
+
def superclass() children[1] end
|
429
433
|
end
|
430
434
|
|
431
435
|
# Utility methods for :casgn nodes.
|
@@ -464,7 +468,7 @@ module Reek
|
|
464
468
|
|
465
469
|
# Utility methods for :yield nodes.
|
466
470
|
module YieldNode
|
467
|
-
def args()
|
471
|
+
def args() children end
|
468
472
|
|
469
473
|
def arg_names
|
470
474
|
args.map { |arg| arg[1] }
|
@@ -42,10 +42,9 @@ module Reek
|
|
42
42
|
def self.from_map(map = {})
|
43
43
|
allocate.tap do |instance|
|
44
44
|
instance.instance_eval do
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
self.excluded_paths = map.fetch(:excluded_paths, []).extend(ExcludedPaths)
|
45
|
+
load_values map.fetch(:directory_directives, {})
|
46
|
+
load_values map.fetch(:default_directive, {})
|
47
|
+
load_values EXCLUDE_PATHS_KEY => map.fetch(:excluded_paths, [])
|
49
48
|
end
|
50
49
|
end
|
51
50
|
end
|
@@ -90,6 +89,10 @@ module Reek
|
|
90
89
|
def find_and_load(path: nil)
|
91
90
|
configuration_file = ConfigurationFileFinder.find_and_load(path: path)
|
92
91
|
|
92
|
+
load_values(configuration_file)
|
93
|
+
end
|
94
|
+
|
95
|
+
def load_values(configuration_file)
|
93
96
|
configuration_file.each do |key, value|
|
94
97
|
case
|
95
98
|
when key == EXCLUDE_PATHS_KEY
|
@@ -8,9 +8,26 @@ module Reek
|
|
8
8
|
|
9
9
|
# :reek:UtilityFunction
|
10
10
|
def smell_type?(key)
|
11
|
-
|
12
|
-
|
13
|
-
|
11
|
+
case key
|
12
|
+
when Class
|
13
|
+
true
|
14
|
+
when String
|
15
|
+
begin
|
16
|
+
Reek::Smells.const_defined? key
|
17
|
+
rescue NameError
|
18
|
+
false
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# :reek:UtilityFunction
|
24
|
+
def key_to_smell_detector(key)
|
25
|
+
case key
|
26
|
+
when Class
|
27
|
+
key
|
28
|
+
else
|
29
|
+
Reek::Smells.const_get key
|
30
|
+
end
|
14
31
|
end
|
15
32
|
|
16
33
|
def error_message_for_missing_directory(pathname)
|
@@ -32,7 +32,7 @@ module Reek
|
|
32
32
|
with_valid_directory(path) do |directory|
|
33
33
|
self[directory] = config.each_with_object({}) do |(key, value), hash|
|
34
34
|
abort(error_message_for_invalid_smell_type(key)) unless smell_type?(key)
|
35
|
-
hash[
|
35
|
+
hash[key_to_smell_detector(key)] = value
|
36
36
|
end
|
37
37
|
end
|
38
38
|
self
|
@@ -25,7 +25,7 @@ module Reek
|
|
25
25
|
# :reek:FeatureEnvy
|
26
26
|
def examine_context(ctx)
|
27
27
|
ctx.default_assignments.select do |_param, value|
|
28
|
-
[:true, :false].include?(value
|
28
|
+
[:true, :false].include?(value.type)
|
29
29
|
end.map do |parameter, _value|
|
30
30
|
smell_warning(
|
31
31
|
context: ctx,
|
@@ -44,7 +44,6 @@ module Reek
|
|
44
44
|
else
|
45
45
|
[]
|
46
46
|
end
|
47
|
-
# BUG: no longer reports nesting outside methods (eg. in Optparse)
|
48
47
|
end
|
49
48
|
|
50
49
|
private
|
@@ -72,9 +71,10 @@ module Reek
|
|
72
71
|
result
|
73
72
|
end
|
74
73
|
|
74
|
+
# :reek:FeatureEnvy
|
75
75
|
def ignored_iterator?(exp)
|
76
|
-
|
77
|
-
|
76
|
+
ignore_iterators.any? { |pattern| /#{pattern}/ =~ exp.call.method_name } ||
|
77
|
+
exp.without_block_arguments?
|
78
78
|
end
|
79
79
|
end
|
80
80
|
end
|
@@ -39,7 +39,7 @@ module Reek
|
|
39
39
|
#
|
40
40
|
def examine_context(ctx)
|
41
41
|
max_allowed_ivars = value(MAX_ALLOWED_IVARS_KEY, ctx, DEFAULT_MAX_IVARS)
|
42
|
-
count = ctx.local_nodes(:ivasgn).map { |ivasgn| ivasgn
|
42
|
+
count = ctx.local_nodes(:ivasgn).map { |ivasgn| ivasgn.children.first }.uniq.length
|
43
43
|
return [] if count <= max_allowed_ivars
|
44
44
|
[smell_warning(
|
45
45
|
context: ctx,
|
@@ -83,18 +83,18 @@ module Reek
|
|
83
83
|
def find_assignment_variable_names(exp, accumulator)
|
84
84
|
assignment_nodes = exp.each_node(:lvasgn, [:class, :module, :defs, :def])
|
85
85
|
|
86
|
-
case exp.
|
86
|
+
case exp.type
|
87
87
|
when :class, :module
|
88
88
|
assignment_nodes += exp.each_node(:ivasgn, [:class, :module])
|
89
89
|
end
|
90
90
|
|
91
|
-
assignment_nodes.each { |asgn| accumulator[asgn
|
91
|
+
assignment_nodes.each { |asgn| accumulator[asgn.children.first].push(asgn.line) }
|
92
92
|
end
|
93
93
|
|
94
94
|
# :reek:FeatureEnvy
|
95
95
|
# :reek:TooManyStatements: { max_statements: 6 }
|
96
96
|
def find_block_argument_variable_names(exp, accumulator)
|
97
|
-
arg_search_exp = case exp.
|
97
|
+
arg_search_exp = case exp.type
|
98
98
|
when :class, :module
|
99
99
|
exp
|
100
100
|
when :defs, :def
|
data/lib/reek/tree_walker.rb
CHANGED
@@ -140,14 +140,15 @@ module Reek
|
|
140
140
|
alias_method :process_kwbegin, :process_begin
|
141
141
|
|
142
142
|
def process_if(exp)
|
143
|
-
|
144
|
-
count_clause(
|
143
|
+
children = exp.children
|
144
|
+
count_clause(children[1])
|
145
|
+
count_clause(children[2])
|
145
146
|
element.count_statements(-1)
|
146
147
|
process_default(exp)
|
147
148
|
end
|
148
149
|
|
149
150
|
def process_while(exp)
|
150
|
-
count_clause(exp[
|
151
|
+
count_clause(exp.children[1])
|
151
152
|
element.count_statements(-1)
|
152
153
|
process_default(exp)
|
153
154
|
end
|
@@ -155,19 +156,19 @@ module Reek
|
|
155
156
|
alias_method :process_until, :process_while
|
156
157
|
|
157
158
|
def process_for(exp)
|
158
|
-
count_clause(exp[
|
159
|
+
count_clause(exp.children[2])
|
159
160
|
element.count_statements(-1)
|
160
161
|
process_default(exp)
|
161
162
|
end
|
162
163
|
|
163
164
|
def process_rescue(exp)
|
164
|
-
count_clause(exp
|
165
|
+
count_clause(exp.children.first)
|
165
166
|
element.count_statements(-1)
|
166
167
|
process_default(exp)
|
167
168
|
end
|
168
169
|
|
169
170
|
def process_resbody(exp)
|
170
|
-
count_statement_list(exp[
|
171
|
+
count_statement_list(exp.children[1..-1].compact)
|
171
172
|
process_default(exp)
|
172
173
|
end
|
173
174
|
|
data/lib/reek/version.rb
CHANGED
data/spec/factories/factories.rb
CHANGED
@@ -11,6 +11,17 @@ FactoryGirl.define do
|
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
+
factory :method_context, class: Reek::Context::MethodContext do
|
15
|
+
skip_create
|
16
|
+
transient do
|
17
|
+
source 'def foo; end'
|
18
|
+
end
|
19
|
+
|
20
|
+
initialize_with do
|
21
|
+
new(nil, Reek::Source::SourceCode.from(source).syntax_tree)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
14
25
|
factory :smell_detector, class: Reek::Smells::SmellDetector do
|
15
26
|
skip_create
|
16
27
|
transient do
|
@@ -38,7 +38,31 @@ RSpec.describe Reek::Configuration::AppConfiguration do
|
|
38
38
|
end
|
39
39
|
|
40
40
|
describe '#from_map' do
|
41
|
-
|
41
|
+
let(:default_directive_value) do
|
42
|
+
{ 'IrresponsibleModule' => { 'enabled' => false } }
|
43
|
+
end
|
44
|
+
|
45
|
+
let(:directory_directives_value) do
|
46
|
+
{ 'spec/samples/three_clean_files' =>
|
47
|
+
{ 'UtilityFunction' => { 'enabled' => false } } }
|
48
|
+
end
|
49
|
+
|
50
|
+
let(:exclude_paths_value) do
|
51
|
+
['spec/samples/two_smelly_files',
|
52
|
+
'spec/samples/source_with_non_ruby_files']
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'properly sets the configuration from simple data structures' do
|
56
|
+
config = described_class.from_map(directory_directives: directory_directives_value,
|
57
|
+
default_directive: default_directive_value,
|
58
|
+
excluded_paths: exclude_paths_value)
|
59
|
+
|
60
|
+
expect(config.send(:excluded_paths)).to eq(expected_excluded_paths)
|
61
|
+
expect(config.send(:default_directive)).to eq(expected_default_directive)
|
62
|
+
expect(config.send(:directory_directives)).to eq(expected_directory_directives)
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'properly sets the configuration from native structures' do
|
42
66
|
config = described_class.from_map(directory_directives: expected_directory_directives,
|
43
67
|
default_directive: expected_default_directive,
|
44
68
|
excluded_paths: expected_excluded_paths)
|
@@ -51,17 +75,18 @@ RSpec.describe Reek::Configuration::AppConfiguration do
|
|
51
75
|
end
|
52
76
|
|
53
77
|
describe '#directive_for' do
|
78
|
+
let(:source_via) { 'spec/samples/three_clean_files/dummy.rb' }
|
79
|
+
|
54
80
|
context 'our source is in a directory for which we have a directive' do
|
55
81
|
let(:baz_config) { { Reek::Smells::IrresponsibleModule => { enabled: false } } }
|
56
82
|
let(:bang_config) { { Reek::Smells::Attribute => { enabled: true } } }
|
57
83
|
|
58
84
|
let(:directory_directives) do
|
59
85
|
{
|
60
|
-
|
61
|
-
|
86
|
+
'spec/samples/two_smelly_files' => baz_config,
|
87
|
+
'spec/samples/three_clean_files' => bang_config
|
62
88
|
}
|
63
89
|
end
|
64
|
-
let(:source_via) { 'foo/bar/bang/dummy.rb' }
|
65
90
|
|
66
91
|
it 'returns the corresponding directive' do
|
67
92
|
configuration = described_class.from_map directory_directives: directory_directives
|
@@ -70,22 +95,16 @@ RSpec.describe Reek::Configuration::AppConfiguration do
|
|
70
95
|
end
|
71
96
|
|
72
97
|
context 'our source is not in a directory for which we have a directive' do
|
73
|
-
let(:
|
74
|
-
{ Reek::Smells::IrresponsibleModule => { enabled: false } }
|
75
|
-
end
|
98
|
+
let(:default_directive) { { Reek::Smells::IrresponsibleModule => { enabled: false } } }
|
76
99
|
let(:attribute_config) { { Reek::Smells::Attribute => { enabled: false } } }
|
77
|
-
let(:default_directive) do
|
78
|
-
irresponsible_module_config
|
79
|
-
end
|
80
100
|
let(:directory_directives) do
|
81
|
-
{
|
101
|
+
{ 'spec/samples/two_smelly_files' => attribute_config }
|
82
102
|
end
|
83
|
-
let(:source_via) { 'foo/bar/bang/dummy.rb' }
|
84
103
|
|
85
104
|
it 'returns the default directive' do
|
86
105
|
configuration = described_class.from_map directory_directives: directory_directives,
|
87
106
|
default_directive: default_directive
|
88
|
-
expect(configuration.directive_for(source_via)).to eq(
|
107
|
+
expect(configuration.directive_for(source_via)).to eq(default_directive)
|
89
108
|
end
|
90
109
|
end
|
91
110
|
end
|
@@ -118,7 +118,7 @@ RSpec.describe Reek::Context::CodeContext do
|
|
118
118
|
end
|
119
119
|
|
120
120
|
it "yields the method's full AST" do
|
121
|
-
ctx.each_node(:def, []) { |exp| expect(exp
|
121
|
+
ctx.each_node(:def, []) { |exp| expect(exp.children.first).to eq(:calloo) }
|
122
122
|
end
|
123
123
|
|
124
124
|
context 'pruning the traversal' do
|
@@ -49,27 +49,9 @@ RSpec.describe Reek::Smells::NestedIterators do
|
|
49
49
|
it 'detects an iterator with an empty block' do
|
50
50
|
src = <<-EOS
|
51
51
|
def foo
|
52
|
-
bar
|
53
|
-
|
54
|
-
|
55
|
-
expect(src).to reek_of(:NestedIterators)
|
56
|
-
end
|
57
|
-
|
58
|
-
it 'should report nested iterators only once per method' do
|
59
|
-
src = <<-EOS
|
60
|
-
def bad(fred)
|
61
|
-
@fred.each {|item| item.each {|part| @joe.send} }
|
62
|
-
@jim.each {|ting| ting.each {|piece| @hal.send} }
|
63
|
-
end
|
64
|
-
EOS
|
65
|
-
expect(src).to reek_of(:NestedIterators)
|
66
|
-
end
|
67
|
-
|
68
|
-
it 'reports nested iterators only once per method even if levels are different' do
|
69
|
-
src = <<-EOS
|
70
|
-
def bad(fred)
|
71
|
-
@fred.each {|item| item.each {|part| part.foo} }
|
72
|
-
@jim.each {|ting| ting.each {|piece| piece.each {|atom| atom.foo } } }
|
52
|
+
bar do |bar|
|
53
|
+
baz {|baz| }
|
54
|
+
end
|
73
55
|
end
|
74
56
|
EOS
|
75
57
|
expect(src).to reek_of(:NestedIterators)
|
@@ -103,7 +85,7 @@ RSpec.describe Reek::Smells::NestedIterators do
|
|
103
85
|
expect(src).to reek_of(:NestedIterators, count: 3)
|
104
86
|
end
|
105
87
|
|
106
|
-
it 'handles the case where super
|
88
|
+
it 'handles the case where super receives a block' do
|
107
89
|
src = <<-EOS
|
108
90
|
def super_call_with_block
|
109
91
|
super do |k|
|
@@ -115,7 +97,7 @@ RSpec.describe Reek::Smells::NestedIterators do
|
|
115
97
|
expect(src).to reek_of(:NestedIterators)
|
116
98
|
end
|
117
99
|
|
118
|
-
it 'handles the case where super
|
100
|
+
it 'handles the case where super receives a block and arguments' do
|
119
101
|
src = <<-EOS
|
120
102
|
def super_call_with_block
|
121
103
|
super(foo) do |k|
|
@@ -127,6 +109,71 @@ RSpec.describe Reek::Smells::NestedIterators do
|
|
127
109
|
expect(src).to reek_of(:NestedIterators)
|
128
110
|
end
|
129
111
|
|
112
|
+
describe 'examine_context / warnings' do
|
113
|
+
let(:detector) { build(:smell_detector, smell_type: :NestedIterators) }
|
114
|
+
|
115
|
+
it 'reports correctly' do
|
116
|
+
source = <<-EOS
|
117
|
+
def foo
|
118
|
+
bar do |bar|
|
119
|
+
baz {|baz| }
|
120
|
+
end
|
121
|
+
end
|
122
|
+
EOS
|
123
|
+
warnings = detector.examine_context(build(:method_context, source: source))
|
124
|
+
warning = warnings.first
|
125
|
+
|
126
|
+
expect(warning.smell_category).to eq(Reek::Smells::NestedIterators.smell_category)
|
127
|
+
expect(warning.smell_type).to eq(Reek::Smells::NestedIterators.smell_type)
|
128
|
+
expect(warning.parameters[:name]).to eq('foo')
|
129
|
+
expect(warning.lines).to eq([3])
|
130
|
+
end
|
131
|
+
|
132
|
+
it 'should report nested iterators only once per method' do
|
133
|
+
source = <<-EOS
|
134
|
+
def bad(fred)
|
135
|
+
@fred.each {|item| item.each {|part| @joe.send} }
|
136
|
+
@jim.each {|ting| ting.each {|piece| @hal.send} }
|
137
|
+
end
|
138
|
+
EOS
|
139
|
+
|
140
|
+
warnings = detector.examine_context(build(:method_context, source: source))
|
141
|
+
expect(warnings.size).to eq(1)
|
142
|
+
warning = warnings.first
|
143
|
+
expect(warning.parameters[:name]).to eq('bad')
|
144
|
+
end
|
145
|
+
|
146
|
+
it 'reports nested iterators only once per method even if levels are different' do
|
147
|
+
source = <<-EOS
|
148
|
+
def bad(fred)
|
149
|
+
@fred.each {|item| item.each {|part| part.foo} }
|
150
|
+
@jim.each {|ting| ting.each {|piece| piece.each {|atom| atom.foo } } }
|
151
|
+
end
|
152
|
+
EOS
|
153
|
+
warnings = detector.examine_context(build(:method_context, source: source))
|
154
|
+
expect(warnings.size).to eq(1)
|
155
|
+
warning = warnings.first
|
156
|
+
|
157
|
+
expect(warning.parameters[:name]).to eq('bad')
|
158
|
+
expect(warning.lines).to eq([3])
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
describe 'iterators without block arguments' do
|
163
|
+
it 'does not count those iterators' do
|
164
|
+
source = <<-EOS
|
165
|
+
def foo
|
166
|
+
before do
|
167
|
+
item.each do |part|
|
168
|
+
puts part
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
EOS
|
173
|
+
expect(source).not_to reek_of(:NestedIterators)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
130
177
|
context 'when the allowed nesting depth is 3' do
|
131
178
|
let(:configuration) do
|
132
179
|
config = { Reek::Smells::NestedIterators =>
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: reek
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kevin Rutherford
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date: 2015-09-
|
14
|
+
date: 2015-09-28 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: parser
|