rubocop-performance 1.7.1 → 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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +18 -7
  3. data/lib/rubocop/cop/performance/ancestors_include.rb +14 -13
  4. data/lib/rubocop/cop/performance/big_decimal_with_numeric_argument.rb +7 -12
  5. data/lib/rubocop/cop/performance/bind_call.rb +8 -18
  6. data/lib/rubocop/cop/performance/caller.rb +3 -2
  7. data/lib/rubocop/cop/performance/case_when_splat.rb +18 -11
  8. data/lib/rubocop/cop/performance/casecmp.rb +12 -20
  9. data/lib/rubocop/cop/performance/chain_array_allocation.rb +4 -10
  10. data/lib/rubocop/cop/performance/collection_literal_in_loop.rb +140 -0
  11. data/lib/rubocop/cop/performance/compare_with_block.rb +10 -21
  12. data/lib/rubocop/cop/performance/count.rb +13 -16
  13. data/lib/rubocop/cop/performance/delete_prefix.rb +13 -22
  14. data/lib/rubocop/cop/performance/delete_suffix.rb +13 -22
  15. data/lib/rubocop/cop/performance/detect.rb +29 -26
  16. data/lib/rubocop/cop/performance/double_start_end_with.rb +16 -24
  17. data/lib/rubocop/cop/performance/end_with.rb +8 -13
  18. data/lib/rubocop/cop/performance/fixed_size.rb +1 -1
  19. data/lib/rubocop/cop/performance/flat_map.rb +20 -22
  20. data/lib/rubocop/cop/performance/inefficient_hash_search.rb +13 -14
  21. data/lib/rubocop/cop/performance/io_readlines.rb +25 -36
  22. data/lib/rubocop/cop/performance/open_struct.rb +2 -2
  23. data/lib/rubocop/cop/performance/range_include.rb +7 -6
  24. data/lib/rubocop/cop/performance/redundant_block_call.rb +11 -6
  25. data/lib/rubocop/cop/performance/redundant_match.rb +11 -6
  26. data/lib/rubocop/cop/performance/redundant_merge.rb +18 -17
  27. data/lib/rubocop/cop/performance/redundant_sort_block.rb +6 -16
  28. data/lib/rubocop/cop/performance/redundant_string_chars.rb +8 -12
  29. data/lib/rubocop/cop/performance/regexp_match.rb +20 -20
  30. data/lib/rubocop/cop/performance/reverse_each.rb +9 -5
  31. data/lib/rubocop/cop/performance/reverse_first.rb +4 -10
  32. data/lib/rubocop/cop/performance/size.rb +6 -6
  33. data/lib/rubocop/cop/performance/sort_reverse.rb +6 -15
  34. data/lib/rubocop/cop/performance/squeeze.rb +6 -10
  35. data/lib/rubocop/cop/performance/start_with.rb +8 -13
  36. data/lib/rubocop/cop/performance/string_include.rb +9 -13
  37. data/lib/rubocop/cop/performance/string_replacement.rb +23 -27
  38. data/lib/rubocop/cop/performance/sum.rb +129 -0
  39. data/lib/rubocop/cop/performance/times_map.rb +11 -18
  40. data/lib/rubocop/cop/performance/unfreeze_string.rb +1 -1
  41. data/lib/rubocop/cop/performance/uri_default_parser.rb +6 -12
  42. data/lib/rubocop/cop/performance_cops.rb +2 -0
  43. data/lib/rubocop/performance/version.rb +1 -1
  44. metadata +6 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 34605a937c1a3e040428bee06e322220659c105b6e835423d9e0e73869fd83ee
4
- data.tar.gz: e3fa0103bd2518cec45334bc162e144963eef9e3d09ab9fd6bd7b2d35b654e84
3
+ metadata.gz: 00b98a3d2af1bcfd781dfab24e4a8deb118d29fdaedeca8b2d404ff03eab3529
4
+ data.tar.gz: cc5508a731d9c932d2c141892aefbe15f635d18bdd95ae6ad5b4998c931fcaf2
5
5
  SHA512:
