rubocop-performance 1.7.1 → 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
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