rubocop-performance 1.7.0 → 1.9.1

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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +8 -0
  3. data/config/default.yml +49 -7
  4. data/lib/rubocop/cop/mixin/regexp_metacharacter.rb +4 -4
  5. data/lib/rubocop/cop/performance/ancestors_include.rb +16 -12
  6. data/lib/rubocop/cop/performance/array_semi_infinite_range_slice.rb +77 -0
  7. data/lib/rubocop/cop/performance/big_decimal_with_numeric_argument.rb +17 -14
  8. data/lib/rubocop/cop/performance/bind_call.rb +9 -18
  9. data/lib/rubocop/cop/performance/block_given_with_explicit_block.rb +52 -0
  10. data/lib/rubocop/cop/performance/caller.rb +14 -15
  11. data/lib/rubocop/cop/performance/case_when_splat.rb +18 -11
  12. data/lib/rubocop/cop/performance/casecmp.rb +13 -20
  13. data/lib/rubocop/cop/performance/chain_array_allocation.rb +24 -28
  14. data/lib/rubocop/cop/performance/collection_literal_in_loop.rb +140 -0
  15. data/lib/rubocop/cop/performance/compare_with_block.rb +10 -21
  16. data/lib/rubocop/cop/performance/constant_regexp.rb +68 -0
  17. data/lib/rubocop/cop/performance/count.rb +14 -16
  18. data/lib/rubocop/cop/performance/delete_prefix.rb +14 -22
  19. data/lib/rubocop/cop/performance/delete_suffix.rb +14 -22
  20. data/lib/rubocop/cop/performance/detect.rb +65 -32
  21. data/lib/rubocop/cop/performance/double_start_end_with.rb +16 -24
  22. data/lib/rubocop/cop/performance/end_with.rb +9 -13
  23. data/lib/rubocop/cop/performance/fixed_size.rb +2 -1
  24. data/lib/rubocop/cop/performance/flat_map.rb +21 -22
  25. data/lib/rubocop/cop/performance/inefficient_hash_search.rb +15 -14
  26. data/lib/rubocop/cop/performance/io_readlines.rb +27 -42
  27. data/lib/rubocop/cop/performance/method_object_as_block.rb +32 -0
  28. data/lib/rubocop/cop/performance/open_struct.rb +3 -2
  29. data/lib/rubocop/cop/performance/range_include.rb +8 -6
  30. data/lib/rubocop/cop/performance/redundant_block_call.rb +15 -10
  31. data/lib/rubocop/cop/performance/redundant_match.rb +12 -6
  32. data/lib/rubocop/cop/performance/redundant_merge.rb +19 -17
  33. data/lib/rubocop/cop/performance/redundant_sort_block.rb +6 -16
  34. data/lib/rubocop/cop/performance/redundant_string_chars.rb +10 -18
  35. data/lib/rubocop/cop/performance/regexp_match.rb +20 -20
  36. data/lib/rubocop/cop/performance/reverse_each.rb +10 -5
  37. data/lib/rubocop/cop/performance/reverse_first.rb +5 -10
  38. data/lib/rubocop/cop/performance/size.rb +7 -6
  39. data/lib/rubocop/cop/performance/sort_reverse.rb +6 -15
  40. data/lib/rubocop/cop/performance/squeeze.rb +8 -11
  41. data/lib/rubocop/cop/performance/start_with.rb +9 -13
  42. data/lib/rubocop/cop/performance/string_include.rb +13 -14
  43. data/lib/rubocop/cop/performance/string_replacement.rb +24 -27
  44. data/lib/rubocop/cop/performance/sum.rb +236 -0
  45. data/lib/rubocop/cop/performance/times_map.rb +12 -18
  46. data/lib/rubocop/cop/performance/unfreeze_string.rb +20 -2
  47. data/lib/rubocop/cop/performance/uri_default_parser.rb +7 -12
  48. data/lib/rubocop/cop/performance_cops.rb +6 -0
  49. data/lib/rubocop/performance/version.rb +6 -1
  50. metadata +25 -13
@@ -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
 
@@ -24,68 +24,53 @@ module RuboCop
24
24
  # file.each_line.find { |l| l.start_with?('#') }
25
25
  # file.each_line { |l| puts l }
26
26
  #