6
- metadata.gz: 5fe72ab5a0370aa8e0cc434080a27a38d8979084c53c50cd18e8cbc0896572ddc9d011130d56283f9d588268253ec0e2e44fc32b81483e1458689c60c17e835b
7
- data.tar.gz: a60d627b0d1ded2fb6103a78fb4414fc1e1f1ff13ab9f4e82c399289c9a40c3c6e0385ee2509074175bd724755f768dd4625bb9791af993032a1f840a0533ef6
6
+ metadata.gz: 806f21556b5e791a6a00866714c4feec42726b20d4c82db7c4cd89ec576624eaac3007f4a68fca4fd633fbc3ed7a41801fdb0dfc64f767ee241582a4185bbb8b
7
+ data.tar.gz: 1e922a3d51a62df60cdb26f99960fec91e1fa558ceea751bcce7ec529f7d4db6e1644d0dfd24848cb75e384e08fd6a3f2afaaa8f531a1118a09d35d6fa1b0e21
@@ -49,6 +49,13 @@ Performance/ChainArrayAllocation:
49
49
  Enabled: false
50
50
  VersionAdded: '0.59'
51
51
 
52
+ Performance/CollectionLiteralInLoop:
53
+ Description: 'Extract Array and Hash literals outside of loops into local variables or constants.'
54
+ Enabled: true
55
+ VersionAdded: '1.8'
56
+ # Min number of elements to consider an offense
57
+ MinSize: 1
58
+
52
59
  Performance/CompareWithBlock:
53
60
  Description: 'Use `sort_by(&:foo)` instead of `sort { |a, b| a.foo <=> b.foo }`.'
54
61
  Enabled: true
@@ -56,16 +63,14 @@ Performance/CompareWithBlock:
56
63
 
57
64
  Performance/Count:
58
65
  Description: >-
59
- Use `count` instead of `select...size`, `reject...size`,
60
- `select...count`, `reject...count`, `select...length`,
61
- and `reject...length`.
66
+ Use `count` instead of `{select,find_all,filter,reject}...{size,count,length}`.
62
67
  # This cop has known compatibility issues with `ActiveRecord` and other
63
68
  # frameworks. ActiveRecord's `count` ignores the block that is passed to it.
64
69
  # For more information, see the documentation in the cop itself.
65
70
  SafeAutoCorrect: false
66
71
  Enabled: true
67
72
  VersionAdded: '0.31'
68
- VersionChanged: '1.5'
73
+ VersionChanged: '1.8'
69
74
 
70
75
  Performance/DeletePrefix:
71
76
  Description: 'Use `delete_prefix` instead of `gsub`.'
@@ -81,8 +86,8 @@ Performance/DeleteSuffix:
81
86
 
82
87
  Performance/Detect:
83
88
  Description: >-
84
- Use `detect` instead of `select.first`, `find_all.first`,
85
- `select.last`, and `find_all.last`.
89
+ Use `detect` instead of `select.first`, `find_all.first`, `filter.first`,
90
+ `select.last`, `find_all.last`, and `filter.last`.
86
91
  Reference: 'https://github.com/JuanitoFatas/fast-ruby#enumerabledetect-vs-enumerableselectfirst-code'
87
92
  # This cop has known compatibility issues with `ActiveRecord` and other
88
93
  # frameworks. `ActiveRecord` does not implement a `detect` method and `find`
@@ -91,7 +96,7 @@ Performance/Detect:
91
96
  SafeAutoCorrect: false
92
97
  Enabled: true
93
98
  VersionAdded: '0.30'
94
- VersionChanged: '1.5'
99
+ VersionChanged: '1.8'
95
100
 
96
101
  Performance/DoubleStartEndWith:
97
102
  Description: >-
@@ -261,6 +266,12 @@ Performance/StringReplacement:
261
266
  Enabled: true
262
267
  VersionAdded: '0.33'
263
268
 
269
+ Performance/Sum:
270
+ Description: 'Use `sum` instead of a custom array summation.'
271
+ Reference: 'https://blog.bigbinary.com/2016/11/02/ruby-2-4-introduces-enumerable-sum.html'
272
+ Enabled: 'pending'
273
+ VersionAdded: '1.8'
274
+
264
275
  Performance/TimesMap:
