rubocop 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +8 -5
- data/config/default.yml +52 -3
- data/lib/rubocop.rb +8 -0
- data/lib/rubocop/cli/command/auto_genenerate_config.rb +1 -1
- data/lib/rubocop/comment_config.rb +1 -1
- data/lib/rubocop/cop/bundler/duplicated_gem.rb +23 -3
- data/lib/rubocop/cop/commissioner.rb +9 -9
- data/lib/rubocop/cop/corrector.rb +3 -1
- data/lib/rubocop/cop/force.rb +1 -1
- data/lib/rubocop/cop/layout/def_end_alignment.rb +1 -1
- data/lib/rubocop/cop/layout/empty_lines_around_access_modifier.rb +1 -0
- data/lib/rubocop/cop/layout/extra_spacing.rb +1 -2
- data/lib/rubocop/cop/layout/trailing_whitespace.rb +1 -1
- data/lib/rubocop/cop/lint/debugger.rb +2 -3
- data/lib/rubocop/cop/lint/duplicate_regexp_character_class_element.rb +77 -0
- data/lib/rubocop/cop/lint/empty_block.rb +46 -0
- data/lib/rubocop/cop/lint/flip_flop.rb +8 -2
- data/lib/rubocop/cop/lint/literal_in_interpolation.rb +17 -3
- data/lib/rubocop/cop/lint/number_conversion.rb +46 -13
- data/lib/rubocop/cop/lint/out_of_range_regexp_ref.rb +27 -8
- data/lib/rubocop/cop/lint/to_enum_arguments.rb +95 -0
- data/lib/rubocop/cop/lint/unmodified_reduce_accumulator.rb +185 -0
- data/lib/rubocop/cop/lint/useless_access_modifier.rb +2 -2
- data/lib/rubocop/cop/mixin/line_length_help.rb +1 -1
- data/lib/rubocop/cop/naming/predicate_name.rb +2 -1
- data/lib/rubocop/cop/offense.rb +3 -3
- data/lib/rubocop/cop/style/arguments_forwarding.rb +142 -0
- data/lib/rubocop/cop/style/document_dynamic_eval_definition.rb +67 -0
- data/lib/rubocop/cop/style/multiple_comparison.rb +54 -7
- data/lib/rubocop/cop/style/redundant_regexp_character_class.rb +7 -1
- data/lib/rubocop/cop/style/semicolon.rb +3 -0
- data/lib/rubocop/cop/style/swap_values.rb +108 -0
- data/lib/rubocop/cop/team.rb +6 -1
- data/lib/rubocop/cop/util.rb +1 -1
- data/lib/rubocop/ext/regexp_node.rb +10 -7
- data/lib/rubocop/ext/regexp_parser.rb +77 -0
- data/lib/rubocop/formatter/formatter_set.rb +1 -1
- data/lib/rubocop/magic_comment.rb +2 -2
- data/lib/rubocop/rspec/shared_contexts.rb +4 -0
- data/lib/rubocop/version.rb +1 -1
- metadata +13 -5
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Lint
|
6
|
+
# This cop checks for blocks without a body.
|
7
|
+
# Such empty blocks are typically an oversight or we should provide a comment
|
8
|
+
# be clearer what we're aiming for.
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# # bad
|
12
|
+
# items.each { |item| }
|
13
|
+
#
|
14
|
+
# # good
|
15
|
+
# items.each { |item| puts item }
|
16
|
+
#
|
17
|
+
# @example AllowComments: true (default)
|
18
|
+
# # good
|
19
|
+
# items.each do |item|
|
20
|
+
# # TODO: implement later (inner comment)
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# items.each { |item| } # TODO: implement later (inline comment)
|
24
|
+
#
|
25
|
+
# @example AllowComments: false
|
26
|
+
# # bad
|
27
|
+
# items.each do |item|
|
28
|
+
# # TODO: implement later (inner comment)
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# items.each { |item| } # TODO: implement later (inline comment)
|
32
|
+
#
|
33
|
+
class EmptyBlock < Base
|
34
|
+
MSG = 'Empty block detected.'
|
35
|
+
|
36
|
+
def on_block(node)
|
37
|
+
return if node.body
|
38
|
+
return if cop_config['AllowComments'] &&
|
39
|
+
processed_source.contains_comment?(node.source_range)
|
40
|
+
|
41
|
+
add_offense(node)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -3,8 +3,14 @@
|
|
3
3
|
module RuboCop
|
4
4
|
module Cop
|
5
5
|
module Lint
|
6
|
-
# This cop looks for uses of flip-flop operator
|
7
|
-
#
|
6
|
+
# This cop looks for uses of flip-flop operator
|
7
|
+
# based on the Ruby Style Guide.
|
8
|
+
#
|
9
|
+
# Here is the history of flip-flops in Ruby.
|
10
|
+
# flip-flop operator is deprecated in Ruby 2.6.0 and
|
11
|
+
# the deprecation has been reverted by Ruby 2.7.0 and
|
12
|
+
# backported to Ruby 2.6.
|
13
|
+
# See: https://bugs.ruby-lang.org/issues/5400
|
8
14
|
#
|
9
15
|
# @example
|
10
16
|
# # bad
|
@@ -31,12 +31,18 @@ module RuboCop
|
|
31
31
|
return if special_keyword?(final_node)
|
32
32
|
return unless prints_as_self?(final_node)
|
33
33
|
|
34
|
+
# %W and %I split the content into words before expansion
|
35
|
+
# treating each interpolation as a word component, so
|
36
|
+
# interpolation should not be removed if the expanded value
|
37
|
+
# contains a space character.
|
38
|
+
expanded_value = autocorrected_value(final_node)
|
39
|
+
return if in_array_percent_literal?(begin_node) &&
|
40
|
+
/\s/.match?(expanded_value)
|
41
|
+
|
34
42
|
add_offense(final_node) do |corrector|
|
35
43
|
return if final_node.dstr_type? # nested, fixed in next iteration
|
36
44
|
|
37
|
-
|
38
|
-
|
39
|
-
corrector.replace(final_node.parent, value)
|
45
|
+
corrector.replace(final_node.parent, expanded_value)
|
40
46
|
end
|
41
47
|
end
|
42
48
|
|
@@ -92,6 +98,14 @@ module RuboCop
|
|
92
98
|
(COMPOSITE.include?(node.type) &&
|
93
99
|
node.children.all? { |child| prints_as_self?(child) })
|
94
100
|
end
|
101
|
+
|
102
|
+
def in_array_percent_literal?(node)
|
103
|
+
parent = node.parent
|
104
|
+
return false unless parent.dstr_type? || parent.dsym_type?
|
105
|
+
|
106
|
+
grandparent = parent.parent
|
107
|
+
grandparent&.array_type? && grandparent&.percent_literal?
|
108
|
+
end
|
95
109
|
end
|
96
110
|
end
|
97
111
|
end
|
@@ -7,6 +7,17 @@ module RuboCop
|
|
7
7
|
# number conversion can cause unexpected error if auto type conversion
|
8
8
|
# fails. Cop prefer parsing with number class instead.
|
9
9
|
#
|
10
|
+
# Conversion with `Integer`, `Float`, etc. will raise an `ArgumentError`
|
11
|
+
# if given input that is not numeric (eg. an empty string), whereas
|
12
|
+
# `to_i`, etc. will try to convert regardless of input (`''.to_i => 0`).
|
13
|
+
# As such, this cop is disabled by default because it's not necessarily
|
14
|
+
# always correct to raise if a value is not numeric.
|
15
|
+
#
|
16
|
+
# NOTE: Some values cannot be converted properly using one of the `Kernel`
|
17
|
+
# method (for instance, `Time` and `DateTime` values are allowed by this
|
18
|
+
# cop by default). Similarly, Rails' duration methods do not work well
|
19
|
+
# with `Integer()` and can be ignored with `IgnoredMethods`.
|
20
|
+
#
|
10
21
|
# @example
|
11
22
|
#
|
12
23
|
# # bad
|
@@ -20,8 +31,19 @@ module RuboCop
|
|
20
31
|
# Integer('10', 10)
|
21
32
|
# Float('10.2')
|
22
33
|
# Complex('10')
|
34
|
+
#
|
35
|
+
# @example IgnoredMethods: [minutes]
|
36
|
+
#
|
37
|
+
# # good
|
38
|
+
# 10.minutes.to_i
|
39
|
+
#
|
40
|
+
# @example IgnoredClasses: [Time, DateTime] (default)
|
41
|
+
#
|
42
|
+
# # good
|
43
|
+
# Time.now.to_datetime.to_i
|
23
44
|
class NumberConversion < Base
|
24
45
|
extend AutoCorrector
|
46
|
+
include IgnoredMethods
|
25
47
|
|
26
48
|
CONVERSION_METHOD_CLASS_MAPPING = {
|
27
49
|
to_i: "#{Integer.name}(%<number_object>s, 10)",
|
@@ -38,13 +60,9 @@ module RuboCop
|
|
38
60
|
(send $_ ${:to_i :to_f :to_c})
|
39
61
|
PATTERN
|
40
62
|
|
41
|
-
def_node_matcher :datetime?, <<~PATTERN
|
42
|
-
(send (const {nil? (cbase)} {:Time :DateTime}) ...)
|
43
|
-
PATTERN
|
44
|
-
|
45
63
|
def on_send(node)
|
46
64
|
to_method(node) do |receiver, to_method|
|
47
|
-
next if receiver.nil? ||
|
65
|
+
next if receiver.nil? || ignore_receiver?(receiver)
|
48
66
|
|
49
67
|
message = format(
|
50
68
|
MSG,
|
@@ -60,18 +78,33 @@ module RuboCop
|
|
60
78
|
|
61
79
|
private
|
62
80
|
|
63
|
-
def
|
64
|
-
|
65
|
-
|
66
|
-
|
81
|
+
def correct_method(node, receiver)
|
82
|
+
format(CONVERSION_METHOD_CLASS_MAPPING[node.method_name],
|
83
|
+
number_object: receiver.source)
|
84
|
+
end
|
67
85
|
|
68
|
-
|
86
|
+
def ignore_receiver?(receiver)
|
87
|
+
if receiver.send_type? && ignored_method?(receiver.method_name)
|
88
|
+
true
|
89
|
+
elsif (receiver = top_receiver(receiver))
|
90
|
+
receiver.const_type? && ignored_class?(receiver.const_name)
|
91
|
+
else
|
92
|
+
false
|
69
93
|
end
|
70
94
|
end
|
71
95
|
|
72
|
-
def
|
73
|
-
|
74
|
-
|
96
|
+
def top_receiver(node)
|
97
|
+
receiver = node
|
98
|
+
receiver = receiver.receiver until receiver.receiver.nil?
|
99
|
+
receiver
|
100
|
+
end
|
101
|
+
|
102
|
+
def ignored_classes
|
103
|
+
cop_config.fetch('IgnoredClasses', [])
|
104
|
+
end
|
105
|
+
|
106
|
+
def ignored_class?(name)
|
107
|
+
ignored_classes.include?(name.to_s)
|
75
108
|
end
|
76
109
|
end
|
77
110
|
end
|
@@ -19,7 +19,7 @@ module RuboCop
|
|
19
19
|
# puts $1 # => foo
|
20
20
|
#
|
21
21
|
class OutOfRangeRegexpRef < Base
|
22
|
-
MSG = '
|
22
|
+
MSG = '$%<backref>s is out of range (%<count>s regexp capture %<group>s detected).'
|
23
23
|
|
24
24
|
REGEXP_RECEIVER_METHODS = %i[=~ === match].to_set.freeze
|
25
25
|
REGEXP_ARGUMENT_METHODS = %i[=~ match grep gsub gsub! sub sub! [] slice slice! index rindex
|
@@ -35,14 +35,13 @@ module RuboCop
|
|
35
35
|
check_regexp(node.children.first)
|
36
36
|
end
|
37
37
|
|
38
|
-
def
|
38
|
+
def after_send(node)
|
39
39
|
@valid_ref = nil
|
40
40
|
|
41
|
-
if node
|
42
|
-
check_regexp(node.receiver)
|
43
|
-
elsif node.first_argument&.regexp_type? \
|
44
|
-
&& REGEXP_ARGUMENT_METHODS.include?(node.method_name)
|
41
|
+
if regexp_first_argument?(node)
|
45
42
|
check_regexp(node.first_argument)
|
43
|
+
elsif regexp_receiver?(node)
|
44
|
+
check_regexp(node.receiver)
|
46
45
|
end
|
47
46
|
end
|
48
47
|
|
@@ -56,9 +55,16 @@ module RuboCop
|
|
56
55
|
|
57
56
|
def on_nth_ref(node)
|
58
57
|
backref, = *node
|
59
|
-
return if @valid_ref.nil?
|
58
|
+
return if @valid_ref.nil? || backref <= @valid_ref
|
59
|
+
|
60
|
+
message = format(
|
61
|
+
MSG,
|
62
|
+
backref: backref,
|
63
|
+
count: @valid_ref.zero? ? 'no' : @valid_ref,
|
64
|
+
group: @valid_ref == 1 ? 'group' : 'groups'
|
65
|
+
)
|
60
66
|
|
61
|
-
add_offense(node
|
67
|
+
add_offense(node, message: message)
|
62
68
|
end
|
63
69
|
|
64
70
|
private
|
@@ -73,6 +79,19 @@ module RuboCop
|
|
73
79
|
node.each_capture(named: false).count
|
74
80
|
end
|
75
81
|
end
|
82
|
+
|
83
|
+
def regexp_first_argument?(send_node)
|
84
|
+
send_node.first_argument&.regexp_type? \
|
85
|
+
&& REGEXP_ARGUMENT_METHODS.include?(send_node.method_name)
|
86
|
+
end
|
87
|
+
|
88
|
+
def regexp_receiver?(send_node)
|
89
|
+
send_node.receiver&.regexp_type?
|
90
|
+
end
|
91
|
+
|
92
|
+
def nth_ref_receiver?(send_node)
|
93
|
+
send_node.receiver&.nth_ref_type?
|
94
|
+
end
|
76
95
|
end
|
77
96
|
end
|
78
97
|
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Lint
|
6
|
+
# This cop ensures that `to_enum`/`enum_for`, called for the current method,
|
7
|
+
# has correct arguments.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# # bad
|
11
|
+
# def method(x, y = 1)
|
12
|
+
# return to_enum(__method__, x) # `y` is missing
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# # good
|
16
|
+
# def method(x, y = 1)
|
17
|
+
# return to_enum(__method__, x, y)
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# # bad
|
21
|
+
# def method(required:)
|
22
|
+
# return to_enum(:method, required: something) # `required` has incorrect value
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# # good
|
26
|
+
# def method(required:)
|
27
|
+
# return to_enum(:method, required: required)
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
class ToEnumArguments < Base
|
31
|
+
MSG = 'Ensure you correctly provided all the arguments.'
|
32
|
+
|
33
|
+
RESTRICT_ON_SEND = %i[to_enum enum_for].freeze
|
34
|
+
|
35
|
+
def_node_matcher :enum_conversion_call?, <<~PATTERN
|
36
|
+
(send {nil? self} {:to_enum :enum_for} $_ $...)
|
37
|
+
PATTERN
|
38
|
+
|
39
|
+
def_node_matcher :method_name?, <<~PATTERN
|
40
|
+
{(send nil? :__method__) (sym %1)}
|
41
|
+
PATTERN
|
42
|
+
|
43
|
+
def_node_matcher :passing_keyword_arg?, <<~PATTERN
|
44
|
+
(pair (sym %1) (lvar %1))
|
45
|
+
PATTERN
|
46
|
+
|
47
|
+
def on_send(node)
|
48
|
+
def_node = node.each_ancestor(:def, :defs).first
|
49
|
+
return unless def_node
|
50
|
+
|
51
|
+
enum_conversion_call?(node) do |method_node, arguments|
|
52
|
+
add_offense(node) unless method_name?(method_node, def_node.method_name) &&
|
53
|
+
arguments_match?(arguments, def_node)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def arguments_match?(arguments, def_node)
|
60
|
+
index = 0
|
61
|
+
|
62
|
+
def_node.arguments.reject(&:blockarg_type?).all? do |def_arg|
|
63
|
+
send_arg = arguments[index]
|
64
|
+
case def_arg.type
|
65
|
+
when :arg, :restarg, :optarg
|
66
|
+
index += 1
|
67
|
+
end
|
68
|
+
|
69
|
+
send_arg && argument_match?(send_arg, def_arg)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
74
|
+
def argument_match?(send_arg, def_arg)
|
75
|
+
def_arg_name = def_arg.children[0]
|
76
|
+
|
77
|
+
case def_arg.type
|
78
|
+
when :arg, :restarg
|
79
|
+
send_arg.source == def_arg.source
|
80
|
+
when :optarg
|
81
|
+
send_arg.source == def_arg_name.to_s
|
82
|
+
when :kwoptarg, :kwarg
|
83
|
+
send_arg.hash_type? &&
|
84
|
+
send_arg.pairs.any? { |pair| passing_keyword_arg?(pair, def_arg_name) }
|
85
|
+
when :kwrestarg
|
86
|
+
send_arg.each_child_node(:kwsplat).any? { |child| child.source == def_arg.source }
|
87
|
+
when :forward_arg
|
88
|
+
send_arg.forwarded_args_type?
|
89
|
+
end
|
90
|
+
end
|
91
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,185 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Lint
|
6
|
+
# Looks for `reduce` or `inject` blocks where the value returned (implicitly or
|
7
|
+
# explicitly) does not include the accumulator. A block is considered valid as
|
8
|
+
# long as at least one return value includes the accumulator.
|
9
|
+
#
|
10
|
+
# If the accumulator is not included in the return value, then the entire
|
11
|
+
# block will just return a transformation of the last element value, and
|
12
|
+
# could be rewritten as such without a loop.
|
13
|
+
#
|
14
|
+
# Also catches instances where an index of the accumulator is returned, as
|
15
|
+
# this may change the type of object being retained.
|
16
|
+
#
|
17
|
+
# NOTE: For the purpose of reducing false positives, this cop only flags
|
18
|
+
# returns in `reduce` blocks where the element is the only variable in
|
19
|
+
# the expression (since we will not be able to tell what other variables
|
20
|
+
# relate to via static analysis).
|
21
|
+
#
|
22
|
+
# @example
|
23
|
+
#
|
24
|
+
# # bad
|
25
|
+
# (1..4).reduce(0) do |acc, el|
|
26
|
+
# el * 2
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# # bad, may raise a NoMethodError after the first iteration
|
30
|
+
# %w(a b c).reduce({}) do |acc, letter|
|
31
|
+
# acc[letter] = true
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# # good
|
35
|
+
# (1..4).reduce(0) do |acc, el|
|
36
|
+
# acc + el * 2
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# # good, element is returned but modified using the accumulator
|
40
|
+
# values.reduce do |acc, el|
|
41
|
+
# el << acc
|
42
|
+
# el
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# # good, returns the accumulator instead of the index
|
46
|
+
# %w(a b c).reduce({}) do |acc, letter|
|
47
|
+
# acc[letter] = true
|
48
|
+
# acc
|
49
|
+
# end
|
50
|
+
#
|
51
|
+
# # good, at least one branch returns the accumulator
|
52
|
+
# values.reduce(nil) do |result, value|
|
53
|
+
# break result if something?
|
54
|
+
# value
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# # ignored as the return value cannot be determined
|
58
|
+
# enum.reduce do |acc, el|
|
59
|
+
# x = foo(acc, el)
|
60
|
+
# bar(x)
|
61
|
+
# end
|
62
|
+
class UnmodifiedReduceAccumulator < Base
|
63
|
+
MSG = 'Ensure the accumulator `%<accum>s` will be modified by `%<method>s`.'
|
64
|
+
MSG_INDEX = 'Do not return an element of the accumulator in `%<method>s`.'
|
65
|
+
|
66
|
+
def_node_matcher :reduce_with_block?, <<~PATTERN
|
67
|
+
(block (send _recv {:reduce :inject} ...) (args arg+) ...)
|
68
|
+
PATTERN
|
69
|
+
|
70
|
+
def_node_matcher :accumulator_index?, <<~PATTERN
|
71
|
+
(send (lvar %1) {:[] :[]=} ...)
|
72
|
+
PATTERN
|
73
|
+
|
74
|
+
def_node_search :element_modified?, <<~PATTERN
|
75
|
+
{
|
76
|
+
(send _receiver !{:[] :[]=} <`(lvar %1) `_ ...>) # method(el, ...)
|
77
|
+
(send (lvar %1) _message <{ivar gvar cvar lvar send} ...>) # el.method(...)
|
78
|
+
(lvasgn %1 _) # el = ...
|
79
|
+
(%RuboCop::AST::Node::SHORTHAND_ASSIGNMENTS (lvasgn %1) ... _) # el += ...
|
80
|
+
}
|
81
|
+
PATTERN
|
82
|
+
|
83
|
+
def_node_matcher :lvar_used?, <<~PATTERN
|
84
|
+
{
|
85
|
+
(lvar %1)
|
86
|
+
(lvasgn %1 ...)
|
87
|
+
(send (lvar %1) :<< ...)
|
88
|
+
(dstr (begin (lvar %1)))
|
89
|
+
(%RuboCop::AST::Node::SHORTHAND_ASSIGNMENTS (lvasgn %1))
|
90
|
+
}
|
91
|
+
PATTERN
|
92
|
+
|
93
|
+
def_node_search :expression_values, <<~PATTERN
|
94
|
+
{
|
95
|
+
(%RuboCop::AST::Node::VARIABLES $_)
|
96
|
+
(%RuboCop::AST::Node::EQUALS_ASSIGNMENTS $_ ...)
|
97
|
+
(send (%RuboCop::AST::Node::VARIABLES $_) :<< ...)
|
98
|
+
$(send _ _)
|
99
|
+
(dstr (begin {(%RuboCop::AST::Node::VARIABLES $_)}))
|
100
|
+
(%RuboCop::AST::Node::SHORTHAND_ASSIGNMENTS (%RuboCop::AST::Node::EQUALS_ASSIGNMENTS $_) ...)
|
101
|
+
}
|
102
|
+
PATTERN
|
103
|
+
|
104
|
+
def on_block(node)
|
105
|
+
return unless reduce_with_block?(node)
|
106
|
+
|
107
|
+
check_return_values(node)
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
# Return values in a block are either the value given to next,
|
113
|
+
# the last line of a multiline block, or the only line of the block
|
114
|
+
def return_values(block_body_node)
|
115
|
+
nodes = [block_body_node.begin_type? ? block_body_node.child_nodes.last : block_body_node]
|
116
|
+
|
117
|
+
block_body_node.each_descendant(:next, :break) do |n|
|
118
|
+
# Ignore `next`/`break` inside an inner block
|
119
|
+
next if n.each_ancestor(:block).first != block_body_node.parent
|
120
|
+
next unless n.first_argument
|
121
|
+
|
122
|
+
nodes << n.first_argument
|
123
|
+
end
|
124
|
+
|
125
|
+
nodes
|
126
|
+
end
|
127
|
+
|
128
|
+
def check_return_values(block_node)
|
129
|
+
return_values = return_values(block_node.body)
|
130
|
+
accumulator_name = block_arg_name(block_node, 0)
|
131
|
+
element_name = block_arg_name(block_node, 1)
|
132
|
+
message_opts = { method: block_node.method_name, accum: accumulator_name }
|
133
|
+
|
134
|
+
if (node = returned_accumulator_index(return_values, accumulator_name))
|
135
|
+
add_offense(node, message: format(MSG_INDEX, message_opts))
|
136
|
+
elsif potential_offense?(return_values, block_node.body, element_name, accumulator_name)
|
137
|
+
return_values.each do |return_val|
|
138
|
+
unless acceptable_return?(return_val, element_name)
|
139
|
+
add_offense(return_val, message: format(MSG, message_opts))
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def block_arg_name(node, index)
|
146
|
+
node.arguments[index].node_parts[0]
|
147
|
+
end
|
148
|
+
|
149
|
+
# Look for an index of the accumulator being returned
|
150
|
+
# This is always an offense, in order to try to catch potential exceptions
|
151
|
+
# due to type mismatches
|
152
|
+
def returned_accumulator_index(return_values, accumulator_name)
|
153
|
+
return_values.detect { |val| accumulator_index?(val, accumulator_name) }
|
154
|
+
end
|
155
|
+
|
156
|
+
def potential_offense?(return_values, block_body, element_name, accumulator_name)
|
157
|
+
!(element_modified?(block_body, element_name) ||
|
158
|
+
returns_accumulator_anywhere?(return_values, accumulator_name))
|
159
|
+
end
|
160
|
+
|
161
|
+
# If the accumulator is used in any return value, the node is acceptable since
|
162
|
+
# the accumulator has a chance to change each iteration
|
163
|
+
def returns_accumulator_anywhere?(return_values, accumulator_name)
|
164
|
+
return_values.any? { |node| lvar_used?(node, accumulator_name) }
|
165
|
+
end
|
166
|
+
|
167
|
+
# Determine if a return value is acceptable for the purposes of this cop
|
168
|
+
# If it is an expression containing the accumulator, it is acceptable
|
169
|
+
# Otherwise, it is only unacceptable if it contains the iterated element, since we
|
170
|
+
# otherwise do not have enough information to prevent false positives.
|
171
|
+
def acceptable_return?(return_val, element_name)
|
172
|
+
vars = expression_values(return_val).uniq
|
173
|
+
return true if vars.none? || (vars - [element_name]).any?
|
174
|
+
|
175
|
+
false
|
176
|
+
end
|
177
|
+
|
178
|
+
# Exclude `begin` nodes inside a `dstr` from being collected by `return_values`
|
179
|
+
def allowed_type?(parent_node)
|
180
|
+
!parent_node.dstr_type?
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|