rubocop-performance 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE.txt +20 -0
- data/README.md +1 -0
- data/lib/rubocop-performance.rb +5 -0
- data/lib/rubocop/cop/performance/caller.rb +69 -0
- data/lib/rubocop/cop/performance/case_when_splat.rb +173 -0
- data/lib/rubocop/cop/performance/casecmp.rb +117 -0
- data/lib/rubocop/cop/performance/compare_with_block.rb +119 -0
- data/lib/rubocop/cop/performance/count.rb +102 -0
- data/lib/rubocop/cop/performance/detect.rb +110 -0
- data/lib/rubocop/cop/performance/double_start_end_with.rb +94 -0
- data/lib/rubocop/cop/performance/end_with.rb +56 -0
- data/lib/rubocop/cop/performance/fixed_size.rb +97 -0
- data/lib/rubocop/cop/performance/flat_map.rb +78 -0
- data/lib/rubocop/cop/performance/inefficient_hash_search.rb +99 -0
- data/lib/rubocop/cop/performance/lstrip_rstrip.rb +46 -0
- data/lib/rubocop/cop/performance/range_include.rb +47 -0
- data/lib/rubocop/cop/performance/redundant_block_call.rb +93 -0
- data/lib/rubocop/cop/performance/redundant_match.rb +56 -0
- data/lib/rubocop/cop/performance/redundant_merge.rb +169 -0
- data/lib/rubocop/cop/performance/redundant_sort_by.rb +50 -0
- data/lib/rubocop/cop/performance/regexp_match.rb +264 -0
- data/lib/rubocop/cop/performance/reverse_each.rb +42 -0
- data/lib/rubocop/cop/performance/sample.rb +142 -0
- data/lib/rubocop/cop/performance/size.rb +71 -0
- data/lib/rubocop/cop/performance/start_with.rb +59 -0
- data/lib/rubocop/cop/performance/string_replacement.rb +172 -0
- data/lib/rubocop/cop/performance/times_map.rb +71 -0
- data/lib/rubocop/cop/performance/unfreeze_string.rb +50 -0
- data/lib/rubocop/cop/performance/unneeded_sort.rb +165 -0
- data/lib/rubocop/cop/performance/uri_default_parser.rb +47 -0
- data/lib/rubocop/cop/performance_cops.rb +37 -0
- data/lib/rubocop/performance/version.rb +9 -0
- metadata +114 -0
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Performance
|
6
|
+
# This cop is used to identify usages of
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
# [1, 2, 3, 4].map { |e| [e, e] }.flatten(1)
|
11
|
+
# [1, 2, 3, 4].collect { |e| [e, e] }.flatten(1)
|
12
|
+
#
|
13
|
+
# # good
|
14
|
+
# [1, 2, 3, 4].flat_map { |e| [e, e] }
|
15
|
+
# [1, 2, 3, 4].map { |e| [e, e] }.flatten
|
16
|
+
# [1, 2, 3, 4].collect { |e| [e, e] }.flatten
|
17
|
+
class FlatMap < Cop
|
18
|
+
include RangeHelp
|
19
|
+
|
20
|
+
MSG = 'Use `flat_map` instead of `%<method>s...%<flatten>s`.'.freeze
|
21
|
+
FLATTEN_MULTIPLE_LEVELS = ' Beware, `flat_map` only flattens 1 level ' \
|
22
|
+
'and `flatten` can be used to flatten ' \
|
23
|
+
'multiple levels.'.freeze
|
24
|
+
|
25
|
+
def_node_matcher :flat_map_candidate?, <<-PATTERN
|
26
|
+
(send (block $(send _ ${:collect :map}) ...) ${:flatten :flatten!} $...)
|
27
|
+
PATTERN
|
28
|
+
|
29
|
+
def on_send(node)
|
30
|
+
flat_map_candidate?(node) do |map_node, first_method, flatten, params|
|
31
|
+
flatten_level, = *params.first
|
32
|
+
if cop_config['EnabledForFlattenWithoutParams'] && !flatten_level
|
33
|
+
offense_for_levels(node, map_node, first_method, flatten)
|
34
|
+
elsif flatten_level == 1
|
35
|
+
offense_for_method(node, map_node, first_method, flatten)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def autocorrect(node)
|
41
|
+
map_node, _first_method, _flatten, params = flat_map_candidate?(node)
|
42
|
+
flatten_level, = *params.first
|
43
|
+
|
44
|
+
return unless flatten_level
|
45
|
+
|
46
|
+
range = range_between(node.loc.dot.begin_pos,
|
47
|
+
node.source_range.end_pos)
|
48
|
+
|
49
|
+
lambda do |corrector|
|
50
|
+
corrector.remove(range)
|
51
|
+
corrector.replace(map_node.loc.selector, 'flat_map')
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def offense_for_levels(node, map_node, first_method, flatten)
|
58
|
+
message = MSG + FLATTEN_MULTIPLE_LEVELS
|
59
|
+
register_offense(node, map_node, first_method, flatten, message)
|
60
|
+
end
|
61
|
+
|
62
|
+
def offense_for_method(node, map_node, first_method, flatten)
|
63
|
+
register_offense(node, map_node, first_method, flatten, MSG)
|
64
|
+
end
|
65
|
+
|
66
|
+
def register_offense(node, map_node, first_method, flatten, message)
|
67
|
+
range = range_between(map_node.loc.selector.begin_pos,
|
68
|
+
node.loc.expression.end_pos)
|
69
|
+
|
70
|
+
add_offense(node,
|
71
|
+
location: range,
|
72
|
+
message: format(message, method: first_method,
|
73
|
+
flatten: flatten))
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Performance
|
6
|
+
# This cop checks for inefficient searching of keys and values within
|
7
|
+
# hashes.
|
8
|
+
#
|
9
|
+
# `Hash#keys.include?` is less efficient than `Hash#key?` because
|
10
|
+
# the former allocates a new array and then performs an O(n) search
|
11
|
+
# through that array, while `Hash#key?` does not allocate any array and
|
12
|
+
# performs a faster O(1) search for the key.
|
13
|
+
#
|
14
|
+
# `Hash#values.include?` is less efficient than `Hash#value?`. While they
|
15
|
+
# both perform an O(n) search through all of the values, calling `values`
|
16
|
+
# allocates a new array while using `value?` does not.
|
17
|
+
#
|
18
|
+
# @example
|
19
|
+
# # bad
|
20
|
+
# { a: 1, b: 2 }.keys.include?(:a)
|
21
|
+
# { a: 1, b: 2 }.keys.include?(:z)
|
22
|
+
# h = { a: 1, b: 2 }; h.keys.include?(100)
|
23
|
+
#
|
24
|
+
# # good
|
25
|
+
# { a: 1, b: 2 }.key?(:a)
|
26
|
+
# { a: 1, b: 2 }.has_key?(:z)
|
27
|
+
# h = { a: 1, b: 2 }; h.key?(100)
|
28
|
+
#
|
29
|
+
# # bad
|
30
|
+
# { a: 1, b: 2 }.values.include?(2)
|
31
|
+
# { a: 1, b: 2 }.values.include?('garbage')
|
32
|
+
# h = { a: 1, b: 2 }; h.values.include?(nil)
|
33
|
+
#
|
34
|
+
# # good
|
35
|
+
# { a: 1, b: 2 }.value?(2)
|
36
|
+
# { a: 1, b: 2 }.has_value?('garbage')
|
37
|
+
# h = { a: 1, b: 2 }; h.value?(nil)
|
38
|
+
#
|
39
|
+
class InefficientHashSearch < Cop
|
40
|
+
def_node_matcher :inefficient_include?, <<-PATTERN
|
41
|
+
(send (send $_ {:keys :values}) :include? _)
|
42
|
+
PATTERN
|
43
|
+
|
44
|
+
def on_send(node)
|
45
|
+
inefficient_include?(node) do |receiver|
|
46
|
+
return if receiver.nil?
|
47
|
+
|
48
|
+
add_offense(node)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def autocorrect(node)
|
53
|
+
lambda do |corrector|
|
54
|
+
# Replace `keys.include?` or `values.include?` with the appropriate
|
55
|
+
# `key?`/`value?` method.
|
56
|
+
corrector.replace(
|
57
|
+
node.loc.expression,
|
58
|
+
"#{autocorrect_hash_expression(node)}."\
|
59
|
+
"#{autocorrect_method(node)}(#{autocorrect_argument(node)})"
|
60
|
+
)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def message(node)
|
67
|
+
"Use `##{autocorrect_method(node)}` instead of "\
|
68
|
+
"`##{current_method(node)}.include?`."
|
69
|
+
end
|
70
|
+
|
71
|
+
def autocorrect_method(node)
|
72
|
+
case current_method(node)
|
73
|
+
when :keys then use_long_method ? 'has_key?' : 'key?'
|
74
|
+
when :values then use_long_method ? 'has_value?' : 'value?'
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def current_method(node)
|
79
|
+
node.receiver.method_name
|
80
|
+
end
|
81
|
+
|
82
|
+
def use_long_method
|
83
|
+
preferred_config = config.for_all_cops['Style/PreferredHashMethods']
|
84
|
+
preferred_config &&
|
85
|
+
preferred_config['EnforcedStyle'] == 'long' &&
|
86
|
+
preferred_config['Enabled']
|
87
|
+
end
|
88
|
+
|
89
|
+
def autocorrect_argument(node)
|
90
|
+
node.arguments.first.source
|
91
|
+
end
|
92
|
+
|
93
|
+
def autocorrect_hash_expression(node)
|
94
|
+
node.receiver.receiver.source
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Performance
|
6
|
+
# This cop identifies places where `lstrip.rstrip` can be replaced by
|
7
|
+
# `strip`.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# # bad
|
11
|
+
# 'abc'.lstrip.rstrip
|
12
|
+
# 'abc'.rstrip.lstrip
|
13
|
+
#
|
14
|
+
# # good
|
15
|
+
# 'abc'.strip
|
16
|
+
class LstripRstrip < Cop
|
17
|
+
include RangeHelp
|
18
|
+
|
19
|
+
MSG = 'Use `strip` instead of `%<methods>s`.'.freeze
|
20
|
+
|
21
|
+
def_node_matcher :lstrip_rstrip, <<-PATTERN
|
22
|
+
{(send $(send _ $:rstrip) $:lstrip)
|
23
|
+
(send $(send _ $:lstrip) $:rstrip)}
|
24
|
+
PATTERN
|
25
|
+
|
26
|
+
def on_send(node)
|
27
|
+
lstrip_rstrip(node) do |first_send, method_one, method_two|
|
28
|
+
range = range_between(first_send.loc.selector.begin_pos,
|
29
|
+
node.source_range.end_pos)
|
30
|
+
add_offense(node,
|
31
|
+
location: range,
|
32
|
+
message: format(MSG,
|
33
|
+
methods: "#{method_one}.#{method_two}"))
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def autocorrect(node)
|
38
|
+
range = range_between(node.receiver.loc.selector.begin_pos,
|
39
|
+
node.source_range.end_pos)
|
40
|
+
|
41
|
+
->(corrector) { corrector.replace(range, 'strip') }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Performance
|
6
|
+
# This cop identifies uses of `Range#include?`, which iterates over each
|
7
|
+
# item in a `Range` to see if a specified item is there. In contrast,
|
8
|
+
# `Range#cover?` simply compares the target item with the beginning and
|
9
|
+
# end points of the `Range`. In a great majority of cases, this is what
|
10
|
+
# is wanted.
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
# # bad
|
14
|
+
# ('a'..'z').include?('b') # => true
|
15
|
+
#
|
16
|
+
# # good
|
17
|
+
# ('a'..'z').cover?('b') # => true
|
18
|
+
#
|
19
|
+
# # Example of a case where `Range#cover?` may not provide
|
20
|
+
# # the desired result:
|
21
|
+
#
|
22
|
+
# ('a'..'z').cover?('yellow') # => true
|
23
|
+
class RangeInclude < Cop
|
24
|
+
MSG = 'Use `Range#cover?` instead of `Range#include?`.'.freeze
|
25
|
+
|
26
|
+
# TODO: If we traced out assignments of variables to their uses, we
|
27
|
+
# might pick up on a few more instances of this issue
|
28
|
+
# Right now, we only detect direct calls on a Range literal
|
29
|
+
# (We don't even catch it if the Range is in double parens)
|
30
|
+
|
31
|
+
def_node_matcher :range_include, <<-PATTERN
|
32
|
+
(send {irange erange (begin {irange erange})} :include? ...)
|
33
|
+
PATTERN
|
34
|
+
|
35
|
+
def on_send(node)
|
36
|
+
return unless range_include(node)
|
37
|
+
|
38
|
+
add_offense(node, location: :selector)
|
39
|
+
end
|
40
|
+
|
41
|
+
def autocorrect(node)
|
42
|
+
->(corrector) { corrector.replace(node.loc.selector, 'cover?') }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Performance
|
6
|
+
# This cop identifies the use of a `&block` parameter and `block.call`
|
7
|
+
# where `yield` would do just as well.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# # bad
|
11
|
+
# def method(&block)
|
12
|
+
# block.call
|
13
|
+
# end
|
14
|
+
# def another(&func)
|
15
|
+
# func.call 1, 2, 3
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# # good
|
19
|
+
# def method
|
20
|
+
# yield
|
21
|
+
# end
|
22
|
+
# def another
|
23
|
+
# yield 1, 2, 3
|
24
|
+
# end
|
25
|
+
class RedundantBlockCall < Cop
|
26
|
+
MSG = 'Use `yield` instead of `%<argname>s.call`.'.freeze
|
27
|
+
YIELD = 'yield'.freeze
|
28
|
+
OPEN_PAREN = '('.freeze
|
29
|
+
CLOSE_PAREN = ')'.freeze
|
30
|
+
SPACE = ' '.freeze
|
31
|
+
|
32
|
+
def_node_matcher :blockarg_def, <<-PATTERN
|
33
|
+
{(def _ (args ... (blockarg $_)) $_)
|
34
|
+
(defs _ _ (args ... (blockarg $_)) $_)}
|
35
|
+
PATTERN
|
36
|
+
|
37
|
+
def_node_search :blockarg_calls, <<-PATTERN
|
38
|
+
(send (lvar %1) :call ...)
|
39
|
+
PATTERN
|
40
|
+
|
41
|
+
def_node_search :blockarg_assigned?, <<-PATTERN
|
42
|
+
(lvasgn %1 ...)
|
43
|
+
PATTERN
|
44
|
+
|
45
|
+
def on_def(node)
|
46
|
+
blockarg_def(node) do |argname, body|
|
47
|
+
next unless body
|
48
|
+
|
49
|
+
calls_to_report(argname, body).each do |blockcall|
|
50
|
+
add_offense(blockcall, message: format(MSG, argname: argname))
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# offenses are registered on the `block.call` nodes
|
56
|
+
def autocorrect(node)
|
57
|
+
_receiver, _method, *args = *node
|
58
|
+
new_source = String.new(YIELD)
|
59
|
+
unless args.empty?
|
60
|
+
new_source += if parentheses?(node)
|
61
|
+
OPEN_PAREN
|
62
|
+
else
|
63
|
+
SPACE
|
64
|
+
end
|
65
|
+
|
66
|
+
new_source << args.map(&:source).join(', ')
|
67
|
+
end
|
68
|
+
|
69
|
+
new_source << CLOSE_PAREN if parentheses?(node) && !args.empty?
|
70
|
+
->(corrector) { corrector.replace(node.source_range, new_source) }
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def calls_to_report(argname, body)
|
76
|
+
return [] if blockarg_assigned?(body, argname)
|
77
|
+
|
78
|
+
calls = to_enum(:blockarg_calls, body, argname)
|
79
|
+
|
80
|
+
return [] if calls.any? { |call| args_include_block_pass?(call) }
|
81
|
+
|
82
|
+
calls
|
83
|
+
end
|
84
|
+
|
85
|
+
def args_include_block_pass?(blockcall)
|
86
|
+
_receiver, _call, *args = *blockcall
|
87
|
+
|
88
|
+
args.any?(&:block_pass_type?)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Performance
|
6
|
+
# This cop identifies the use of `Regexp#match` or `String#match`, which
|
7
|
+
# returns `#<MatchData>`/`nil`. The return value of `=~` is an integral
|
8
|
+
# index/`nil` and is more performant.
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# # bad
|
12
|
+
# do_something if str.match(/regex/)
|
13
|
+
# while regex.match('str')
|
14
|
+
# do_something
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# # good
|
18
|
+
# method(str =~ /regex/)
|
19
|
+
# return value unless regex =~ 'str'
|
20
|
+
class RedundantMatch < Cop
|
21
|
+
MSG = 'Use `=~` in places where the `MatchData` returned by ' \
|
22
|
+
'`#match` will not be used.'.freeze
|
23
|
+
|
24
|
+
# 'match' is a fairly generic name, so we don't flag it unless we see
|
25
|
+
# a string or regexp literal on one side or the other
|
26
|
+
def_node_matcher :match_call?, <<-PATTERN
|
27
|
+
{(send {str regexp} :match _)
|
28
|
+
(send !nil? :match {str regexp})}
|
29
|
+
PATTERN
|
30
|
+
|
31
|
+
def_node_matcher :only_truthiness_matters?, <<-PATTERN
|
32
|
+
^({if while until case while_post until_post} equal?(%0) ...)
|
33
|
+
PATTERN
|
34
|
+
|
35
|
+
def on_send(node)
|
36
|
+
return unless match_call?(node) &&
|
37
|
+
(!node.value_used? || only_truthiness_matters?(node)) &&
|
38
|
+
!(node.parent && node.parent.block_type?)
|
39
|
+
|
40
|
+
add_offense(node)
|
41
|
+
end
|
42
|
+
|
43
|
+
def autocorrect(node)
|
44
|
+
# Regexp#match can take a second argument, but this cop doesn't
|
45
|
+
# register an offense in that case
|
46
|
+
return unless node.first_argument.regexp_type?
|
47
|
+
|
48
|
+
new_source =
|
49
|
+
node.receiver.source + ' =~ ' + node.first_argument.source
|
50
|
+
|
51
|
+
->(corrector) { corrector.replace(node.source_range, new_source) }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Performance
|
6
|
+
# This cop identifies places where `Hash#merge!` can be replaced by
|
7
|
+
# `Hash#[]=`.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# hash.merge!(a: 1)
|
11
|
+
# hash.merge!({'key' => 'value'})
|
12
|
+
# hash.merge!(a: 1, b: 2)
|
13
|
+
class RedundantMerge < Cop
|
14
|
+
AREF_ASGN = '%<receiver>s[%<key>s] = %<value>s'.freeze
|
15
|
+
MSG = 'Use `%<prefer>s` instead of `%<current>s`.'.freeze
|
16
|
+
|
17
|
+
def_node_matcher :redundant_merge_candidate, <<-PATTERN
|
18
|
+
(send $!nil? :merge! [(hash $...) !kwsplat_type?])
|
19
|
+
PATTERN
|
20
|
+
|
21
|
+
def_node_matcher :modifier_flow_control?, <<-PATTERN
|
22
|
+
[{if while until} modifier_form?]
|
23
|
+
PATTERN
|
24
|
+
|
25
|
+
def on_send(node)
|
26
|
+
each_redundant_merge(node) do |redundant_merge_node|
|
27
|
+
add_offense(redundant_merge_node)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def autocorrect(node)
|
32
|
+
redundant_merge_candidate(node) do |receiver, pairs|
|
33
|
+
new_source = to_assignments(receiver, pairs).join("\n")
|
34
|
+
|
35
|
+
if node.parent && pairs.size > 1
|
36
|
+
correct_multiple_elements(node, node.parent, new_source)
|
37
|
+
else
|
38
|
+
correct_single_element(node, new_source)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def message(node)
|
46
|
+
redundant_merge_candidate(node) do |receiver, pairs|
|
47
|
+
assignments = to_assignments(receiver, pairs).join('; ')
|
48
|
+
|
49
|
+
format(MSG, prefer: assignments, current: node.source)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def each_redundant_merge(node)
|
54
|
+
redundant_merge_candidate(node) do |receiver, pairs|
|
55
|
+
next if non_redundant_merge?(node, receiver, pairs)
|
56
|
+
|
57
|
+
yield node
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def non_redundant_merge?(node, receiver, pairs)
|
62
|
+
non_redundant_pairs?(receiver, pairs) ||
|
63
|
+
non_redundant_value_used?(receiver, node)
|
64
|
+
end
|
65
|
+
|
66
|
+
def non_redundant_pairs?(receiver, pairs)
|
67
|
+
pairs.size > 1 && !receiver.pure? || pairs.size > max_key_value_pairs
|
68
|
+
end
|
69
|
+
|
70
|
+
def non_redundant_value_used?(receiver, node)
|
71
|
+
node.value_used? &&
|
72
|
+
!EachWithObjectInspector.new(node, receiver).value_used?
|
73
|
+
end
|
74
|
+
|
75
|
+
def correct_multiple_elements(node, parent, new_source)
|
76
|
+
if modifier_flow_control?(parent)
|
77
|
+
new_source = rewrite_with_modifier(node, parent, new_source)
|
78
|
+
node = parent
|
79
|
+
else
|
80
|
+
padding = "\n#{leading_spaces(node)}"
|
81
|
+
new_source.gsub!(/\n/, padding)
|
82
|
+
end
|
83
|
+
|
84
|
+
->(corrector) { corrector.replace(node.source_range, new_source) }
|
85
|
+
end
|
86
|
+
|
87
|
+
def correct_single_element(node, new_source)
|
88
|
+
->(corrector) { corrector.replace(node.source_range, new_source) }
|
89
|
+
end
|
90
|
+
|
91
|
+
def to_assignments(receiver, pairs)
|
92
|
+
pairs.map do |pair|
|
93
|
+
key, value = *pair
|
94
|
+
|
95
|
+
key = key.sym_type? && pair.colon? ? ":#{key.source}" : key.source
|
96
|
+
|
97
|
+
format(AREF_ASGN, receiver: receiver.source,
|
98
|
+
key: key,
|
99
|
+
value: value.source)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def rewrite_with_modifier(node, parent, new_source)
|
104
|
+
cond, = *parent
|
105
|
+
padding = "\n#{(' ' * indent_width) + leading_spaces(node)}"
|
106
|
+
new_source.gsub!(/\n/, padding)
|
107
|
+
|
108
|
+
parent.loc.keyword.source << ' ' << cond.source << padding <<
|
109
|
+
new_source << "\n" << leading_spaces(node) << 'end'
|
110
|
+
end
|
111
|
+
|
112
|
+
def leading_spaces(node)
|
113
|
+
node.source_range.source_line[/\A\s*/]
|
114
|
+
end
|
115
|
+
|
116
|
+
def indent_width
|
117
|
+
@config.for_cop('IndentationWidth')['Width'] || 2
|
118
|
+
end
|
119
|
+
|
120
|
+
def max_key_value_pairs
|
121
|
+
Integer(cop_config['MaxKeyValuePairs'])
|
122
|
+
end
|
123
|
+
|
124
|
+
# A utility class for checking the use of values within an
|
125
|
+
# `each_with_object` call.
|
126
|
+
class EachWithObjectInspector
|
127
|
+
extend NodePattern::Macros
|
128
|
+
|
129
|
+
def initialize(node, receiver)
|
130
|
+
@node = node
|
131
|
+
@receiver = unwind(receiver)
|
132
|
+
end
|
133
|
+
|
134
|
+
def value_used?
|
135
|
+
return false unless eligible_receiver? && second_argument
|
136
|
+
|
137
|
+
receiver.loc.name.source == second_argument.loc.name.source
|
138
|
+
end
|
139
|
+
|
140
|
+
private
|
141
|
+
|
142
|
+
attr_reader :node, :receiver
|
143
|
+
|
144
|
+
def eligible_receiver?
|
145
|
+
receiver.respond_to?(:lvar_type?) && receiver.lvar_type?
|
146
|
+
end
|
147
|
+
|
148
|
+
def second_argument
|
149
|
+
parent = node.parent
|
150
|
+
parent = parent.parent if parent.begin_type?
|
151
|
+
|
152
|
+
@second_argument ||= each_with_object_node(parent)
|
153
|
+
end
|
154
|
+
|
155
|
+
def unwind(receiver)
|
156
|
+
while receiver.respond_to?(:send_type?) && receiver.send_type?
|
157
|
+
receiver, = *receiver
|
158
|
+
end
|
159
|
+
receiver
|
160
|
+
end
|
161
|
+
|
162
|
+
def_node_matcher :each_with_object_node, <<-PATTERN
|
163
|
+
(block (send _ :each_with_object _) (args _ $_) ...)
|
164
|
+
PATTERN
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|