265
276
  Description: 'Checks for .times.map calls.'
266
277
  AutoCorrect: false
@@ -13,8 +13,9 @@ module RuboCop
13
13
  # # good
14
14
  # A <= B
15
15
  #
16
- class AncestorsInclude < Cop
16
+ class AncestorsInclude < Base
17
17
  include RangeHelp
18
+ extend AutoCorrector
18
19
 
19
20
  MSG = 'Use `<=` instead of `ancestors.include?`.'
20
21
 
@@ -23,23 +24,23 @@ module RuboCop
23
24
  PATTERN
24
25
 
25
26
  def on_send(node)
26
- return unless ancestors_include_candidate?(node)
27
+ return unless (subclass, superclass = ancestors_include_candidate?(node))
28
+ return if subclass && !subclass.const_type?
27
29
 
28
- location_of_ancestors = node.children[0].loc.selector.begin_pos
29
- end_location = node.loc.selector.end_pos
30
- range = range_between(location_of_ancestors, end_location)
30
+ add_offense(range(node)) do |corrector|
31
+ subclass_source = subclass ? subclass.source : 'self'
31
32
 
32
- add_offense(node, location: range)
33
+ corrector.replace(node, "#{subclass_source} <= #{superclass.source}")
34
+ end
33
35
  end
34
36
 
35
- def autocorrect(node)
36
- ancestors_include_candidate?(node) do |subclass, superclass|
37
- lambda do |corrector|
38
- subclass_source = subclass ? subclass.source : 'self'
37
+ private
39
38
 
40
- corrector.replace(node, "#{subclass_source} <= #{superclass.source}")
41
- end
42
- end
39
+ def range(node)
40
+ location_of_ancestors = node.children[0].loc.selector.begin_pos
41
+ end_location = node.loc.selector.end_pos
42
+
43
+ range_between(location_of_ancestors, end_location)
43
44
  end
44
45
  end
45
46
  end
@@ -16,7 +16,9 @@ module RuboCop
16
16
  # BigDecimal('1', 2)
17
17
  # BigDecimal('1.2', 3, exception: true)
18
18
  #
19
- class BigDecimalWithNumericArgument < Cop
19
+ class BigDecimalWithNumericArgument < Base
20
+ extend AutoCorrector
21
+
20
22
  MSG = 'Convert numeric argument to string before passing to `BigDecimal`.'
21
23
 
22
24
  def_node_matcher :big_decimal_with_numeric_argument?, <<~PATTERN
@@ -24,18 +26,11 @@ module RuboCop
24
26
  PATTERN
25
27
 
26
28
  def on_send(node)
27
- big_decimal_with_numeric_argument?(node) do |numeric|
28
- next if numeric.float_type? && specifies_precision?(node)
29
-
30
- add_offense(node, location: numeric.source_range)
31
- end
32
- end
29
+ return unless (numeric = big_decimal_with_numeric_argument?(node))
30
+ return if numeric.float_type? && specifies_precision?(node)
33
31
 
34
- def autocorrect(node)
35
- big_decimal_with_numeric_argument?(node) do |numeric|
36
- lambda do |corrector|
37
- corrector.wrap(numeric, "'", "'")
38
- end
32
+ add_offense(numeric.source_range) do |corrector|
33
+ corrector.wrap(numeric, "'", "'")
39
34
  end
40
35
  end
41
36
 
@@ -19,8 +19,9 @@ module RuboCop
19
19
  # # good
20
20
  # umethod.bind_call(obj, foo, bar)
21
21
  #
22
- class BindCall < Cop
22
+ class BindCall < Base
23
23
  include RangeHelp
24
+ extend AutoCorrector
24
25
  extend TargetRubyVersion
25
26
 
26
27
  minimum_target_ruby_version 2.7
@@ -37,28 +38,17 @@ module RuboCop
37
38
  PATTERN
38
39
 
39
40
  def on_send(node)
40
- bind_with_call_method?(node) do |receiver, bind_arg, call_args_node|
41
- range = correction_range(receiver, node)
42
-
43
- call_args = build_call_args(call_args_node)
44
-
45
- message = message(bind_arg.source, call_args)
46
-
47
- add_offense(node, location: range, message: message)
48
- end
49
- end
50
-
51
- def autocorrect(node)
52
- receiver, bind_arg, call_args_node = bind_with_call_method?(node)
41
+ return unless (receiver, bind_arg, call_args_node = bind_with_call_method?(node))
53
42
 
