rubocop-performance 1.5.0 → 1.7.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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +5 -1
  4. data/config/default.yml +75 -6
  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 +45 -0
  8. data/lib/rubocop/cop/performance/big_decimal_with_numeric_argument.rb +43 -0
  9. data/lib/rubocop/cop/performance/bind_call.rb +87 -0
  10. data/lib/rubocop/cop/performance/caller.rb +3 -3
  11. data/lib/rubocop/cop/performance/casecmp.rb +5 -3
  12. data/lib/rubocop/cop/performance/chain_array_allocation.rb +1 -1
  13. data/lib/rubocop/cop/performance/compare_with_block.rb +2 -2
  14. data/lib/rubocop/cop/performance/count.rb +3 -3
  15. data/lib/rubocop/cop/performance/delete_prefix.rb +96 -0
  16. data/lib/rubocop/cop/performance/delete_suffix.rb +96 -0
  17. data/lib/rubocop/cop/performance/detect.rb +1 -1
  18. data/lib/rubocop/cop/performance/double_start_end_with.rb +2 -2
  19. data/lib/rubocop/cop/performance/end_with.rb +36 -13
  20. data/lib/rubocop/cop/performance/fixed_size.rb +1 -1
  21. data/lib/rubocop/cop/performance/flat_map.rb +1 -1
  22. data/lib/rubocop/cop/performance/inefficient_hash_search.rb +1 -1
  23. data/lib/rubocop/cop/performance/io_readlines.rb +127 -0
  24. data/lib/rubocop/cop/performance/open_struct.rb +1 -1
  25. data/lib/rubocop/cop/performance/range_include.rb +10 -8
  26. data/lib/rubocop/cop/performance/redundant_block_call.rb +3 -3
  27. data/lib/rubocop/cop/performance/redundant_match.rb +2 -2
  28. data/lib/rubocop/cop/performance/redundant_merge.rb +21 -8
  29. data/lib/rubocop/cop/performance/redundant_sort_block.rb +53 -0
  30. data/lib/rubocop/cop/performance/redundant_string_chars.rb +137 -0
  31. data/lib/rubocop/cop/performance/regexp_match.rb +13 -13
  32. data/lib/rubocop/cop/performance/reverse_each.rb +3 -2
  33. data/lib/rubocop/cop/performance/reverse_first.rb +78 -0
  34. data/lib/rubocop/cop/performance/size.rb +35 -37
  35. data/lib/rubocop/cop/performance/sort_reverse.rb +54 -0
  36. data/lib/rubocop/cop/performance/squeeze.rb +70 -0
  37. data/lib/rubocop/cop/performance/start_with.rb +36 -16
  38. data/lib/rubocop/cop/performance/string_include.rb +57 -0
  39. data/lib/rubocop/cop/performance/string_replacement.rb +4 -11
  40. data/lib/rubocop/cop/performance/times_map.rb +1 -1
  41. data/lib/rubocop/cop/performance/unfreeze_string.rb +3 -7
  42. data/lib/rubocop/cop/performance/uri_default_parser.rb +1 -1
  43. data/lib/rubocop/cop/performance_cops.rb +15 -0
  44. data/lib/rubocop/performance/inject.rb +1 -1
  45. data/lib/rubocop/performance/version.rb +1 -1
  46. metadata +25 -11
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # This cop identifies places where `reverse.first(n)` and `reverse.first`
7
+ # can be replaced by `last(n).reverse` and `last`.
8
+ #
9
+ # @example
10
+ #
11
+ # # bad
12
+ # array.reverse.first(5)
13
+ # array.reverse.first
14
+ #
15
+ # # good
16
+ # array.last(5).reverse
17
+ # array.last
18
+ #
19
+ class ReverseFirst < Cop
20
+ include RangeHelp
21
+
22
+ MSG = 'Use `%<good_method>s` instead of `%<bad_method>s`.'
23
+
24
+ def_node_matcher :reverse_first_candidate?, <<~PATTERN
25
+ (send $(send _ :reverse) :first (int _)?)
26
+ PATTERN
27
+
28
+ def on_send(node)
29
+ reverse_first_candidate?(node) do |receiver|
30
+ range = correction_range(receiver, node)
31
+ message = build_message(node)
32
+
33
+ add_offense(node, location: range, message: message)
34
+ end
35
+ end
36
+
37
+ def autocorrect(node)
38
+ reverse_first_candidate?(node) do |receiver|
39
+ range = correction_range(receiver, node)
40
+ replacement = build_good_method(node)
41
+
42
+ lambda do |corrector|
43
+ corrector.replace(range, replacement)
44
+ end
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def correction_range(receiver, node)
51
+ range_between(receiver.loc.selector.begin_pos, node.loc.expression.end_pos)
52
+ end
53
+
54
+ def build_message(node)
55
+ good_method = build_good_method(node)
56
+ bad_method = build_bad_method(node)
57
+ format(MSG, good_method: good_method, bad_method: bad_method)
58
+ end
59
+
60
+ def build_good_method(node)
61
+ if node.arguments?
62
+ "last(#{node.arguments.first.source}).reverse"
63
+ else
64
+ 'last'
65
+ end
66
+ end
67
+
68
+ def build_bad_method(node)
69
+ if node.arguments?
70
+ "reverse.first(#{node.arguments.first.source})"
71
+ else
72
+ 'reverse.first'
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -9,15 +9,27 @@ module RuboCop
9
9
  # @example
