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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +5 -1
  4. data/config/default.yml +96 -13
  5. data/lib/rubocop/cop/mixin/regexp_metacharacter.rb +76 -0
  6. data/lib/rubocop/cop/mixin/sort_block.rb +28 -0
  7. data/lib/rubocop/cop/performance/ancestors_include.rb +48 -0
  8. data/lib/rubocop/cop/performance/big_decimal_with_numeric_argument.rb +45 -0
  9. data/lib/rubocop/cop/performance/bind_call.rb +77 -0
  10. data/lib/rubocop/cop/performance/caller.rb +5 -4
  11. data/lib/rubocop/cop/performance/case_when_splat.rb +18 -11
  12. data/lib/rubocop/cop/performance/casecmp.rb +17 -23
  13. data/lib/rubocop/cop/performance/chain_array_allocation.rb +5 -11
  14. data/lib/rubocop/cop/performance/collection_literal_in_loop.rb +140 -0
  15. data/lib/rubocop/cop/performance/compare_with_block.rb +12 -23
  16. data/lib/rubocop/cop/performance/count.rb +14 -17
  17. data/lib/rubocop/cop/performance/delete_prefix.rb +87 -0
  18. data/lib/rubocop/cop/performance/delete_suffix.rb +87 -0
  19. data/lib/rubocop/cop/performance/detect.rb +30 -27
  20. data/lib/rubocop/cop/performance/double_start_end_with.rb +18 -26
  21. data/lib/rubocop/cop/performance/end_with.rb +38 -25
  22. data/lib/rubocop/cop/performance/fixed_size.rb +2 -2
  23. data/lib/rubocop/cop/performance/flat_map.rb +21 -23
  24. data/lib/rubocop/cop/performance/inefficient_hash_search.rb +14 -15
  25. data/lib/rubocop/cop/performance/io_readlines.rb +116 -0
  26. data/lib/rubocop/cop/performance/open_struct.rb +3 -3
  27. data/lib/rubocop/cop/performance/range_include.rb +15 -12
  28. data/lib/rubocop/cop/performance/redundant_block_call.rb +14 -9
  29. data/lib/rubocop/cop/performance/redundant_match.rb +13 -8
  30. data/lib/rubocop/cop/performance/redundant_merge.rb +36 -23
  31. data/lib/rubocop/cop/performance/redundant_sort_block.rb +43 -0
  32. data/lib/rubocop/cop/performance/redundant_string_chars.rb +133 -0
  33. data/lib/rubocop/cop/performance/regexp_match.rb +32 -32
  34. data/lib/rubocop/cop/performance/reverse_each.rb +10 -5
  35. data/lib/rubocop/cop/performance/reverse_first.rb +72 -0
  36. data/lib/rubocop/cop/performance/size.rb +41 -43
  37. data/lib/rubocop/cop/performance/sort_reverse.rb +45 -0
  38. data/lib/rubocop/cop/performance/squeeze.rb +66 -0
  39. data/lib/rubocop/cop/performance/start_with.rb +38 -28
  40. data/lib/rubocop/cop/performance/string_include.rb +55 -0
  41. data/lib/rubocop/cop/performance/string_replacement.rb +25 -36
  42. data/lib/rubocop/cop/performance/sum.rb +129 -0
  43. data/lib/rubocop/cop/performance/times_map.rb +12 -19
  44. data/lib/rubocop/cop/performance/unfreeze_string.rb +4 -8
  45. data/lib/rubocop/cop/performance/uri_default_parser.rb +7 -13
  46. data/lib/rubocop/cop/performance_cops.rb +17 -0
  47. data/lib/rubocop/performance/inject.rb +1 -1
  48. data/lib/rubocop/performance/version.rb +1 -1
  49. metadata +27 -11
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # This cop identifies places where `sort { |a, b| b <=> a }`
7
+ # can be replaced by a faster `sort.reverse`.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # array.sort { |a, b| b <=> a }
12
+ #
13
+ # # good
14
+ # array.sort.reverse
15
+ #
16
+ class SortReverse < Base
17
+ include SortBlock
18
+ extend AutoCorrector
19
+
20
+ MSG = 'Use `sort.reverse` instead of `%<bad_method>s`.'
21
+
22
+ def on_block(node)
23
+ sort_with_block?(node) do |send, var_a, var_b, body|
24
+ replaceable_body?(body, var_b, var_a) do
25
+ range = sort_range(send, node)
26
+
27
+ add_offense(range, message: message(var_a, var_b)) do |corrector|
28
+ replacement = 'sort.reverse'
29
+
30
+ corrector.replace(range, replacement)
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def message(var_a, var_b)
39
+ bad_method = "sort { |#{var_a}, #{var_b}| #{var_b} <=> #{var_a} }"
40
+ format(MSG, bad_method: bad_method)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # This cop identifies places where `gsub(/a+/, 'a')` and `gsub!(/a+/, 'a')`
7
+ # can be replaced by `squeeze('a')` and `squeeze!('a')`.
8
+ #
9
+ # The `squeeze('a')` method is faster than `gsub(/a+/, 'a')`.
10
+ #
11
+ # @example
12
+ #
13
+ # # bad
14
+ # str.gsub(/a+/, 'a')
15
+ # str.gsub!(/a+/, 'a')
16
+ #
17
+ # # good
18
+ # str.squeeze('a')
19
+ # str.squeeze!('a')
20
+ #
21
+ class Squeeze < Base
22
+ extend AutoCorrector
23
+
24
+ MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
25
+
26
+ PREFERRED_METHODS = {
27
+ gsub: :squeeze,
28
+ gsub!: :squeeze!
29
+ }.freeze
30
+
31
+ def_node_matcher :squeeze_candidate?, <<~PATTERN
32
+ (send
33
+ $!nil? ${:gsub :gsub!}
34
+ (regexp
35
+ (str $#repeating_literal?)
36
+ (regopt))
37
+ (str $_))
38
+ PATTERN
39
+
40
+ def on_send(node)
41
+ squeeze_candidate?(node) do |receiver, bad_method, regexp_str, replace_str|
42
+ regexp_str = regexp_str[0..-2] # delete '+' from the end
43
+ regexp_str = interpret_string_escapes(regexp_str)
44
+ return unless replace_str == regexp_str
45
+
46
+ good_method = PREFERRED_METHODS[bad_method]
47
+ message = format(MSG, current: bad_method, prefer: good_method)
48
+
49
+ add_offense(node.loc.selector, message: message) do |corrector|
50
+ string_literal = to_string_literal(replace_str)
51
+ new_code = "#{receiver.source}.#{good_method}(#{string_literal})"
52
+
53
+ corrector.replace(node.source_range, new_code)
54
+ end
55
+ end
56
+ end
57
+
58
+ private
59
+
60
+ def repeating_literal?(regex_str)
61
+ regex_str.match?(/\A(?:#{Util::LITERAL_REGEX})\+\z/)
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -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
7
- # `String#start_with?` would suffice.
6
+ # This cop identifies unnecessary use of a regex where `String#start_with?` would suffice.
7
+ #
8
+ # This cop has `SafeMultiline` configuration option that `true` by default because
9
+ # `^start` is unsafe as it will behave incompatible with `start_with?`
10
+ # for receiver is multiline string.
8
11
  #
9
12
  # @example
10
13
  # # bad
@@ -17,47 +20,54 @@ module RuboCop
17
20
  #
18
21
  # # good
19
22
  # 'abc'.start_with?('ab')
20
- class StartWith < Cop
23
+ #
24
+ # @example SafeMultiline: true (default)
25
+ #
26
+ # # good
27
+ # 'abc'.match?(/^ab/)
28
+ # /^ab/.match?('abc')
29
+ # 'abc' =~ /^ab/
30
+ # /^ab/ =~ 'abc'
31
+ # 'abc'.match(/^ab/)
32
+ # /^ab/.match('abc')
33
+ #
34
+ # @example SafeMultiline: false
35
+ #
36
+ # # bad
37
+ # 'abc'.match?(/^ab/)
38
+ # /^ab/.match?('abc')
39
+ # 'abc' =~ /^ab/
40
+ # /^ab/ =~ 'abc'
41
+ # 'abc'.match(/^ab/)
42
+ # /^ab/.match('abc')
43
+ #
44
+ class StartWith < Base
45
+ include RegexpMetacharacter
46
+ extend AutoCorrector
47
+
21
48
  MSG = 'Use `String#start_with?` instead of a regex match anchored to ' \
22
49
  'the beginning of the string.'
23
- SINGLE_QUOTE = "'"
24
50
 
25
- def_node_matcher :redundant_regex?, <<-PATTERN
51
+ def_node_matcher :redundant_regex?, <<~PATTERN
26
52
  {(send $!nil? {:match :=~ :match?} (regexp (str $#literal_at_start?) (regopt)))
27
53
  (send (regexp (str $#literal_at_start?) (regopt)) {:match :match?} $_)
28
54
  (match-with-lvasgn (regexp (str $#literal_at_start?) (regopt)) $_)}
29
55
  PATTERN
30
56
 
31
- def literal_at_start?(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 start of the string?
35
- # (tricky: \s, \d, and so on are metacharacters, but other characters
36
- # escaped with a slash are just literals. LITERAL_REGEX takes all
37
- # that into account.)
38
- regex_str =~ /\A\\A(?:#{LITERAL_REGEX})+\z/
39
- end
40
-
41
57
  def on_send(node)
42
- return unless redundant_regex?(node)
43
-
44
- add_offense(node)
45
- end
46
- alias on_match_with_lvasgn on_send
58
+ return unless (receiver, regex_str = redundant_regex?(node))
47
59
 
48
- def autocorrect(node)
49
- redundant_regex?(node) do |receiver, regex_str|
60
+ add_offense(node) do |corrector|
50
61
  receiver, regex_str = regex_str, receiver if receiver.is_a?(String)
51
- regex_str = regex_str[2..-1] # drop \A anchor
62
+ regex_str = drop_start_metacharacter(regex_str)
52
63
  regex_str = interpret_string_escapes(regex_str)
53
64
 
54
- lambda do |corrector|
55
- new_source = receiver.source + '.start_with?(' +
56
- to_string_literal(regex_str) + ')'
57
- corrector.replace(node.source_range, new_source)
58
- end
65
+ new_source = "#{receiver.source}.start_with?(#{to_string_literal(regex_str)})"
66
+
67
+ corrector.replace(node.source_range, new_source)
59
68
  end
60
69
  end
70
+ alias on_match_with_lvasgn on_send
61
71
  end
62
72
  end
63
73
  end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # This cop identifies unnecessary use of a regex where
7
+ # `String#include?` would suffice.
8
+ #
9
+ # This cop's offenses are not safe to auto-correct if a receiver is nil.
10
+ #
11
+ # @example
12
+ # # bad
13
+ # 'abc'.match?(/ab/)
14
+ # /ab/.match?('abc')
15
+ # 'abc' =~ /ab/
16
+ # /ab/ =~ 'abc'
17
+ # 'abc'.match(/ab/)
18
+ # /ab/.match('abc')
19
+ #
20
+ # # good
21
+ # 'abc'.include?('ab')
22
+ class StringInclude < Base
23
+ extend AutoCorrector
24
+
25
+ MSG = 'Use `String#include?` instead of a regex match with literal-only pattern.'
26
+
27
+ def_node_matcher :redundant_regex?, <<~PATTERN
28
+ {(send $!nil? {:match :=~ :match?} (regexp (str $#literal?) (regopt)))
29
+ (send (regexp (str $#literal?) (regopt)) {:match :match?} $str)
30
+ (match-with-lvasgn (regexp (str $#literal?) (regopt)) $_)}
31
+ PATTERN
32
+
33
+ def on_send(node)
34
+ return unless (receiver, regex_str = redundant_regex?(node))
35
+
36
+ add_offense(node) do |corrector|
37
+ receiver, regex_str = regex_str, receiver if receiver.is_a?(String)
38
+ regex_str = interpret_string_escapes(regex_str)
39
+
40
+ new_source = "#{receiver.source}.include?(#{to_string_literal(regex_str)})"
41
+
42
+ corrector.replace(node.source_range, new_source)
43
+ end
44
+ end
45
+ alias on_match_with_lvasgn on_send
46
+
47
+ private
48
+
49
+ def literal?(regex_str)
50
+ regex_str.match?(/\A#{Util::LITERAL_REGEX}+\z/)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -18,17 +18,17 @@ module RuboCop
18
18
  # 'abc'.gsub(/a+/, 'd')
19
19
  # 'abc'.tr('b', 'd')
20
20
  # 'a b c'.delete(' ')
21
- class StringReplacement < Cop
21
+ class StringReplacement < Base
22
22
  include RangeHelp
23
+ extend AutoCorrector
23
24
 
24
25
  MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
25
26
  DETERMINISTIC_REGEX = /\A(?:#{LITERAL_REGEX})+\Z/.freeze
26
27
  DELETE = 'delete'
27
28
  TR = 'tr'
28
29
  BANG = '!'
29
- SINGLE_QUOTE = "'"
30
30
 
31
- def_node_matcher :string_replacement?, <<-PATTERN
31
+ def_node_matcher :string_replacement?, <<~PATTERN
32
32
  (send _ {:gsub :gsub!}
33
33
  ${regexp str (send (const nil? :Regexp) {:new :compile} _)}
34
34
  $str)
@@ -43,37 +43,37 @@ module RuboCop
43
43
  end
44
44
  end
45
45
 
46
- def autocorrect(node)
47
- _string, _method, first_param, second_param = *node
46
+ private
47
+
48
+ def offense(node, first_param, second_param)
48
49
  first_source, = first_source(first_param)
50
+ first_source = interpret_string_escapes(first_source) unless first_param.str_type?
49
51
  second_source, = *second_param
52
+ message = message(node, first_source, second_source)
50
53
 
51
- unless first_param.str_type?
52
- first_source = interpret_string_escapes(first_source)
54
+ add_offense(range(node), message: message) do |corrector|
55
+ autocorrect(corrector, node)
53
56
  end
57
+ end
54
58
 
55
- replacement_method =
56
- replacement_method(node, first_source, second_source)
59
+ def autocorrect(corrector, node)
60
+ _string, _method, first_param, second_param = *node
61
+ first_source, = first_source(first_param)
62
+ second_source, = *second_param
57
63
 
58
- replace_method(node, first_source, second_source, first_param,
59
- replacement_method)
60
- end
64
+ first_source = interpret_string_escapes(first_source) unless first_param.str_type?
61
65
 
62
- def replace_method(node, first, second, first_param, replacement)
63
- lambda do |corrector|
64
- corrector.replace(node.loc.selector, replacement)
65
- unless first_param.str_type?
66
- corrector.replace(first_param.source_range,
67
- to_string_literal(first))
68
- end
69
-
70
- if second.empty? && first.length == 1
71
- remove_second_param(corrector, node, first_param)
72
- end
73
- end
66
+ replace_method(corrector, node, first_source, second_source, first_param)
74
67
  end
75
68
 
76
- private
69
+ def replace_method(corrector, node, first_source, second_source, first_param)
70
+ replacement_method = replacement_method(node, first_source, second_source)
71
+
72
+ corrector.replace(node.loc.selector, replacement_method)
73
+ corrector.replace(first_param.source_range, to_string_literal(first_source)) unless first_param.str_type?
74
+
75
+ remove_second_param(corrector, node, first_param) if second_source.empty? && first_source.length == 1
76
+ end
77
77
 
78
78
  def accept_second_param?(second_param)
79
79
  second_source, = *second_param
@@ -97,17 +97,6 @@ module RuboCop
97
97
  first_source.length != 1
98
98
  end
99
99
 
100
- def offense(node, first_param, second_param)
101
- first_source, = first_source(first_param)
102
- unless first_param.str_type?
103
- first_source = interpret_string_escapes(first_source)
104
- end
105
- second_source, = *second_param
106
- message = message(node, first_source, second_source)
107
-
108
- add_offense(node, location: range(node), message: message)
109
- end
110
-
111
100
  def first_source(first_param)
112
101
  case first_param.type
113
102
  when :regexp
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # This cop identifies places where custom code finding the sum of elements
7
+ # in some Enumerable object can be replaced by `Enumerable#sum` method.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # [1, 2, 3].inject(:+)
12
+ # [1, 2, 3].reduce(10, :+)
13
+ # [1, 2, 3].reduce { |acc, elem| acc + elem }
14
+ #
15
+ # # good
16
+ # [1, 2, 3].sum
17
+ # [1, 2, 3].sum(10)
18
+ # [1, 2, 3].sum
19
+ #
20
+ class Sum < Base
21
+ include RangeHelp
22
+ extend AutoCorrector
23
+
24
+ MSG = 'Use `%<good_method>s` instead of `%<bad_method>s`.'
25
+
26
+ def_node_matcher :sum_candidate?, <<~PATTERN
27
+ (send _ ${:inject :reduce} $_init ? (sym :+))
28
+ PATTERN
29
+
30
+ def_node_matcher :sum_with_block_candidate?, <<~PATTERN
31
+ (block
32
+ $(send _ {:inject :reduce} $_init ?)
33
+ (args (arg $_acc) (arg $_elem))
34
+ $send)
35
+ PATTERN
36
+
37
+ def_node_matcher :acc_plus_elem?, <<~PATTERN
38
+ (send (lvar %1) :+ (lvar %2))
39
+ PATTERN
40
+ alias elem_plus_acc? acc_plus_elem?
41
+
42
+ def on_send(node)
43
+ sum_candidate?(node) do |method, init|
44
+ range = sum_method_range(node)
45
+ message = build_method_message(method, init)
46
+
47
+ add_offense(range, message: message) do |corrector|
48
+ autocorrect(corrector, init, range)
49
+ end
50
+ end
51
+ end
52
+
53
+ def on_block(node)
54
+ sum_with_block_candidate?(node) do |send, init, var_acc, var_elem, body|
55
+ if acc_plus_elem?(body, var_acc, var_elem) || elem_plus_acc?(body, var_elem, var_acc)
56
+ range = sum_block_range(send, node)
57
+ message = build_block_message(send, init, var_acc, var_elem, body)
58
+
59
+ add_offense(range, message: message) do |corrector|
60
+ autocorrect(corrector, init, range)
61
+ end
62
+ end
63
+ end
64
+ end
65
+
66
+ private
67
+
68
+ def autocorrect(corrector, init, range)
69
+ return if init.empty?
70
+
71
+ replacement = build_good_method(init)
72
+
73
+ corrector.replace(range, replacement)
74
+ end
75
+
76
+ def sum_method_range(node)
77
+ range_between(node.loc.selector.begin_pos, node.loc.end.end_pos)
78
+ end
79
+
80
+ def sum_block_range(send, node)
81
+ range_between(send.loc.selector.begin_pos, node.loc.end.end_pos)
82
+ end
83
+
84
+ def build_method_message(method, init)
85
+ good_method = build_good_method(init)
86
+ bad_method = build_method_bad_method(init, method)
87
+ format(MSG, good_method: good_method, bad_method: bad_method)
88
+ end
89
+
90
+ def build_block_message(send, init, var_acc, var_elem, body)
91
+ good_method = build_good_method(init)
92
+ bad_method = build_block_bad_method(send.method_name, init, var_acc, var_elem, body)
93
+ format(MSG, good_method: good_method, bad_method: bad_method)
94
+ end
95
+
96
+ def build_good_method(init)
97
+ good_method = 'sum'
98
+
99
+ unless init.empty?
100
+ init = init.first
101
+ good_method += "(#{init.source})" if init.source.to_i != 0
102
+ end
103
+ good_method
104
+ end
105
+
106
+ def build_method_bad_method(init, method)
107
+ bad_method = "#{method}("
108
+ unless init.empty?
109
+ init = init.first
110
+ bad_method += "#{init.source}, "
111
+ end
112
+ bad_method += ':+)'
113
+ bad_method
114
+ end
115
+
116
+ def build_block_bad_method(method, init, var_acc, var_elem, body)
117
+ bad_method = method.to_s
118
+
119
+ unless init.empty?
120
+ init = init.first
121
+ bad_method += "(#{init.source})"
122
+ end
123
+ bad_method += " { |#{var_acc}, #{var_elem}| #{body.source} }"
124
+ bad_method
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end