rubocop-performance 1.5.2 → 1.8.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 (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 +64 -32
  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 +134 -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 +41 -11
@@ -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 `String#end_with?`
7
- # would suffice.
6
+ # This cop identifies unnecessary use of a regex where `String#end_with?` would suffice.
7
+ #
8
+ # This cop has `SafeMultiline` configuration option that `true` by default because
9
+ # `end$` is unsafe as it will behave incompatible with `end_with?`
10
+ # for receiver is multiline string.
8
11
  #
9
12
  # @example
10
13
  # # bad
@@ -17,44 +20,54 @@ module RuboCop
17
20
  #
18
21
  # # good
19
22
  # 'abc'.end_with?('bc')
20
- class EndWith < Cop
23
+ #
24
+ # @example SafeMultiline: true (default)
25
+ #
26
+ # # good
27
+ # 'abc'.match?(/bc$/)
28
+ # /bc$/.match?('abc')
29
+ # 'abc' =~ /bc$/
30
+ # /bc$/ =~ 'abc'
31
+ # 'abc'.match(/bc$/)
32
+ # /bc$/.match('abc')
33
+ #
34
+ # @example SafeMultiline: false
35
+ #
36
+ # # bad
37
+ # 'abc'.match?(/bc$/)
38
+ # /bc$/.match?('abc')
39
+ # 'abc' =~ /bc$/
40
+ # /bc$/ =~ 'abc'
41
+ # 'abc'.match(/bc$/)
42
+ # /bc$/.match('abc')
43
+ #
44
+ class EndWith < Base
45
+ include RegexpMetacharacter
46
+ extend AutoCorrector
47
+
21
48
  MSG = 'Use `String#end_with?` instead of a regex match anchored to ' \
22
49
  'the end 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_end?) (regopt)))
27
53
  (send (regexp (str $#literal_at_end?) (regopt)) {:match :match?} $_)
28
54
  (match-with-lvasgn (regexp (str $#literal_at_end?) (regopt)) $_)}
29
55
  PATTERN
30
56
 
31
- def literal_at_end?(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 end of the string?
35
- regex_str =~ /\A(?:#{LITERAL_REGEX})+\\z\z/
36
- end
37
-
38
57
  def on_send(node)
39
- return unless redundant_regex?(node)
40
-
41
- add_offense(node)
42
- end
43
- alias on_match_with_lvasgn on_send
58
+ return unless (receiver, regex_str = redundant_regex?(node))
44
59
 
45
- def autocorrect(node)
46
- redundant_regex?(node) do |receiver, regex_str|
60
+ add_offense(node) do |corrector|
47
61
  receiver, regex_str = regex_str, receiver if receiver.is_a?(String)
48
- regex_str = regex_str[0..-3] # drop \Z anchor
62
+ regex_str = drop_end_metacharacter(regex_str)
49
63
  regex_str = interpret_string_escapes(regex_str)
50
64
 
51
- lambda do |corrector|
52
- new_source = receiver.source + '.end_with?(' +
53
- to_string_literal(regex_str) + ')'
54
- corrector.replace(node.source_range, new_source)
55
- end
65
+ new_source = "#{receiver.source}.end_with?(#{to_string_literal(regex_str)})"
66
+
67
+ corrector.replace(node.source_range, new_source)
56
68
  end
57
69
  end
70
+ alias on_match_with_lvasgn on_send
58
71
  end
59
72
  end
60
73
  end
@@ -45,10 +45,10 @@ 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
50
 
51
- def_node_matcher :counter, <<-MATCHER
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 < 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`.'
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?, <<-PATTERN
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
- node.loc.expression.end_pos)
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
- add_offense(node,
78
- location: range,
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 < Cop
40
- def_node_matcher :inefficient_include?, <<-PATTERN
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
- 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
- )
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 < Cop
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, <<-PATTERN
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, location: :selector)
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 < 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`.'
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, <<-PATTERN
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
- return unless range_include(node)
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
- def autocorrect(node)
45
- ->(corrector) { corrector.replace(node.loc.selector, 'cover?') }
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 < 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 = '('
29
31
  CLOSE_PAREN = ')'
30
32
  SPACE = ' '
31
33
 
32
- def_node_matcher :blockarg_def, <<-PATTERN
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, <<-PATTERN
39
+ def_node_search :blockarg_calls, <<~PATTERN
38
40
  (send (lvar %1) :call ...)
39
41
  PATTERN
40
42
 
41
- def_node_search :blockarg_assigned?, <<-PATTERN
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
- 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)