54
43
  range = correction_range(receiver, node)
55
-
56
44
  call_args = build_call_args(call_args_node)
57
- call_args = ", #{call_args}" unless call_args.empty?
45
+ message = message(bind_arg.source, call_args)
46
+
47
+ add_offense(range, message: message) do |corrector|
48
+ call_args = ", #{call_args}" unless call_args.empty?
58
49
 
59
- replacement_method = "bind_call(#{bind_arg.source}#{call_args})"
50
+ replacement_method = "bind_call(#{bind_arg.source}#{call_args})"
60
51
 
61
- lambda do |corrector|
62
52
  corrector.replace(range, replacement_method)
63
53
  end
64
54
  end
@@ -18,7 +18,7 @@ module RuboCop
18
18
  # caller(1..1).first
19
19
  # caller_locations(2..2).first
20
20
  # caller_locations(1..1).first
21
- class Caller < Cop
21
+ class Caller < Base
22
22
  MSG_BRACE = 'Use `%<method>s(%<n>d..%<n>d).first`' \
23
23
  ' instead of `%<method>s[%<m>d]`.'
24
24
  MSG_FIRST = 'Use `%<method>s(%<n>d..%<n>d).first`' \
@@ -41,7 +41,8 @@ module RuboCop
41
41
  def on_send(node)
42
42
  return unless caller_with_scope_method?(node)
43
43
 
44
- add_offense(node)
44
+ message = message(node)
45
+ add_offense(node, message: message)
45
46
  end
46
47
 
47
48
  private
@@ -53,9 +53,10 @@ module RuboCop
53
53
  # when 5
54
54
  # baz
55
55
  # end
56
- class CaseWhenSplat < Cop
56
+ class CaseWhenSplat < Base
57
57
  include Alignment
58
58
  include RangeHelp
59
+ extend AutoCorrector
59
60
 
60
61
  MSG = 'Reordering `when` conditions with a splat to the end ' \
61
62
  'of the `when` branches can improve performance.'
@@ -66,24 +67,30 @@ module RuboCop
66
67
  when_conditions = case_node.when_branches.flat_map(&:conditions)
67
68
 
68
69
  splat_offenses(when_conditions).reverse_each do |condition|
69
- range = condition.parent.loc.keyword.join(condition.source_range)
70
+ next if ignored_node?(condition.parent)
71
+
72
+ ignore_node(condition.parent)
70
73
  variable, = *condition
71
74
  message = variable.array_type? ? ARRAY_MSG : MSG
72
- add_offense(condition.parent, location: range, message: message)
75
+ add_offense(range(condition), message: message) do |corrector|
76
+ autocorrect(corrector, condition.parent)
77
+ end
73
78
  end
74
79
  end
75
80
 
76
- def autocorrect(when_node)
77
- lambda do |corrector|
78
- if needs_reorder?(when_node)
79
- reorder_condition(corrector, when_node)
80
- else
81
- inline_fix_branch(corrector, when_node)
82
- end
81
+ private
82
+
83
+ def autocorrect(corrector, when_node)
84
+ if needs_reorder?(when_node)
85
+ reorder_condition(corrector, when_node)
86
+ else
87
+ inline_fix_branch(corrector, when_node)
83
88
  end
84
89
  end
85
90
 
86
- private
91
+ def range(node)
92
+ node.parent.loc.keyword.join(node.source_range)
93
+ end
87
94
 
88
95
  def replacement(conditions)
89
96
  reordered = conditions.partition(&:splat_type?).reverse
@@ -19,7 +19,9 @@ module RuboCop
19
19
  # # good
20
20
  # str.casecmp('ABC').zero?
21
21
  # 'abc'.casecmp(str).zero?
22
- class Casecmp < Cop
22
+ class Casecmp < Base
23
+ extend AutoCorrector
24
+
23
25
  MSG = 'Use `%<good>s` instead of `%<bad>s`.'
