rubocop-performance 1.5.2 → 1.8.1
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/LICENSE.txt +1 -1
- data/README.md +5 -1
- data/config/default.yml +96 -13
- data/lib/rubocop/cop/mixin/regexp_metacharacter.rb +76 -0
- data/lib/rubocop/cop/mixin/sort_block.rb +28 -0
- data/lib/rubocop/cop/performance/ancestors_include.rb +48 -0
- data/lib/rubocop/cop/performance/big_decimal_with_numeric_argument.rb +45 -0
- data/lib/rubocop/cop/performance/bind_call.rb +77 -0
- data/lib/rubocop/cop/performance/caller.rb +5 -4
- data/lib/rubocop/cop/performance/case_when_splat.rb +18 -11
- data/lib/rubocop/cop/performance/casecmp.rb +17 -23
- data/lib/rubocop/cop/performance/chain_array_allocation.rb +5 -11
- data/lib/rubocop/cop/performance/collection_literal_in_loop.rb +140 -0
- data/lib/rubocop/cop/performance/compare_with_block.rb +12 -23
- data/lib/rubocop/cop/performance/count.rb +14 -17
- data/lib/rubocop/cop/performance/delete_prefix.rb +87 -0
- data/lib/rubocop/cop/performance/delete_suffix.rb +87 -0
- data/lib/rubocop/cop/performance/detect.rb +64 -32
- data/lib/rubocop/cop/performance/double_start_end_with.rb +18 -26
- data/lib/rubocop/cop/performance/end_with.rb +38 -25
- data/lib/rubocop/cop/performance/fixed_size.rb +2 -2
- data/lib/rubocop/cop/performance/flat_map.rb +21 -23
- data/lib/rubocop/cop/performance/inefficient_hash_search.rb +14 -15
- data/lib/rubocop/cop/performance/io_readlines.rb +116 -0
- data/lib/rubocop/cop/performance/open_struct.rb +3 -3
- data/lib/rubocop/cop/performance/range_include.rb +15 -12
- data/lib/rubocop/cop/performance/redundant_block_call.rb +14 -9
- data/lib/rubocop/cop/performance/redundant_match.rb +13 -8
- data/lib/rubocop/cop/performance/redundant_merge.rb +36 -23
- data/lib/rubocop/cop/performance/redundant_sort_block.rb +43 -0
- data/lib/rubocop/cop/performance/redundant_string_chars.rb +133 -0
- data/lib/rubocop/cop/performance/regexp_match.rb +32 -32
- data/lib/rubocop/cop/performance/reverse_each.rb +10 -5
- data/lib/rubocop/cop/performance/reverse_first.rb +72 -0
- data/lib/rubocop/cop/performance/size.rb +41 -43
- data/lib/rubocop/cop/performance/sort_reverse.rb +45 -0
- data/lib/rubocop/cop/performance/squeeze.rb +66 -0
- data/lib/rubocop/cop/performance/start_with.rb +38 -28
- data/lib/rubocop/cop/performance/string_include.rb +55 -0
- data/lib/rubocop/cop/performance/string_replacement.rb +25 -36
- data/lib/rubocop/cop/performance/sum.rb +134 -0
- data/lib/rubocop/cop/performance/times_map.rb +12 -19
- data/lib/rubocop/cop/performance/unfreeze_string.rb +4 -8
- data/lib/rubocop/cop/performance/uri_default_parser.rb +7 -13
- data/lib/rubocop/cop/performance_cops.rb +17 -0
- data/lib/rubocop/performance/inject.rb +1 -1
- data/lib/rubocop/performance/version.rb +1 -1
- metadata +41 -11
@@ -3,8 +3,11 @@
|
|
3
3
|
module RuboCop
|
4
4
|
module Cop
|
5
5
|
module Performance
|
6
|
-
# This cop identifies unnecessary use of a regex where `String#end_with?`
|
7
|
-
#
|
6
|
+
# This cop identifies unnecessary use of a regex where `String#end_with?` would suffice.
|
7
|
+
#
|
8
|
+
# This cop has `SafeMultiline` configuration option that `true` by default because
|
9
|
+
# `end$` is unsafe as it will behave incompatible with `end_with?`
|
10
|
+
# for receiver is multiline string.
|
8
11
|
#
|
9
12
|
# @example
|
10
13
|
# # bad
|
@@ -17,44 +20,54 @@ module RuboCop
|
|
17
20
|
#
|
18
21
|
# # good
|
19
22
|
# 'abc'.end_with?('bc')
|
20
|
-
|
23
|
+
#
|
24
|
+
# @example SafeMultiline: true (default)
|
25
|
+
#
|
26
|
+
# # good
|
27
|
+
# 'abc'.match?(/bc$/)
|
28
|
+
# /bc$/.match?('abc')
|
29
|
+
# 'abc' =~ /bc$/
|
30
|
+
# /bc$/ =~ 'abc'
|
31
|
+
# 'abc'.match(/bc$/)
|
32
|
+
# /bc$/.match('abc')
|
33
|
+
#
|
34
|
+
# @example SafeMultiline: false
|
35
|
+
#
|
36
|
+
# # bad
|
37
|
+
# 'abc'.match?(/bc$/)
|
38
|
+
# /bc$/.match?('abc')
|
39
|
+
# 'abc' =~ /bc$/
|
40
|
+
# /bc$/ =~ 'abc'
|
41
|
+
# 'abc'.match(/bc$/)
|
42
|
+
# /bc$/.match('abc')
|
43
|
+
#
|
44
|
+
class EndWith < Base
|
45
|
+
include RegexpMetacharacter
|
46
|
+
extend AutoCorrector
|
47
|
+
|
21
48
|
MSG = 'Use `String#end_with?` instead of a regex match anchored to ' \
|
22
49
|
'the end of the string.'
|
23
|
-
SINGLE_QUOTE = "'"
|
24
50
|
|
25
|
-
def_node_matcher :redundant_regex?,
|
51
|
+
def_node_matcher :redundant_regex?, <<~PATTERN
|
26
52
|
{(send $!nil? {:match :=~ :match?} (regexp (str $#literal_at_end?) (regopt)))
|
27
53
|
(send (regexp (str $#literal_at_end?) (regopt)) {:match :match?} $_)
|
28
54
|
(match-with-lvasgn (regexp (str $#literal_at_end?) (regopt)) $_)}
|
29
55
|
PATTERN
|
30
56
|
|
31
|
-
def literal_at_end?(regex_str)
|
32
|
-
# is this regexp 'literal' in the sense of only matching literal
|
33
|
-
# chars, rather than using metachars like . and * and so on?
|
34
|
-
# also, is it anchored at the end of the string?
|
35
|
-
regex_str =~ /\A(?:#{LITERAL_REGEX})+\\z\z/
|
36
|
-
end
|
37
|
-
|
38
57
|
def on_send(node)
|
39
|
-
return unless redundant_regex?(node)
|
40
|
-
|
41
|
-
add_offense(node)
|
42
|
-
end
|
43
|
-
alias on_match_with_lvasgn on_send
|
58
|
+
return unless (receiver, regex_str = redundant_regex?(node))
|
44
59
|
|
45
|
-
|
46
|
-
redundant_regex?(node) do |receiver, regex_str|
|
60
|
+
add_offense(node) do |corrector|
|
47
61
|
receiver, regex_str = regex_str, receiver if receiver.is_a?(String)
|
48
|
-
regex_str = regex_str
|
62
|
+
regex_str = drop_end_metacharacter(regex_str)
|
49
63
|
regex_str = interpret_string_escapes(regex_str)
|
50
64
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
corrector.replace(node.source_range, new_source)
|
55
|
-
end
|
65
|
+
new_source = "#{receiver.source}.end_with?(#{to_string_literal(regex_str)})"
|
66
|
+
|
67
|
+
corrector.replace(node.source_range, new_source)
|
56
68
|
end
|
57
69
|
end
|
70
|
+
alias on_match_with_lvasgn on_send
|
58
71
|
end
|
59
72
|
end
|
60
73
|
end
|
@@ -45,10 +45,10 @@ module RuboCop
|
|
45
45
|
# waldo = { a: corge, b: grault }
|
46
46
|
# waldo.size
|
47
47
|
#
|
48
|
-
class FixedSize <
|
48
|
+
class FixedSize < Base
|
49
49
|
MSG = 'Do not compute the size of statically sized objects.'
|
50
50
|
|
51
|
-
def_node_matcher :counter,
|
51
|
+
def_node_matcher :counter, <<~MATCHER
|
52
52
|
(send ${array hash str sym} {:count :length :size} $...)
|
53
53
|
MATCHER
|
54
54
|
|
@@ -14,15 +14,16 @@ module RuboCop
|
|
14
14
|
# [1, 2, 3, 4].flat_map { |e| [e, e] }
|
15
15
|
# [1, 2, 3, 4].map { |e| [e, e] }.flatten
|
16
16
|
# [1, 2, 3, 4].collect { |e| [e, e] }.flatten
|
17
|
-
class FlatMap <
|
17
|
+
class FlatMap < Base
|
18
18
|
include RangeHelp
|
19
|
+
extend AutoCorrector
|
19
20
|
|
20
21
|
MSG = 'Use `flat_map` instead of `%<method>s...%<flatten>s`.'
|
21
22
|
FLATTEN_MULTIPLE_LEVELS = ' Beware, `flat_map` only flattens 1 level ' \
|
22
23
|
'and `flatten` can be used to flatten ' \
|
23
24
|
'multiple levels.'
|
24
25
|
|
25
|
-
def_node_matcher :flat_map_candidate?,
|
26
|
+
def_node_matcher :flat_map_candidate?, <<~PATTERN
|
26
27
|
(send
|
27
28
|
{
|
28
29
|
(block $(send _ ${:collect :map}) ...)
|
@@ -44,25 +45,11 @@ module RuboCop
|
|
44
45
|
end
|
45
46
|
end
|
46
47
|
|
47
|
-
def autocorrect(node)
|
48
|
-
map_node, _first_method, _flatten, params = flat_map_candidate?(node)
|
49
|
-
flatten_level, = *params.first
|
50
|
-
|
51
|
-
return unless flatten_level
|
52
|
-
|
53
|
-
range = range_between(node.loc.dot.begin_pos,
|
54
|
-
node.source_range.end_pos)
|
55
|
-
|
56
|
-
lambda do |corrector|
|
57
|
-
corrector.remove(range)
|
58
|
-
corrector.replace(map_node.loc.selector, 'flat_map')
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
48
|
private
|
63
49
|
|
64
50
|
def offense_for_levels(node, map_node, first_method, flatten)
|
65
51
|
message = MSG + FLATTEN_MULTIPLE_LEVELS
|
52
|
+
|
66
53
|
register_offense(node, map_node, first_method, flatten, message)
|
67
54
|
end
|
68
55
|
|
@@ -71,13 +58,24 @@ module RuboCop
|
|
71
58
|
end
|
72
59
|
|
73
60
|
def register_offense(node, map_node, first_method, flatten, message)
|
74
|
-
range = range_between(map_node.loc.selector.begin_pos,
|
75
|
-
|
61
|
+
range = range_between(map_node.loc.selector.begin_pos, node.loc.expression.end_pos)
|
62
|
+
message = format(message, method: first_method, flatten: flatten)
|
63
|
+
|
64
|
+
add_offense(range, message: message) do |corrector|
|
65
|
+
autocorrect(corrector, node)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def autocorrect(corrector, node)
|
70
|
+
map_node, _first_method, _flatten, params = flat_map_candidate?(node)
|
71
|
+
flatten_level, = *params.first
|
72
|
+
|
73
|
+
return unless flatten_level
|
74
|
+
|
75
|
+
range = range_between(node.loc.dot.begin_pos, node.source_range.end_pos)
|
76
76
|
|
77
|
-
|
78
|
-
|
79
|
-
message: format(message, method: first_method,
|
80
|
-
flatten: flatten))
|
77
|
+
corrector.remove(range)
|
78
|
+
corrector.replace(map_node.loc.selector, 'flat_map')
|
81
79
|
end
|
82
80
|
end
|
83
81
|
end
|
@@ -36,8 +36,10 @@ module RuboCop
|
|
36
36
|
# { a: 1, b: 2 }.has_value?('garbage')
|
37
37
|
# h = { a: 1, b: 2 }; h.value?(nil)
|
38
38
|
#
|
39
|
-
class InefficientHashSearch <
|
40
|
-
|
39
|
+
class InefficientHashSearch < Base
|
40
|
+
extend AutoCorrector
|
41
|
+
|
42
|
+
def_node_matcher :inefficient_include?, <<~PATTERN
|
41
43
|
(send (send $_ {:keys :values}) :include? _)
|
42
44
|
PATTERN
|
43
45
|
|
@@ -45,19 +47,16 @@ module RuboCop
|
|
45
47
|
inefficient_include?(node) do |receiver|
|
46
48
|
return if receiver.nil?
|
47
49
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
"#{autocorrect_hash_expression(node)}."\
|
59
|
-
"#{autocorrect_method(node)}(#{autocorrect_argument(node)})"
|
60
|
-
)
|
50
|
+
message = message(node)
|
51
|
+
add_offense(node, message: message) do |corrector|
|
52
|
+
# Replace `keys.include?` or `values.include?` with the appropriate
|
53
|
+
# `key?`/`value?` method.
|
54
|
+
corrector.replace(
|
55
|
+
node.loc.expression,
|
56
|
+
"#{autocorrect_hash_expression(node)}."\
|
57
|
+
"#{autocorrect_method(node)}(#{autocorrect_argument(node)})"
|
58
|
+
)
|
59
|
+
end
|
61
60
|
end
|
62
61
|
end
|
63
62
|
|
@@ -0,0 +1,116 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Performance
|
6
|
+
# This cop identifies places where inefficient `readlines` method
|
7
|
+
# can be replaced by `each_line` to avoid fully loading file content into memory.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
#
|
11
|
+
# # bad
|
12
|
+
# File.readlines('testfile').each { |l| puts l }
|
13
|
+
# IO.readlines('testfile', chomp: true).each { |l| puts l }
|
14
|
+
#
|
15
|
+
# conn.readlines(10).map { |l| l.size }
|
16
|
+
# file.readlines.find { |l| l.start_with?('#') }
|
17
|
+
# file.readlines.each { |l| puts l }
|
18
|
+
#
|
19
|
+
# # good
|
20
|
+
# File.open('testfile', 'r').each_line { |l| puts l }
|
21
|
+
# IO.open('testfile').each_line(chomp: true) { |l| puts l }
|
22
|
+
#
|
23
|
+
# conn.each_line(10).map { |l| l.size }
|
24
|
+
# file.each_line.find { |l| l.start_with?('#') }
|
25
|
+
# file.each_line { |l| puts l }
|
26
|
+
#
|
27
|
+
class IoReadlines < Base
|
28
|
+
include RangeHelp
|
29
|
+
extend AutoCorrector
|
30
|
+
|
31
|
+
MSG = 'Use `%<good>s` instead of `%<bad>s`.'
|
32
|
+
ENUMERABLE_METHODS = (Enumerable.instance_methods + [:each]).freeze
|
33
|
+
|
34
|
+
def_node_matcher :readlines_on_class?, <<~PATTERN
|
35
|
+
$(send $(send (const nil? {:IO :File}) :readlines ...) #enumerable_method?)
|
36
|
+
PATTERN
|
37
|
+
|
38
|
+
def_node_matcher :readlines_on_instance?, <<~PATTERN
|
39
|
+
$(send $(send ${nil? !const_type?} :readlines ...) #enumerable_method? ...)
|
40
|
+
PATTERN
|
41
|
+
|
42
|
+
def on_send(node)
|
43
|
+
return unless (captured_values = readlines_on_class?(node) || readlines_on_instance?(node))
|
44
|
+
|
45
|
+
enumerable_call, readlines_call, receiver = *captured_values
|
46
|
+
|
47
|
+
range = offense_range(enumerable_call, readlines_call)
|
48
|
+
good_method = build_good_method(enumerable_call)
|
49
|
+
bad_method = build_bad_method(enumerable_call)
|
50
|
+
|
51
|
+
add_offense(range, message: format(MSG, good: good_method, bad: bad_method)) do |corrector|
|
52
|
+
autocorrect(corrector, enumerable_call, readlines_call, receiver)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def enumerable_method?(node)
|
59
|
+
ENUMERABLE_METHODS.include?(node.to_sym)
|
60
|
+
end
|
61
|
+
|
62
|
+
def autocorrect(corrector, enumerable_call, readlines_call, receiver)
|
63
|
+
# We cannot safely correct `.readlines` method called on IO/File classes
|
64
|
+
# due to its signature and we are not sure with implicit receiver
|
65
|
+
# if it is called in the context of some instance or mentioned class.
|
66
|
+
return if receiver.nil?
|
67
|
+
|
68
|
+
range = correction_range(enumerable_call, readlines_call)
|
69
|
+
|
70
|
+
if readlines_call.arguments?
|
71
|
+
call_args = build_call_args(readlines_call.arguments)
|
72
|
+
replacement = "each_line(#{call_args})"
|
73
|
+
else
|
74
|
+
replacement = 'each_line'
|
75
|
+
end
|
76
|
+
|
77
|
+
corrector.replace(range, replacement)
|
78
|
+
end
|
79
|
+
|
80
|
+
def offense_range(enumerable_call, readlines_call)
|
81
|
+
readlines_pos = readlines_call.loc.selector.begin_pos
|
82
|
+
enumerable_pos = enumerable_call.loc.selector.end_pos
|
83
|
+
range_between(readlines_pos, enumerable_pos)
|
84
|
+
end
|
85
|
+
|
86
|
+
def build_good_method(enumerable_call)
|
87
|
+
if enumerable_call.method?(:each)
|
88
|
+
'each_line'
|
89
|
+
else
|
90
|
+
"each_line.#{enumerable_call.method_name}"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def build_bad_method(enumerable_call)
|
95
|
+
"readlines.#{enumerable_call.method_name}"
|
96
|
+
end
|
97
|
+
|
98
|
+
def correction_range(enumerable_call, readlines_call)
|
99
|
+
begin_pos = readlines_call.loc.selector.begin_pos
|
100
|
+
|
101
|
+
end_pos = if enumerable_call.method?(:each)
|
102
|
+
enumerable_call.loc.expression.end_pos
|
103
|
+
else
|
104
|
+
enumerable_call.loc.dot.begin_pos
|
105
|
+
end
|
106
|
+
|
107
|
+
range_between(begin_pos, end_pos)
|
108
|
+
end
|
109
|
+
|
110
|
+
def build_call_args(call_args_node)
|
111
|
+
call_args_node.map(&:source).join(', ')
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -27,17 +27,17 @@ module RuboCop
|
|
27
27
|
# end
|
28
28
|
# end
|
29
29
|
#
|
30
|
-
class OpenStruct <
|
30
|
+
class OpenStruct < Base
|
31
31
|
MSG = 'Consider using `Struct` over `OpenStruct` ' \
|
32
32
|
'to optimize the performance.'
|
33
33
|
|
34
|
-
def_node_matcher :open_struct,
|
34
|
+
def_node_matcher :open_struct, <<~PATTERN
|
35
35
|
(send (const {nil? cbase} :OpenStruct) :new ...)
|
36
36
|
PATTERN
|
37
37
|
|
38
38
|
def on_send(node)
|
39
39
|
open_struct(node) do
|
40
|
-
add_offense(node
|
40
|
+
add_offense(node.loc.selector)
|
41
41
|
end
|
42
42
|
end
|
43
43
|
end
|
@@ -3,18 +3,19 @@
|
|
3
3
|
module RuboCop
|
4
4
|
module Cop
|
5
5
|
module Performance
|
6
|
-
# This cop identifies uses of `Range#include?`, which iterates over each
|
6
|
+
# This cop identifies uses of `Range#include?` and `Range#member?`, which iterates over each
|
7
7
|
# item in a `Range` to see if a specified item is there. In contrast,
|
8
8
|
# `Range#cover?` simply compares the target item with the beginning and
|
9
9
|
# end points of the `Range`. In a great majority of cases, this is what
|
10
10
|
# is wanted.
|
11
11
|
#
|
12
|
-
# This cop is `Safe: false` by default because `Range#include?` and
|
12
|
+
# This cop is `Safe: false` by default because `Range#include?` (or `Range#member?`) and
|
13
13
|
# `Range#cover?` are not equivalent behaviour.
|
14
14
|
#
|
15
15
|
# @example
|
16
16
|
# # bad
|
17
17
|
# ('a'..'z').include?('b') # => true
|
18
|
+
# ('a'..'z').member?('b') # => true
|
18
19
|
#
|
19
20
|
# # good
|
20
21
|
# ('a'..'z').cover?('b') # => true
|
@@ -23,26 +24,28 @@ module RuboCop
|
|
23
24
|
# # the desired result:
|
24
25
|
#
|
25
26
|
# ('a'..'z').cover?('yellow') # => true
|
26
|
-
class RangeInclude <
|
27
|
-
|
27
|
+
class RangeInclude < Base
|
28
|
+
extend AutoCorrector
|
29
|
+
|
30
|
+
MSG = 'Use `Range#cover?` instead of `Range#%<bad_method>s`.'
|
28
31
|
|
29
32
|
# TODO: If we traced out assignments of variables to their uses, we
|
30
33
|
# might pick up on a few more instances of this issue
|
31
34
|
# Right now, we only detect direct calls on a Range literal
|
32
35
|
# (We don't even catch it if the Range is in double parens)
|
33
36
|
|
34
|
-
def_node_matcher :range_include,
|
35
|
-
(send {irange erange (begin {irange erange})} :include? ...)
|
37
|
+
def_node_matcher :range_include, <<~PATTERN
|
38
|
+
(send {irange erange (begin {irange erange})} ${:include? :member?} ...)
|
36
39
|
PATTERN
|
37
40
|
|
38
41
|
def on_send(node)
|
39
|
-
|
40
|
-
|
41
|
-
add_offense(node, location: :selector)
|
42
|
-
end
|
42
|
+
range_include(node) do |bad_method|
|
43
|
+
message = format(MSG, bad_method: bad_method)
|
43
44
|
|
44
|
-
|
45
|
-
|
45
|
+
add_offense(node.loc.selector, message: message) do |corrector|
|
46
|
+
corrector.replace(node.loc.selector, 'cover?')
|
47
|
+
end
|
48
|
+
end
|
46
49
|
end
|
47
50
|
end
|
48
51
|
end
|
@@ -22,23 +22,25 @@ module RuboCop
|
|
22
22
|
# def another
|
23
23
|
# yield 1, 2, 3
|
24
24
|
# end
|
25
|
-
class RedundantBlockCall <
|
25
|
+
class RedundantBlockCall < Base
|
26
|
+
extend AutoCorrector
|
27
|
+
|
26
28
|
MSG = 'Use `yield` instead of `%<argname>s.call`.'
|
27
29
|
YIELD = 'yield'
|
28
30
|
OPEN_PAREN = '('
|
29
31
|
CLOSE_PAREN = ')'
|
30
32
|
SPACE = ' '
|
31
33
|
|
32
|
-
def_node_matcher :blockarg_def,
|
34
|
+
def_node_matcher :blockarg_def, <<~PATTERN
|
33
35
|
{(def _ (args ... (blockarg $_)) $_)
|
34
36
|
(defs _ _ (args ... (blockarg $_)) $_)}
|
35
37
|
PATTERN
|
36
38
|
|
37
|
-
def_node_search :blockarg_calls,
|
39
|
+
def_node_search :blockarg_calls, <<~PATTERN
|
38
40
|
(send (lvar %1) :call ...)
|
39
41
|
PATTERN
|
40
42
|
|
41
|
-
def_node_search :blockarg_assigned?,
|
43
|
+
def_node_search :blockarg_assigned?, <<~PATTERN
|
42
44
|
(lvasgn %1 ...)
|
43
45
|
PATTERN
|
44
46
|
|
@@ -47,13 +49,17 @@ module RuboCop
|
|
47
49
|
next unless body
|
48
50
|
|
49
51
|
calls_to_report(argname, body).each do |blockcall|
|
50
|
-
add_offense(blockcall, message: format(MSG, argname: argname))
|
52
|
+
add_offense(blockcall, message: format(MSG, argname: argname)) do |corrector|
|
53
|
+
autocorrect(corrector, blockcall)
|
54
|
+
end
|
51
55
|
end
|
52
56
|
end
|
53
57
|
end
|
54
58
|
|
59
|
+
private
|
60
|
+
|
55
61
|
# offenses are registered on the `block.call` nodes
|
56
|
-
def autocorrect(node)
|
62
|
+
def autocorrect(corrector, node)
|
57
63
|
_receiver, _method, *args = *node
|
58
64
|
new_source = String.new(YIELD)
|
59
65
|
unless args.empty?
|
@@ -67,10 +73,9 @@ module RuboCop
|
|
67
73
|
end
|
68
74
|
|
69
75
|
new_source << CLOSE_PAREN if parentheses?(node) && !args.empty?
|
70
|
-
->(corrector) { corrector.replace(node.source_range, new_source) }
|
71
|
-
end
|
72
76
|
|
73
|
-
|
77
|
+
corrector.replace(node.source_range, new_source)
|
78
|
+
end
|
74
79
|
|
75
80
|
def calls_to_report(argname, body)
|
76
81
|
return [] if blockarg_assigned?(body, argname)
|