rubocop-performance 1.6.1 → 1.9.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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +8 -0
  3. data/config/default.yml +95 -8
  4. data/lib/rubocop/cop/mixin/regexp_metacharacter.rb +4 -4
  5. data/lib/rubocop/cop/mixin/sort_block.rb +28 -0
  6. data/lib/rubocop/cop/performance/ancestors_include.rb +49 -0
  7. data/lib/rubocop/cop/performance/array_semi_infinite_range_slice.rb +74 -0
  8. data/lib/rubocop/cop/performance/big_decimal_with_numeric_argument.rb +46 -0
  9. data/lib/rubocop/cop/performance/bind_call.rb +9 -18
  10. data/lib/rubocop/cop/performance/block_given_with_explicit_block.rb +52 -0
  11. data/lib/rubocop/cop/performance/caller.rb +14 -15
  12. data/lib/rubocop/cop/performance/case_when_splat.rb +18 -11
  13. data/lib/rubocop/cop/performance/casecmp.rb +13 -20
  14. data/lib/rubocop/cop/performance/chain_array_allocation.rb +4 -10
  15. data/lib/rubocop/cop/performance/collection_literal_in_loop.rb +140 -0
  16. data/lib/rubocop/cop/performance/compare_with_block.rb +10 -21
  17. data/lib/rubocop/cop/performance/constant_regexp.rb +68 -0
  18. data/lib/rubocop/cop/performance/count.rb +14 -16
  19. data/lib/rubocop/cop/performance/delete_prefix.rb +14 -22
  20. data/lib/rubocop/cop/performance/delete_suffix.rb +14 -22
  21. data/lib/rubocop/cop/performance/detect.rb +65 -32
  22. data/lib/rubocop/cop/performance/double_start_end_with.rb +16 -24
  23. data/lib/rubocop/cop/performance/end_with.rb +9 -13
  24. data/lib/rubocop/cop/performance/fixed_size.rb +2 -1
  25. data/lib/rubocop/cop/performance/flat_map.rb +21 -22
  26. data/lib/rubocop/cop/performance/inefficient_hash_search.rb +15 -14
  27. data/lib/rubocop/cop/performance/io_readlines.rb +112 -0
  28. data/lib/rubocop/cop/performance/method_object_as_block.rb +32 -0
  29. data/lib/rubocop/cop/performance/open_struct.rb +3 -2
  30. data/lib/rubocop/cop/performance/range_include.rb +15 -11
  31. data/lib/rubocop/cop/performance/redundant_block_call.rb +15 -10
  32. data/lib/rubocop/cop/performance/redundant_match.rb +12 -6
  33. data/lib/rubocop/cop/performance/redundant_merge.rb +19 -17
  34. data/lib/rubocop/cop/performance/redundant_sort_block.rb +43 -0
  35. data/lib/rubocop/cop/performance/redundant_string_chars.rb +129 -0
  36. data/lib/rubocop/cop/performance/regexp_match.rb +20 -20
  37. data/lib/rubocop/cop/performance/reverse_each.rb +10 -5
  38. data/lib/rubocop/cop/performance/reverse_first.rb +73 -0
  39. data/lib/rubocop/cop/performance/size.rb +42 -43
  40. data/lib/rubocop/cop/performance/sort_reverse.rb +45 -0
  41. data/lib/rubocop/cop/performance/squeeze.rb +67 -0
  42. data/lib/rubocop/cop/performance/start_with.rb +9 -13
  43. data/lib/rubocop/cop/performance/string_include.rb +56 -0
  44. data/lib/rubocop/cop/performance/string_replacement.rb +24 -27
  45. data/lib/rubocop/cop/performance/sum.rb +236 -0
  46. data/lib/rubocop/cop/performance/times_map.rb +12 -18
  47. data/lib/rubocop/cop/performance/unfreeze_string.rb +20 -2
  48. data/lib/rubocop/cop/performance/uri_default_parser.rb +7 -12
  49. data/lib/rubocop/cop/performance_cops.rb +16 -0
  50. data/lib/rubocop/performance/version.rb +6 -1
  51. metadata +35 -13