27
- class IoReadlines < Cop
27
+ class IoReadlines < Base
28
28
  include RangeHelp
29
+ extend AutoCorrector
29
30
 
30
31
  MSG = 'Use `%<good>s` instead of `%<bad>s`.'
31
- ENUMERABLE_METHODS = (Enumerable.instance_methods + [:each]).freeze
32
+ RESTRICT_ON_SEND = (Enumerable.instance_methods + [:each]).freeze
32
33
 
33
34
  def_node_matcher :readlines_on_class?, <<~PATTERN
34
- $(send $(send (const nil? {:IO :File}) :readlines ...) #enumerable_method?)
35
+ $(send $(send (const nil? {:IO :File}) :readlines ...) _)
35
36
  PATTERN
36
37
 
37
38
  def_node_matcher :readlines_on_instance?, <<~PATTERN
38
- $(send $(send ${nil? !const_type?} :readlines ...) #enumerable_method? ...)
39
+ $(send $(send ${nil? !const_type?} :readlines ...) _ ...)
39
40
  PATTERN
40
41
 
41
42
  def on_send(node)
42
- readlines_on_class?(node) do |enumerable_call, readlines_call|
43
- offense(node, enumerable_call, readlines_call)
44
- end
43
+ return unless (captured_values = readlines_on_class?(node) || readlines_on_instance?(node))
45
44
 
46
- readlines_on_instance?(node) do |enumerable_call, readlines_call, _|
47
- offense(node, enumerable_call, readlines_call)
48
- end
49
- end
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
50
 
51
- def autocorrect(node)
52
- readlines_on_instance?(node) do |enumerable_call, readlines_call, receiver|
53
- # We cannot safely correct `.readlines` method called on IO/File classes
54
- # due to its signature and we are not sure with implicit receiver
55
- # if it is called in the context of some instance or mentioned class.
56
- return if receiver.nil?
57
-
58
- lambda do |corrector|
59
- range = correction_range(enumerable_call, readlines_call)
60
-
61
- if readlines_call.arguments?
62
- call_args = build_call_args(readlines_call.arguments)
63
- replacement = "each_line(#{call_args})"
64
- else
65
- replacement = 'each_line'
66
- end
67
-
68
- corrector.replace(range, replacement)
69
- end
51
+ add_offense(range, message: format(MSG, good: good_method, bad: bad_method)) do |corrector|
52
+ autocorrect(corrector, enumerable_call, readlines_call, receiver)
70
53
  end
71
54
  end
72
55
 
73
56
  private
74
57
 
75
- def enumerable_method?(node)
76
- ENUMERABLE_METHODS.include?(node.to_sym)
77
- end
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?
78
63
 
79
- def offense(node, enumerable_call, readlines_call)
80
- range = offense_range(enumerable_call, readlines_call)
81
- good_method = build_good_method(enumerable_call)
82
- bad_method = build_bad_method(enumerable_call)
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
83
72
 
84
- add_offense(
85
- node,
86
- location: range,
87
- message: format(MSG, good: good_method, bad: bad_method)
88
- )
73
+ corrector.replace(range, replacement)
89
74
  end
90
75
 
91
76
  def offense_range(enumerable_call, readlines_call)
@@ -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
@@ -24,8 +24,11 @@ module RuboCop
24
24
  # # the desired result:
25
25
  #
26
26
  # ('a'..'z').cover?('yellow') # => true
27
- class RangeInclude < Cop
27
+ class RangeInclude < Base
28
+ extend AutoCorrector
29
+
28
30
  MSG = 'Use `Range#cover?` instead of `Range#%<bad_method>s`.'
31
+ RESTRICT_ON_SEND = %i[include? member?].freeze
29
32
 
30
33
  # TODO: If we traced out assignments of variables to their uses, we
31
34
  # might pick up on a few more instances of this issue
@@ -39,12 +42,11 @@ module RuboCop
39
42
  def on_send(node)
40
43
  range_include(node) do |bad_method|
41
44
  message = format(MSG, bad_method: bad_method)
42
- add_offense(node, location: :selector, message: message)
43
- end
44
- end
45
45
 
46
- def autocorrect(node)
47
- ->(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
48
50
  end
49
51
  end
50
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)
@@ -17,9 +17,12 @@ module RuboCop
17
17
  # # good
18
18
  # method(str =~ /regex/)
19
19
  # return value unless regex =~ 'str'
20
- class RedundantMatch < Cop
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.'
25
+ RESTRICT_ON_SEND = %i[match].freeze
23
26
 
24
27
  # 'match' is a fairly generic name, so we don't flag it unless we see
25
28
  # a string or regexp literal on one side or the other
@@ -37,18 +40,21 @@ module RuboCop
37
40
  (!node.value_used? || only_truthiness_matters?(node)) &&
38
41
  !(node.parent && node.parent.block_type?)
39
42
 
40
- add_offense(node)
43
+ add_offense(node) do |corrector|
44
+ autocorrect(corrector, node)
45
+ end
41
46
  end
42
47
 
43
- def autocorrect(node)
48
+ private
49
+
50
+ def autocorrect(corrector, node)
44
51
  # Regexp#match can take a second argument, but this cop doesn't
45
52
  # register an offense in that case
46
53
  return unless node.first_argument.regexp_type?
47
54
 
48
- new_source =
49
- node.receiver.source + ' =~ ' + node.first_argument.source
55
+ new_source = "#{node.receiver.source} =~ #{node.first_argument.source}"
50
56
 
51
- ->(corrector) { corrector.replace(node.source_range, new_source) }
57
+ corrector.replace(node.source_range, new_source)
52
58
  end
53
59
  end
54
60
  end
@@ -24,9 +24,12 @@ module RuboCop
24
24
  # # good
25
25
  # hash[:a] = 1
26
26
  # hash[:b] = 2
27
- class RedundantMerge < Cop
27
+ class RedundantMerge < Base
28
+ extend AutoCorrector
29
+
28
30
  AREF_ASGN = '%<receiver>s[%<key>s] = %<value>s'
29
31
  MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
32
+ RESTRICT_ON_SEND = %i[merge!].freeze
30
33
 
31
34
  WITH_MODIFIER_CORRECTION = <<~RUBY
32
35
  %<keyword>s %<condition>s
@@ -44,18 +47,17 @@ module RuboCop
44
47
 
45
48
  def on_send(node)
46
49
  each_redundant_merge(node) do |redundant_merge_node|
47
- add_offense(redundant_merge_node)
48
- end
49
- end
50
-
51
- def autocorrect(node)
52
- redundant_merge_candidate(node) do |receiver, pairs|
53
- new_source = to_assignments(receiver, pairs).join("\n")
54
-
55
- if node.parent && pairs.size > 1
56
- correct_multiple_elements(node, node.parent, new_source)
57
- else
58
- correct_single_element(node, new_source)
50
+ message = message(node)
51
+ add_offense(redundant_merge_node, message: message) do |corrector|
52
+ redundant_merge_candidate(node) do |receiver, pairs|
53
+ new_source = to_assignments(receiver, pairs).join("\n")
54
+
55
+ if node.parent && pairs.size > 1
56
+ correct_multiple_elements(corrector, node, node.parent, new_source)
57
+ else
58
+ correct_single_element(corrector, node, new_source)
59
+ end
60
+ end
59
61
  end
60
62
  end
61
63
  end
@@ -98,7 +100,7 @@ module RuboCop
98
100
  !EachWithObjectInspector.new(node, receiver).value_used?
99
101
  end
100
102
 
101
- def correct_multiple_elements(node, parent, new_source)
103
+ def correct_multiple_elements(corrector, node, parent, new_source)
102
104
  if modifier_flow_control?(parent)
103
105
  new_source = rewrite_with_modifier(node, parent, new_source)
104
106
  node = parent
@@ -107,11 +109,11 @@ module RuboCop
107
109
  new_source.gsub!(/\n/, padding)
108
110
  end
109
111
 
110
- ->(corrector) { corrector.replace(node.source_range, new_source) }
112
+ corrector.replace(node.source_range, new_source)
111
113
  end
112
114
 
113
- def correct_single_element(node, new_source)
114
- ->(corrector) { corrector.replace(node.source_range, new_source) }
115
+ def correct_single_element(corrector, node, new_source)
116
+ corrector.replace(node.source_range, new_source)
115
117
  end
116
118
 
117
119
  def to_assignments(receiver, pairs)