rubocop-performance 1.5.2 → 1.8.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 +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 +30 -27
- 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 +129 -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 +27 -11
@@ -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)
|
@@ -17,18 +17,20 @@ module RuboCop
|
|
17
17
|
# # good
|
18
18
|
# method(str =~ /regex/)
|
19
19
|
# return value unless regex =~ 'str'
|
20
|
-
class RedundantMatch <
|
20
|
+
class RedundantMatch < Base
|
21
|
+
extend AutoCorrector
|
22
|
+
|
21
23
|
MSG = 'Use `=~` in places where the `MatchData` returned by ' \
|
22
24
|
'`#match` will not be used.'
|
23
25
|
|
24
26
|
# 'match' is a fairly generic name, so we don't flag it unless we see
|
25
27
|
# a string or regexp literal on one side or the other
|
26
|
-
def_node_matcher :match_call?,
|
28
|
+
def_node_matcher :match_call?, <<~PATTERN
|
27
29
|
{(send {str regexp} :match _)
|
28
30
|
(send !nil? :match {str regexp})}
|
29
31
|
PATTERN
|
30
32
|
|
31
|
-
def_node_matcher :only_truthiness_matters?,
|
33
|
+
def_node_matcher :only_truthiness_matters?, <<~PATTERN
|
32
34
|
^({if while until case while_post until_post} equal?(%0) ...)
|
33
35
|
PATTERN
|
34
36
|
|
@@ -37,18 +39,21 @@ module RuboCop
|
|
37
39
|
(!node.value_used? || only_truthiness_matters?(node)) &&
|
38
40
|
!(node.parent && node.parent.block_type?)
|
39
41
|
|
40
|
-
add_offense(node)
|
42
|
+
add_offense(node) do |corrector|
|
43
|
+
autocorrect(corrector, node)
|
44
|
+
end
|
41
45
|
end
|
42
46
|
|
43
|
-
|
47
|
+
private
|
48
|
+
|
49
|
+
def autocorrect(corrector, node)
|
44
50
|
# Regexp#match can take a second argument, but this cop doesn't
|
45
51
|
# register an offense in that case
|
46
52
|
return unless node.first_argument.regexp_type?
|
47
53
|
|
48
|
-
new_source =
|
49
|
-
node.receiver.source + ' =~ ' + node.first_argument.source
|
54
|
+
new_source = "#{node.receiver.source} =~ #{node.first_argument.source}"
|
50
55
|
|
51
|
-
|
56
|
+
corrector.replace(node.source_range, new_source)
|
52
57
|
end
|
53
58
|
end
|
54
59
|
end
|
@@ -5,12 +5,28 @@ module RuboCop
|
|
5
5
|
module Performance
|
6
6
|
# This cop identifies places where `Hash#merge!` can be replaced by
|
7
7
|
# `Hash#[]=`.
|
8
|
+
# You can set the maximum number of key-value pairs to consider
|
9
|
+
# an offense with `MaxKeyValuePairs`.
|
8
10
|
#
|
9
11
|
# @example
|
12
|
+
# # bad
|
10
13
|
# hash.merge!(a: 1)
|
11
14
|
# hash.merge!({'key' => 'value'})
|
15
|
+
#
|
16
|
+
# # good
|
17
|
+
# hash[:a] = 1
|
18
|
+
# hash['key'] = 'value'
|
19
|
+
#
|
20
|
+
# @example MaxKeyValuePairs: 2 (default)
|
21
|
+
# # bad
|
12
22
|
# hash.merge!(a: 1, b: 2)
|
13
|
-
|
23
|
+
#
|
24
|
+
# # good
|
25
|
+
# hash[:a] = 1
|
26
|
+
# hash[:b] = 2
|
27
|
+
class RedundantMerge < Base
|
28
|
+
extend AutoCorrector
|
29
|
+
|
14
30
|
AREF_ASGN = '%<receiver>s[%<key>s] = %<value>s'
|
15
31
|
MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
|
16
32
|
|
@@ -20,28 +36,27 @@ module RuboCop
|
|
20
36
|
%<leading_space>send
|
21
37
|
RUBY
|
22
38
|
|
23
|
-
def_node_matcher :redundant_merge_candidate,
|
39
|
+
def_node_matcher :redundant_merge_candidate, <<~PATTERN
|
24
40
|
(send $!nil? :merge! [(hash $...) !kwsplat_type?])
|
25
41
|
PATTERN
|
26
42
|
|
27
|
-
def_node_matcher :modifier_flow_control?,
|
43
|
+
def_node_matcher :modifier_flow_control?, <<~PATTERN
|
28
44
|
[{if while until} modifier_form?]
|
29
45
|
PATTERN
|
30
46
|
|
31
47
|
def on_send(node)
|
32
48
|
each_redundant_merge(node) do |redundant_merge_node|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
correct_single_element(node, new_source)
|
49
|
+
message = message(node)
|
50
|
+
add_offense(redundant_merge_node, message: message) do |corrector|
|
51
|
+
redundant_merge_candidate(node) do |receiver, pairs|
|
52
|
+
new_source = to_assignments(receiver, pairs).join("\n")
|
53
|
+
|
54
|
+
if node.parent && pairs.size > 1
|
55
|
+
correct_multiple_elements(corrector, node, node.parent, new_source)
|
56
|
+
else
|
57
|
+
correct_single_element(corrector, node, new_source)
|
58
|
+
end
|
59
|
+
end
|
45
60
|
end
|
46
61
|
end
|
47
62
|
end
|
@@ -84,7 +99,7 @@ module RuboCop
|
|
84
99
|
!EachWithObjectInspector.new(node, receiver).value_used?
|
85
100
|
end
|
86
101
|
|
87
|
-
def correct_multiple_elements(node, parent, new_source)
|
102
|
+
def correct_multiple_elements(corrector, node, parent, new_source)
|
88
103
|
if modifier_flow_control?(parent)
|
89
104
|
new_source = rewrite_with_modifier(node, parent, new_source)
|
90
105
|
node = parent
|
@@ -93,11 +108,11 @@ module RuboCop
|
|
93
108
|
new_source.gsub!(/\n/, padding)
|
94
109
|
end
|
95
110
|
|
96
|
-
|
111
|
+
corrector.replace(node.source_range, new_source)
|
97
112
|
end
|
98
113
|
|
99
|
-
def correct_single_element(node, new_source)
|
100
|
-
|
114
|
+
def correct_single_element(corrector, node, new_source)
|
115
|
+
corrector.replace(node.source_range, new_source)
|
101
116
|
end
|
102
117
|
|
103
118
|
def to_assignments(receiver, pairs)
|
@@ -168,13 +183,11 @@ module RuboCop
|
|
168
183
|
end
|
169
184
|
|
170
185
|
def unwind(receiver)
|
171
|
-
while receiver.respond_to?(:send_type?) && receiver.send_type?
|
172
|
-
receiver, = *receiver
|
173
|
-
end
|
186
|
+
receiver, = *receiver while receiver.respond_to?(:send_type?) && receiver.send_type?
|
174
187
|
receiver
|
175
188
|
end
|
176
189
|
|
177
|
-
def_node_matcher :each_with_object_node,
|
190
|
+
def_node_matcher :each_with_object_node, <<~PATTERN
|
178
191
|
(block (send _ :each_with_object _) (args _ $_) ...)
|
179
192
|
PATTERN
|
180
193
|
end
|