10
10
  # # bad
11
11
  # [1, 2, 3].count
12
+ # (1..3).to_a.count
13
+ # Array[*1..3].count
14
+ # Array(1..3).count
12
15
  #
13
16
  # # bad
14
17
  # {a: 1, b: 2, c: 3}.count
18
+ # [[:foo, :bar], [1, 2]].to_h.count
19
+ # Hash[*('a'..'z')].count
20
+ # Hash(key: :value).count
15
21
  #
16
22
  # # good
17
23
  # [1, 2, 3].size
24
+ # (1..3).to_a.size
25
+ # Array[*1..3].size
26
+ # Array(1..3).size
18
27
  #
19
28
  # # good
20
29
  # {a: 1, b: 2, c: 3}.size
30
+ # [[:foo, :bar], [1, 2]].to_h.size
31
+ # Hash[*('a'..'z')].size
32
+ # Hash(key: :value).size
21
33
  #
22
34
  # # good
23
35
  # [1, 2, 3].count { |e| e > 2 }
@@ -26,8 +38,30 @@ module RuboCop
26
38
  class Size < Cop
27
39
  MSG = 'Use `size` instead of `count`.'
28
40
 
41
+ def_node_matcher :array?, <<~PATTERN
42
+ {
43
+ [!nil? array_type?]
44
+ (send _ :to_a)
45
+ (send (const nil? :Array) :[] _)
46
+ (send nil? :Array _)
47
+ }
48
+ PATTERN
49
+
50
+ def_node_matcher :hash?, <<~PATTERN
51
+ {
52
+ [!nil? hash_type?]
53
+ (send _ :to_h)
54
+ (send (const nil? :Hash) :[] _)
55
+ (send nil? :Hash _)
56
+ }
57
+ PATTERN
58
+
59
+ def_node_matcher :count?, <<~PATTERN
60
+ (send {#array? #hash?} :count)
61
+ PATTERN
62
+
29
63
  def on_send(node)
30
- return unless eligible_node?(node)
64
+ return if node.parent&.block_type? || !count?(node)
31
65
 
32
66
  add_offense(node, location: :selector)
33
67
  end
@@ -35,42 +69,6 @@ module RuboCop
35
69
  def autocorrect(node)
36
70
  ->(corrector) { corrector.replace(node.loc.selector, 'size') }
37
71
  end
38
-
39
- private
40
-
41
- def eligible_node?(node)
42
- return false unless node.method?(:count) && !node.arguments?
43
-
44
- eligible_receiver?(node.receiver) && !allowed_parent?(node.parent)
45
- end
46
-
47
- def eligible_receiver?(node)
48
- return false unless node
49
-
50
- array?(node) || hash?(node)
51
- end
52
-
53
- def allowed_parent?(node)
54
- node&.block_type?
55
- end
56
-
57
- def array?(node)
58
- return true if node.array_type?
59
- return false unless node.send_type?
60
-
61
- _, constant = *node.receiver
62
-
63
- constant == :Array || node.method_name == :to_a
64
- end
65
-
66
- def hash?(node)
67
- return true if node.hash_type?
68
- return false unless node.send_type?
69
-
70
- _, constant = *node.receiver
71
-
72
- constant == :Hash || node.method_name == :to_h
73
- end
74
72
  end
75
73
  end
76
74
  end
@@ -0,0 +1,54 @@
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 < Cop
17
+ include SortBlock
18
+
19
+ MSG = 'Use `sort.reverse` instead of `%<bad_method>s`.'
20
+
21
+ def on_block(node)
22
+ sort_with_block?(node) do |send, var_a, var_b, body|
23
+ replaceable_body?(body, var_b, var_a) do
24
+ range = sort_range(send, node)
25
+
26
+ add_offense(
27
+ node,
28
+ location: range,
29
+ message: message(var_a, var_b)
30
+ )
31
+ end
32
+ end
33
+ end
34
+
35
+ def autocorrect(node)
36
+ sort_with_block?(node) do |send, _var_a, _var_b, _body|
37
+ lambda do |corrector|
38
+ range = sort_range(send, node)
39
+ replacement = 'sort.reverse'
40
+ corrector.replace(range, replacement)
41
+ end
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def message(var_a, var_b)
48
+ bad_method = "sort { |#{var_a}, #{var_b}| #{var_b} <=> #{var_a} }"
49
+ format(MSG, bad_method: bad_method)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,70 @@
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 < Cop
22
+ MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
23
+
24
+ PREFERRED_METHODS = {
25
+ gsub: :squeeze,
26
+ gsub!: :squeeze!
27
+ }.freeze
28
+
29
+ def_node_matcher :squeeze_candidate?, <<~PATTERN
30
+ (send
31
+ $!nil? ${:gsub :gsub!}
32
+ (regexp
33
+ (str $#repeating_literal?)
34
+ (regopt))
35
+ (str $_))
36
+ PATTERN
37
+
38
+ def on_send(node)
39
+ squeeze_candidate?(node) do |_, bad_method, regexp_str, replace_str|
40
+ regexp_str = regexp_str[0..-2] # delete '+' from the end
41
+ regexp_str = interpret_string_escapes(regexp_str)
42
+ return unless replace_str == regexp_str
43
+
44
+ good_method = PREFERRED_METHODS[bad_method]
45
+ message = format(MSG, current: bad_method, prefer: good_method)
46
+ add_offense(node, location: :selector, message: message)
47
+ end
48
+ end
49
+
50
+ def autocorrect(node)
51
+ squeeze_candidate?(node) do |receiver, bad_method, _regexp_str, replace_str|
52
+ lambda do |corrector|
53
+ good_method = PREFERRED_METHODS[bad_method]
54
+ string_literal = to_string_literal(replace_str)
55
+
56
+ new_code = "#{receiver.source}.#{good_method}(#{string_literal})"
57
+ corrector.replace(node.source_range, new_code)
58
+ end
59
+ end
60
+ end
61
+
62
+ private
63
+
64
+ def repeating_literal?(regex_str)
65
+ regex_str.match?(/\A(?:#{Util::LITERAL_REGEX})\+\z/)
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -3,47 +3,67 @@
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
11
14
  # 'abc'.match?(/\Aab/)
15
+ # /\Aab/.match?('abc')
12
16
  # 'abc' =~ /\Aab/
17
+ # /\Aab/ =~ 'abc'
13
18
  # 'abc'.match(/\Aab/)
19
+ # /\Aab/.match('abc')
14
20
  #
15
21
  # # good
16
22
  # 'abc'.start_with?('ab')
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
+ #
17
44
  class StartWith < Cop
45
+ include RegexpMetacharacter
46
+
18
47
  MSG = 'Use `String#start_with?` instead of a regex match anchored to ' \
19
48
  'the beginning of the string.'
20
- SINGLE_QUOTE = "'"
21
49
 
22
- def_node_matcher :redundant_regex?, <<-PATTERN
50
+ def_node_matcher :redundant_regex?, <<~PATTERN
23
51
  {(send $!nil? {:match :=~ :match?} (regexp (str $#literal_at_start?) (regopt)))
24
- (send (regexp (str $#literal_at_start?) (regopt)) {:match :=~} $_)}
52
+ (send (regexp (str $#literal_at_start?) (regopt)) {:match :match?} $_)
53
+ (match-with-lvasgn (regexp (str $#literal_at_start?) (regopt)) $_)}
25
54
  PATTERN
26
55
 
27
- def literal_at_start?(regex_str)
28
- # is this regexp 'literal' in the sense of only matching literal
29
- # chars, rather than using metachars like `.` and `*` and so on?
30
- # also, is it anchored at the start of the string?
31
- # (tricky: \s, \d, and so on are metacharacters, but other characters
32
- # escaped with a slash are just literals. LITERAL_REGEX takes all
33
- # that into account.)
34
- regex_str =~ /\A\\A(?:#{LITERAL_REGEX})+\z/
35
- end
36
-
37
56
  def on_send(node)
38
57
  return unless redundant_regex?(node)
39
58
 
40
59
  add_offense(node)
41
60
  end
61
+ alias on_match_with_lvasgn on_send
42
62
 
43
63
  def autocorrect(node)
44
64
  redundant_regex?(node) do |receiver, regex_str|
45
65
  receiver, regex_str = regex_str, receiver if receiver.is_a?(String)
46
- regex_str = regex_str[2..-1] # drop \A anchor
66
+ regex_str = drop_start_metacharacter(regex_str)
47
67
  regex_str = interpret_string_escapes(regex_str)
48
68
 
49
69
  lambda do |corrector|
@@ -0,0 +1,57 @@
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
+ # @example
10
+ # # bad
11
+ # 'abc'.match?(/ab/)
12
+ # /ab/.match?('abc')
13
+ # 'abc' =~ /ab/
14
+ # /ab/ =~ 'abc'
15
+ # 'abc'.match(/ab/)
16
+ # /ab/.match('abc')
17
+ #
18
+ # # good
19
+ # 'abc'.include?('ab')
20
+ class StringInclude < Cop
21
+ MSG = 'Use `String#include?` instead of a regex match with literal-only pattern.'
22
+
23
+ def_node_matcher :redundant_regex?, <<~PATTERN
24
+ {(send $!nil? {:match :=~ :match?} (regexp (str $#literal?) (regopt)))
25
+ (send (regexp (str $#literal?) (regopt)) {:match :match?} $str)
26
+ (match-with-lvasgn (regexp (str $#literal?) (regopt)) $_)}
27
+ PATTERN
28
+
29
+ def on_send(node)
30
+ return unless redundant_regex?(node)
31
+
32
+ add_offense(node)
33
+ end
34
+ alias on_match_with_lvasgn on_send
35
+
36
+ def autocorrect(node)
37
+ redundant_regex?(node) do |receiver, regex_str|
38
+ receiver, regex_str = regex_str, receiver if receiver.is_a?(String)
39
+ regex_str = interpret_string_escapes(regex_str)
40
+
41
+ lambda do |corrector|
42
+ new_source = receiver.source + '.include?(' +
43
+ to_string_literal(regex_str) + ')'
44
+ corrector.replace(node.source_range, new_source)
45
+ end
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def literal?(regex_str)
52
+ regex_str.match?(/\A#{Util::LITERAL_REGEX}+\z/)
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end