24
26
  CASE_METHODS = %i[downcase upcase].freeze
25
27
 
@@ -48,21 +50,13 @@ module RuboCop
48
50
  return unless downcase_eq(node) || eq_downcase(node)
49
51
  return unless (parts = take_method_apart(node))
50
52
 
51
- _, _, arg, variable = parts
53
+ _receiver, method, arg, variable = parts
52
54
  good_method = build_good_method(arg, variable)
53
55
 
54
- add_offense(
55
- node,
56
- message: format(MSG, good: good_method, bad: node.source)
57
- )
58
- end
59
-
60
- def autocorrect(node)
61
- return unless (parts = take_method_apart(node))
62
-
63
- receiver, method, arg, variable = parts
64
-
65
- correction(node, receiver, method, arg, variable)
56
+ message = format(MSG, good: good_method, bad: node.source)
57
+ add_offense(node, message: message) do |corrector|
58
+ correction(corrector, node, method, arg, variable)
59
+ end
66
60
  end
67
61
 
68
62
  private
@@ -84,14 +78,12 @@ module RuboCop
84
78
  [receiver, method, arg, variable]
85
79
  end
86
80
 
87
- def correction(node, _receiver, method, arg, variable)
88
- lambda do |corrector|
89
- corrector.insert_before(node.loc.expression, '!') if method == :!=
81
+ def correction(corrector, node, method, arg, variable)
82
+ corrector.insert_before(node.loc.expression, '!') if method == :!=
90
83
 
91
- replacement = build_good_method(arg, variable)
84
+ replacement = build_good_method(arg, variable)
92
85
 
93
- corrector.replace(node.loc.expression, replacement)
94
- end
86
+ corrector.replace(node.loc.expression, replacement)
95
87
  end
96
88
 
97
89
  def build_good_method(arg, variable)
@@ -20,7 +20,7 @@ module RuboCop
20
20
  # array.flatten!
21
21
  # array.map! { |x| x.downcase }
22
22
  # array
23
- class ChainArrayAllocation < Cop
23
+ class ChainArrayAllocation < Base
24
24
  include RangeHelp
25
25
 
26
26
  # These methods return a new array but only sometimes. They must be
@@ -61,15 +61,9 @@ module RuboCop
61
61
 
62
62
  def on_send(node)
63
63
  flat_map_candidate?(node) do |fm, sm, _|
64
- range = range_between(
65
- node.loc.dot.begin_pos,
66
- node.source_range.end_pos
67
- )
68
- add_offense(
69
- node,
70
- location: range,
71
- message: format(MSG, method: fm, second_method: sm)
72
- )
64
+ range = range_between(node.loc.dot.begin_pos, node.source_range.end_pos)
65
+
66
+ add_offense(range, message: format(MSG, method: fm, second_method: sm))
73
67
  end
74
68
  end
75
69
  end