@@ -41,11 +41,13 @@ module RuboCop
41
41
  # 'abc'.match(/bc$/)
42
42
  # /bc$/.match('abc')
43
43
  #
44
- class EndWith < Cop
44
+ class EndWith < Base
45
45
  include RegexpMetacharacter
46
+ extend AutoCorrector
46
47
 
47
48
  MSG = 'Use `String#end_with?` instead of a regex match anchored to ' \
48
49
  'the end of the string.'
50
+ RESTRICT_ON_SEND = %i[match =~ match?].freeze
49
51
 
50
52
  def_node_matcher :redundant_regex?, <<~PATTERN
51
53
  {(send $!nil? {:match :=~ :match?} (regexp (str $#literal_at_end?) (regopt)))
@@ -54,25 +56,19 @@ module RuboCop
54
56
  PATTERN
55
57
 
56
58
  def on_send(node)
57
- return unless redundant_regex?(node)
59
+ return unless (receiver, regex_str = redundant_regex?(node))
58
60
 
59
- add_offense(node)
60
- end
61
- alias on_match_with_lvasgn on_send
62
-
63
- def autocorrect(node)
64
- redundant_regex?(node) do |receiver, regex_str|
61
+ add_offense(node) do |corrector|
65
62
  receiver, regex_str = regex_str, receiver if receiver.is_a?(String)
66
63
  regex_str = drop_end_metacharacter(regex_str)
67
64
  regex_str = interpret_string_escapes(regex_str)
68
65
 
69
- lambda do |corrector|
70
- new_source = receiver.source + '.end_with?(' +
71
- to_string_literal(regex_str) + ')'
72
- corrector.replace(node.source_range, new_source)
73
- end
66
+ new_source = "#{receiver.source}.end_with?(#{to_string_literal(regex_str)})"
67
+
68
+ corrector.replace(node.source_range, new_source)
74
69
  end
75
70
  end
71
+ alias on_match_with_lvasgn on_send
76
72
  end
77
73
  end
78
74
  end
@@ -45,8 +45,9 @@ module RuboCop
45
45
  # waldo = { a: corge, b: grault }
46
46
  # waldo.size
47
47
  #
48
- class FixedSize < Cop
48
+ class FixedSize < Base
49
49
  MSG = 'Do not compute the size of statically sized objects.'
50
+ RESTRICT_ON_SEND = %i[count length size].freeze
50
51
 
51
52
  def_node_matcher :counter, <<~MATCHER
52
53
  (send ${array hash str sym} {:count :length :size} $...)
@@ -14,10 +14,12 @@ 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 < Cop
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`.'
22
+ RESTRICT_ON_SEND = %i[flatten flatten!].freeze
21
23
  FLATTEN_MULTIPLE_LEVELS = ' Beware, `flat_map` only flattens 1 level ' \
22
24
  'and `flatten` can be used to flatten ' \
23
25
  'multiple levels.'
@@ -44,25 +46,11 @@ module RuboCop
44
46
  end
45
47
  end
46
48
 
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
49
  private
63
50
 
64
51
  def offense_for_levels(node, map_node, first_method, flatten)
65
52
  message = MSG + FLATTEN_MULTIPLE_LEVELS
53
+
66
54
  register_offense(node, map_node, first_method, flatten, message)
67
55
  end
68
56
 
@@ -71,13 +59,24 @@ module RuboCop
71
59
  end
72
60
 
73
61
  def register_offense(node, map_node, first_method, flatten, message)
74
- range = range_between(map_node.loc.selector.begin_pos,
75
- node.loc.expression.end_pos)
62
+ range = range_between(map_node.loc.selector.begin_pos, node.loc.expression.end_pos)
63
+ message = format(message, method: first_method, flatten: flatten)
64
+
65
+ add_offense(range, message: message) do |corrector|
66
+ autocorrect(corrector, node)
67
+ end
68
+ end
69
+
70
+ def autocorrect(corrector, node)
71
+ map_node, _first_method, _flatten, params = flat_map_candidate?(node)
72
+ flatten_level, = *params.first
73
+
74
+ return unless flatten_level
75
+
76
+ range = range_between(node.loc.dot.begin_pos, node.source_range.end_pos)
76
77
 
77
- add_offense(node,
78
- location: range,
79
- message: format(message, method: first_method,
80
- flatten: flatten))
78
+ corrector.remove(range)
79
+ corrector.replace(map_node.loc.selector, 'flat_map')
81
80
  end
82
81
  end
83
82
  end
@@ -36,7 +36,11 @@ 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 < Cop
39
+ class InefficientHashSearch < Base
40
+ extend AutoCorrector
41
+
42
+ RESTRICT_ON_SEND = %i[include?].freeze
43
+
40
44
  def_node_matcher :inefficient_include?, <<~PATTERN
41
45
  (send (send $_ {:keys :values}) :include? _)
42
46
  PATTERN
@@ -45,19 +49,16 @@ module RuboCop
45
49
  inefficient_include?(node) do |receiver|
46
50
  return if receiver.nil?
47
51
 
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
- )
52
+ message = message(node)
53
+ add_offense(node, message: message) 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
61
62
  end
62
63
  end
63
64
 
@@ -0,0 +1,112 @@
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
+ RESTRICT_ON_SEND = (Enumerable.instance_methods + [:each]).freeze
33
+
34
+ def_node_matcher :readlines_on_class?, <<~PATTERN
35
+ $(send $(send (const nil? {:IO :File}) :readlines ...) _)
36
+ PATTERN
37
+
38
+ def_node_matcher :readlines_on_instance?, <<~PATTERN
39
+ $(send $(send ${nil? !const_type?} :readlines ...) _ ...)
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 autocorrect(corrector, enumerable_call, readlines_call, receiver)
59
+ # We cannot safely correct `.readlines` method called on IO/File classes
60
+ # due to its signature and we are not sure with implicit receiver
61
+ # if it is called in the context of some instance or mentioned class.
62
+ return if receiver.nil?
63
+
64
+ range = correction_range(enumerable_call, readlines_call)
65
+
66
+ if readlines_call.arguments?
67
+ call_args = build_call_args(readlines_call.arguments)
68
+ replacement = "each_line(#{call_args})"
69
+ else
70
+ replacement = 'each_line'
71
+ end
72
+
73
+ corrector.replace(range, replacement)
74
+ end
75
+
76
+ def offense_range(enumerable_call, readlines_call)
77
+ readlines_pos = readlines_call.loc.selector.begin_pos
78
+ enumerable_pos = enumerable_call.loc.selector.end_pos
79
+ range_between(readlines_pos, enumerable_pos)
80
+ end
81
+
82
+ def build_good_method(enumerable_call)
83
+ if enumerable_call.method?(:each)
84
+ 'each_line'
85
+ else
86
+ "each_line.#{enumerable_call.method_name}"
87
+ end
88
+ end
89
+
90
+ def build_bad_method(enumerable_call)
91
+ "readlines.#{enumerable_call.method_name}"
92
+ end
93
+
94
+ def correction_range(enumerable_call, readlines_call)
95
+ begin_pos = readlines_call.loc.selector.begin_pos
96
+
97
+ end_pos = if enumerable_call.method?(:each)
98
+ enumerable_call.loc.expression.end_pos
99
+ else
100
+ enumerable_call.loc.dot.begin_pos
101
+ end
102
+
103
+ range_between(begin_pos, end_pos)
104
+ end
105
+
106
+ def build_call_args(call_args_node)
107
+ call_args_node.map(&:source).join(', ')
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # This cop identifies places where methods are converted to blocks, with the
7
+ # use of `&method`, and passed as arguments to method calls.
8
+ # It is faster to replace those with explicit blocks, calling those methods inside.
9
+ #
10
+ # @example
11
+ # # bad
12
+ # array.map(&method(:do_something))
13
+ # [1, 2, 3].each(&out.method(:puts))
14
+ #
15
+ # # good
16
+ # array.map { |x| do_something(x) }
17
+ # [1, 2, 3].each { |x| out.puts(x) }
18
+ #
19
+ class MethodObjectAsBlock < Base
20
+ MSG = 'Use block explicitly instead of block-passing a method object.'
21
+
22
+ def_node_matcher :method_object_as_argument?, <<~PATTERN
23
+ (^send (send _ :method sym))
24
+ PATTERN
25
+
26
+ def on_block_pass(node)
27
+ add_offense(node) if method_object_as_argument?(node)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -27,9 +27,10 @@ module RuboCop
27
27
  # end
28
28
  # end
29
29
  #
30
- class OpenStruct < Cop
30
+ class OpenStruct < Base
31
31
  MSG = 'Consider using `Struct` over `OpenStruct` ' \
32
32
  'to optimize the performance.'
33
+ RESTRICT_ON_SEND = %i[new].freeze
33
34
 
34
35
  def_node_matcher :open_struct, <<~PATTERN
35
36
  (send (const {nil? cbase} :OpenStruct) :new ...)
@@ -37,7 +38,7 @@ module RuboCop
37
38
 
38
39
  def on_send(node)
39
40
  open_struct(node) do
40
- add_offense(node, location: :selector)
41
+ add_offense(node.loc.selector)
41
42
  end
42
43
  end
43
44
  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,8 +24,11 @@ module RuboCop
23
24
  # # the desired result:
24
25
  #
25
26
  # ('a'..'z').cover?('yellow') # => true
26
- class RangeInclude < Cop
27
- MSG = 'Use `Range#cover?` instead of `Range#include?`.'
27
+ class RangeInclude < Base
28
+ extend AutoCorrector
29
+
30
+ MSG = 'Use `Range#cover?` instead of `Range#%<bad_method>s`.'
31
+ RESTRICT_ON_SEND = %i[include? member?].freeze
28
32
 
29
33
  # TODO: If we traced out assignments of variables to their uses, we
30
34
  # might pick up on a few more instances of this issue
@@ -32,17 +36,17 @@ module RuboCop
32
36
  # (We don't even catch it if the Range is in double parens)
33
37
 
34
38
  def_node_matcher :range_include, <<~PATTERN
35
- (send {irange erange (begin {irange erange})} :include? ...)
39
+ (send {irange erange (begin {irange erange})} ${:include? :member?} ...)
36
40
  PATTERN
37
41
 
38
42
  def on_send(node)
39
- return unless range_include(node)
40
-
41
- add_offense(node, location: :selector)
42
- end
43
+ range_include(node) do |bad_method|
44
+ message = format(MSG, bad_method: bad_method)
43
45
 
44
- def autocorrect(node)
45
- ->(corrector) { corrector.replace(node.loc.selector, 'cover?') }
46
+ add_offense(node.loc.selector, message: message) do |corrector|
47
+ corrector.replace(node.loc.selector, 'cover?')
48
+ end
49
+ end
46
50
  end
47
51
  end
48
52
  end
@@ -22,7 +22,9 @@ module RuboCop
22
22
  # def another
23
23
  # yield 1, 2, 3
24
24
  # end
25
- class RedundantBlockCall < Cop
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 = '('
@@ -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,19 +73,18 @@ 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
- private
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)
77
82
 
78
- calls = to_enum(:blockarg_calls, body, argname)
83
+ blockarg_calls(body, argname).map do |call|
84
+ return [] if args_include_block_pass?(call)
79
85
 
80
- return [] if calls.any? { |call| args_include_block_pass?(call) }
81
-
82
- calls
86
+ call
87
+ end
83
88
  end
84
89
 
85
90
  def args_include_block_pass?(blockcall)