@@ -0,0 +1,140 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+
5
+ module RuboCop
6
+ module Cop
7
+ module Performance
8
+ # This cop identifies places where Array and Hash literals are used
9
+ # within loops. It is better to extract them into a local variable or constant
10
+ # to avoid unnecessary allocations on each iteration.
11
+ #
12
+ # You can set the minimum number of elements to consider
13
+ # an offense with `MinSize`.
14
+ #
15
+ # @example
16
+ # # bad
17
+ # users.select do |user|
18
+ # %i[superadmin admin].include?(user.role)
19
+ # end
20
+ #
21
+ # # good
22
+ # admin_roles = %i[superadmin admin]
23
+ # users.select do |user|
24
+ # admin_roles.include?(user.role)
25
+ # end
26
+ #
27
+ # # good
28
+ # ADMIN_ROLES = %i[superadmin admin]
29
+ # ...
30
+ # users.select do |user|
31
+ # ADMIN_ROLES.include?(user.role)
32
+ # end
33
+ #
34
+ class CollectionLiteralInLoop < Base
35
+ MSG = 'Avoid immutable %<literal_class>s literals in loops. '\
36
+ 'It is better to extract it into a local variable or a constant.'
37
+
38
+ POST_CONDITION_LOOP_TYPES = %i[while_post until_post].freeze
39
+ LOOP_TYPES = (POST_CONDITION_LOOP_TYPES + %i[while until for]).freeze
40
+
41
+ ENUMERABLE_METHOD_NAMES = (Enumerable.instance_methods + [:each]).to_set.freeze
42
+ NONMUTATING_ARRAY_METHODS = %i[& * + - <=> == [] all? any? assoc at
43
+ bsearch bsearch_index collect combination
44
+ compact count cycle deconstruct difference dig
45
+ drop drop_while each each_index empty? eql?
46
+ fetch filter find_index first flatten hash
47
+ include? index inspect intersection join
48
+ last length map max min minmax none? one? pack
49
+ permutation product rassoc reject
50
+ repeated_combination repeated_permutation reverse
51
+ reverse_each rindex rotate sample select shuffle
52
+ size slice sort sum take take_while
53
+ to_a to_ary to_h to_s transpose union uniq
54
+ values_at zip |].freeze
55
+
56
+ ARRAY_METHODS = (ENUMERABLE_METHOD_NAMES | NONMUTATING_ARRAY_METHODS).to_set.freeze
57
+
58
+ NONMUTATING_HASH_METHODS = %i[< <= == > >= [] any? assoc compact dig
59
+ each each_key each_pair each_value empty?
60
+ eql? fetch fetch_values filter flatten has_key?
61
+ has_value? hash include? inspect invert key key?
62
+ keys? length member? merge rassoc rehash reject
63
+ select size slice to_a to_h to_hash to_proc to_s
64
+ transform_keys transform_values value? values values_at].freeze
65
+
66
+ HASH_METHODS = (ENUMERABLE_METHOD_NAMES | NONMUTATING_HASH_METHODS).to_set.freeze
67
+
68
+ def_node_matcher :kernel_loop?, <<~PATTERN
69
+ (block
70
+ (send {nil? (const nil? :Kernel)} :loop)
71
+ ...)
72
+ PATTERN
73
+
74
+ def_node_matcher :enumerable_loop?, <<~PATTERN
75
+ (block
76
+ (send $_ #enumerable_method? ...)
77
+ ...)
78
+ PATTERN
79
+
80
+ def on_send(node)
81
+ receiver, method, = *node.children
82
+ return unless check_literal?(receiver, method) && parent_is_loop?(receiver)
83
+
84
+ message = format(MSG, literal_class: literal_class(receiver))
85
+ add_offense(receiver, message: message)
86
+ end
87
+
88
+ private
89
+
90
+ def check_literal?(node, method)
91
+ !node.nil? &&
92
+ nonmutable_method_of_array_or_hash?(node, method) &&
93
+ node.children.size >= min_size &&
94
+ node.recursive_basic_literal?
95
+ end
96
+
97
+ def nonmutable_method_of_array_or_hash?(node, method)
98
+ (node.array_type? && ARRAY_METHODS.include?(method)) ||
99
+ (node.hash_type? && HASH_METHODS.include?(method))
100
+ end
101
+
102
+ def parent_is_loop?(node)
103
+ node.each_ancestor.any? { |ancestor| loop?(ancestor, node) }
104
+ end
105
+
106
+ def loop?(ancestor, node)
107
+ keyword_loop?(ancestor.type) ||
108
+ kernel_loop?(ancestor) ||
109
+ node_within_enumerable_loop?(node, ancestor)
110
+ end
111
+
112
+ def keyword_loop?(type)
113
+ LOOP_TYPES.include?(type)
114
+ end
115
+
116
+ def node_within_enumerable_loop?(node, ancestor)
117
+ enumerable_loop?(ancestor) do |receiver|
118
+ receiver != node && !receiver.descendants.include?(node)
119
+ end
120
+ end
121
+
122
+ def literal_class(node)
123
+ if node.array_type?
124
+ 'Array'
125
+ elsif node.hash_type?
126
+ 'Hash'
127
+ end
128
+ end
129
+
130
+ def enumerable_method?(method_name)
131
+ ENUMERABLE_METHOD_NAMES.include?(method_name)
132
+ end
133
+
134
+ def min_size
135
+ Integer(cop_config['MinSize'] || 1)
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end