rubocop-performance 1.11.4 → 1.13.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/config/default.yml +21 -8
  4. data/lib/rubocop/cop/performance/ancestors_include.rb +4 -0
  5. data/lib/rubocop/cop/performance/array_semi_infinite_range_slice.rb +3 -1
  6. data/lib/rubocop/cop/performance/big_decimal_with_numeric_argument.rb +23 -12
  7. data/lib/rubocop/cop/performance/case_when_splat.rb +7 -5
  8. data/lib/rubocop/cop/performance/casecmp.rb +4 -2
  9. data/lib/rubocop/cop/performance/concurrent_monotonic_time.rb +42 -0
  10. data/lib/rubocop/cop/performance/count.rb +22 -13
  11. data/lib/rubocop/cop/performance/delete_prefix.rb +3 -4
  12. data/lib/rubocop/cop/performance/delete_suffix.rb +3 -4
  13. data/lib/rubocop/cop/performance/detect.rb +6 -5
  14. data/lib/rubocop/cop/performance/double_start_end_with.rb +21 -0
  15. data/lib/rubocop/cop/performance/end_with.rb +5 -0
  16. data/lib/rubocop/cop/performance/inefficient_hash_search.rb +3 -0
  17. data/lib/rubocop/cop/performance/map_compact.rb +21 -7
  18. data/lib/rubocop/cop/performance/open_struct.rb +4 -0
  19. data/lib/rubocop/cop/performance/range_include.rb +3 -2
  20. data/lib/rubocop/cop/performance/redundant_block_call.rb +1 -0
  21. data/lib/rubocop/cop/performance/redundant_equality_comparison_block.rb +20 -7
  22. data/lib/rubocop/cop/performance/redundant_merge.rb +4 -3
  23. data/lib/rubocop/cop/performance/redundant_string_chars.rb +15 -7
  24. data/lib/rubocop/cop/performance/start_with.rb +5 -0
  25. data/lib/rubocop/cop/performance/string_identifier_argument.rb +62 -0
  26. data/lib/rubocop/cop/performance/string_include.rb +2 -1
  27. data/lib/rubocop/cop/performance/sum.rb +47 -23
  28. data/lib/rubocop/cop/performance/times_map.rb +12 -0
  29. data/lib/rubocop/cop/performance/unfreeze_string.rb +5 -5
  30. data/lib/rubocop/cop/performance_cops.rb +2 -0
  31. data/lib/rubocop/performance/version.rb +1 -1
  32. metadata +7 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9f8e6344d490eed1972435e9ce89e171634bff029196b42987d02ee02745ddce
4
- data.tar.gz: 863428f02b2e9e387f40bc534775928035ca945d08cfdb8b6602b2765e67f074
3
+ metadata.gz: 7df725930fece6ca4b7ffd94e80b36095813fca1b646632d87bf7cc556b2e039
4
+ data.tar.gz: 75292501b62f29f90223ce6cc727a90dea11c37e5972c15197ddd81d6a2272ce
5
5
  SHA512:
6
- metadata.gz: fb7b9ea8f9626ea64dd44af1c3f6cc2e6141fdf6dc50b4f7aca09c88c11fe68b54a808cfaa2b0bed3a429f4614e85f84a724f833d5c767d7dc9fdc1f1a082014
7
- data.tar.gz: a2b8d58a302ea4070e1ce702263806ca1fe3b9d7e3c9944156299c28d8df9ad311d321d202b4cdf02723dc6a331b6817604afea9d7e684e2c9a3cf4300763460
6
+ metadata.gz: 60cffee092cca563bf9e87e4bfc1aa578a410c1caca1fc13a13b4b26e2002f766ef70afeef6c2011067b6fb7d79559318de320514a75538a89f180d26ceeaa6e
7
+ data.tar.gz: af8f26dcb82cc2f77cb6b4f9913117b211f100a48e6539ba108439ce22491664b54f9ad773a427d41f6a6ff5c07a9dee71676bc61f8f1bf90d825ffe5641bf9f
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2012-21 Bozhidar Batsov
1
+ Copyright (c) 2012-22 Bozhidar Batsov
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/config/default.yml CHANGED
@@ -17,7 +17,7 @@ Performance/ArraySemiInfiniteRangeSlice:
17
17
  VersionAdded: '1.9'
18
18
 
19
19
  Performance/BigDecimalWithNumericArgument:
20
- Description: 'Convert numeric argument to string before passing to BigDecimal.'
20
+ Description: 'Convert numeric literal to string and pass it to `BigDecimal`.'
21
21
  Enabled: 'pending'
22
22
  VersionAdded: '1.7'
23
23
 
@@ -43,10 +43,9 @@ Performance/CaseWhenSplat:
43
43
  Reordering `when` conditions with a splat to the end
44
44
  of the `when` branches can improve performance.
45
45
  Enabled: false
46
- AutoCorrect: false
47
46
  SafeAutoCorrect: false
48
47
  VersionAdded: '0.34'
49
- VersionChanged: '0.59'
48
+ VersionChanged: '1.13'
50
49
 
51
50
  Performance/Casecmp:
52
51
  Description: >-
@@ -76,6 +75,12 @@ Performance/CompareWithBlock:
76
75
  Enabled: true
77
76
  VersionAdded: '0.46'
78
77
 
78
+ Performance/ConcurrentMonotonicTime:
79
+ Description: 'Use `Process.clock_gettime(Process::CLOCK_MONOTONIC)` instead of `Concurrent.monotonic_time`.'
80
+ Reference: 'https://github.com/rails/rails/pull/43502'
81
+ Enabled: pending
82
+ VersionAdded: '1.12'
83
+
79
84
  Performance/ConstantRegexp:
80
85
  Description: 'Finds regular expressions with dynamic components that are all constants.'
81
86
  Enabled: pending
@@ -131,7 +136,7 @@ Performance/DoubleStartEndWith:
131
136
  VersionAdded: '0.36'
132
137
  VersionChanged: '0.48'
133
138
  # Used to check for `starts_with?` and `ends_with?`.
134
- # These methods are defined by `ActiveSupport`.
139
+ # These methods are defined by Active Support.
135
140
  IncludeActiveSupportAliases: false
136
141
 
137
142
  Performance/EndWith:
@@ -306,12 +311,17 @@ Performance/StartWith:
306
311
  VersionAdded: '0.36'
307
312
  VersionChanged: '1.10'
308
313
 
314
+ Performance/StringIdentifierArgument:
315
+ Description: 'Use symbol identifier argument instead of string identifier argument.'
316
+ Enabled: pending
317
+ VersionAdded: '1.13'
318
+
309
319
  Performance/StringInclude:
310
320
  Description: 'Use `String#include?` instead of a regex match with literal-only pattern.'
311
321
  Enabled: 'pending'
312
- AutoCorrect: false
313
322
  SafeAutoCorrect: false
314
323
  VersionAdded: '1.7'
324
+ VersionChanged: '1.12'
315
325
 
316
326
  Performance/StringReplacement:
317
327
  Description: >-
@@ -324,17 +334,20 @@ Performance/StringReplacement:
324
334
 
325
335
  Performance/Sum:
326
336
  Description: 'Use `sum` instead of a custom array summation.'
337
+ SafeAutoCorrect: false
327
338
  Reference: 'https://blog.bigbinary.com/2016/11/02/ruby-2-4-introduces-enumerable-sum.html'
328
339
  Enabled: 'pending'
329
340
  VersionAdded: '1.8'
341
+ VersionChanged: '1.13'
342
+ OnlySumOrWithInitialValue: false
330
343
 
331
344
  Performance/TimesMap:
332
345
  Description: 'Checks for .times.map calls.'
333
- AutoCorrect: false
334
346
  Enabled: true
347
+ # See https://github.com/rubocop/rubocop/issues/4658
348
+ SafeAutoCorrect: false
335
349
  VersionAdded: '0.36'
336
- VersionChanged: '0.50'
337
- SafeAutoCorrect: false # see https://github.com/rubocop/rubocop/issues/4658
350
+ VersionChanged: '1.13'
338
351
 
339
352
  Performance/UnfreezeString:
340
353
  Description: 'Use unary plus to get an unfrozen string literal.'
@@ -6,6 +6,10 @@ module RuboCop
6
6
  # This cop is used to identify usages of `ancestors.include?` and
7
7
  # change them to use `<=` instead.
8
8
  #
9
+ # @safety
10
+ # This cop is unsafe because it can't tell whether the receiver is a class or an object.
11
+ # e.g. the false positive was for `Nokogiri::XML::Node#ancestors`.
12
+ #
9
13
  # @example
10
14
  # # bad
11
15
  # A.ancestors.include?(B)
@@ -7,7 +7,9 @@ module RuboCop
7
7
  # can be replaced by `Array#take` and `Array#drop`.
8
8
  # This cop was created due to a mistake in microbenchmark and hence is disabled by default.
9
9
  # Refer https://github.com/rubocop/rubocop-performance/pull/175#issuecomment-731892717
10
- # This cop is also unsafe for string slices because strings do not have `#take` and `#drop` methods.
10
+ #
11
+ # @safety
12
+ # This cop is unsafe for string slices because strings do not have `#take` and `#drop` methods.
11
13
  #
12
14
  # @example
13
15
  # # bad
@@ -10,36 +10,47 @@ module RuboCop
10
10
  # @example
11
11
  # # bad
12
12
  # BigDecimal(1, 2)
13
+ # 4.to_d(6)
13
14
  # BigDecimal(1.2, 3, exception: true)
15
+ # 4.5.to_d(6, exception: true)
14
16
  #
15
17
  # # good
16
18
  # BigDecimal('1', 2)
19
+ # BigDecimal('4', 6)
17
20
  # BigDecimal('1.2', 3, exception: true)
21
+ # BigDecimal('4.5', 6, exception: true)
18
22
  #
19
23
  class BigDecimalWithNumericArgument < Base
20
24
  extend AutoCorrector
21
25
 
22
- MSG = 'Convert numeric argument to string before passing to `BigDecimal`.'
23
- RESTRICT_ON_SEND = %i[BigDecimal].freeze
26
+ MSG = 'Convert numeric literal to string and pass it to `BigDecimal`.'
27
+ RESTRICT_ON_SEND = %i[BigDecimal to_d].freeze
24
28
 
25
29
  def_node_matcher :big_decimal_with_numeric_argument?, <<~PATTERN
26
30
  (send nil? :BigDecimal $numeric_type? ...)
27
31
  PATTERN
28
32
 
33
+ def_node_matcher :to_d?, <<~PATTERN
34
+ (send [!nil? $numeric_type?] :to_d ...)
35
+ PATTERN
36
+
29
37
  def on_send(node)
30
- return unless (numeric = big_decimal_with_numeric_argument?(node))
31
- return if numeric.float_type? && specifies_precision?(node)
38
+ if (numeric = big_decimal_with_numeric_argument?(node))
39
+ add_offense(numeric.source_range) do |corrector|
40
+ corrector.wrap(numeric, "'", "'")
41
+ end
42
+ elsif (numeric_to_d = to_d?(node))
43
+ add_offense(numeric_to_d.source_range) do |corrector|
44
+ big_decimal_args = node
45
+ .arguments
46
+ .map(&:source)
47
+ .unshift("'#{numeric_to_d.source}'")
48
+ .join(', ')
32
49
 
33
- add_offense(numeric.source_range) do |corrector|
34
- corrector.wrap(numeric, "'", "'")
50
+ corrector.replace(node, "BigDecimal(#{big_decimal_args})")
51
+ end
35
52
  end
36
53
  end
37
-
38
- private
39
-
40
- def specifies_precision?(node)
41
- node.arguments.size > 1 && !node.arguments[1].hash_type?
42
- end
43
54
  end
44
55
  end
45
56
  end
@@ -17,11 +17,13 @@ module RuboCop
17
17
  # this defining a higher level when condition to override a condition
18
18
  # that is inside of the splat expansion.
19
19
  #
20
- # This is not a guaranteed performance improvement. If the data being
21
- # processed by the `case` condition is normalized in a manner that favors
22
- # hitting a condition in the splat expansion, it is possible that
23
- # moving the splat condition to the end will use more memory,
24
- # and run slightly slower.
20
+ # @safety
21
+ # This cop is not unsafe auto-correction because it is not a guaranteed
22
+ # performance improvement. If the data being processed by the `case` condition is
23
+ # normalized in a manner that favors hitting a condition in the splat expansion,
24
+ # it is possible that moving the splat condition to the end will use more memory,
25
+ # and run slightly slower.
26
+ # See for more details: https://github.com/rubocop/rubocop/pull/6163
25
27
  #
26
28
  # @example
27
29
  # # bad
@@ -5,8 +5,10 @@ module RuboCop
5
5
  module Performance
6
6
  # This cop identifies places where a case-insensitive string comparison
7
7
  # can better be implemented using `casecmp`.
8
- # This cop is unsafe because `String#casecmp` and `String#casecmp?` behave
9
- # differently when using Non-ASCII characters.
8
+ #
9
+ # @safety
10
+ # This cop is unsafe because `String#casecmp` and `String#casecmp?` behave
11
+ # differently when using Non-ASCII characters.
10
12
  #
11
13
  # @example
12
14
  # # bad
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # This cop identifies places where `Concurrent.monotonic_time`
7
+ # can be replaced by `Process.clock_gettime(Process::CLOCK_MONOTONIC)`.
8
+ #
9
+ # @example
10
+ #
11
+ # # bad
12
+ # Concurrent.monotonic_time
13
+ #
14
+ # # good
15
+ # Process.clock_gettime(Process::CLOCK_MONOTONIC)
16
+ #
17
+ class ConcurrentMonotonicTime < Base
18
+ extend AutoCorrector
19
+
20
+ MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
21
+ RESTRICT_ON_SEND = %i[monotonic_time].freeze
22
+
23
+ def_node_matcher :concurrent_monotonic_time?, <<~PATTERN
24
+ (send
25
+ (const {nil? cbase} :Concurrent) :monotonic_time ...)
26
+ PATTERN
27
+
28
+ def on_send(node)
29
+ return unless concurrent_monotonic_time?(node)
30
+
31
+ optional_unit_parameter = ", #{node.first_argument.source}" if node.first_argument
32
+ prefer = "Process.clock_gettime(Process::CLOCK_MONOTONIC#{optional_unit_parameter})"
33
+ message = format(MSG, prefer: prefer, current: node.source)
34
+
35
+ add_offense(node, message: message) do |corrector|
36
+ corrector.replace(node, prefer)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -7,6 +7,28 @@ module RuboCop
7
7
  # follow calls to `select`, `find_all`, `filter` or `reject`. Querying logic can instead be
8
8
  # passed to the `count` call.
9
9
  #
10
+ # @safety
11
+ # This cop is unsafe because it has known compatibility issues with `ActiveRecord` and other
12
+ # frameworks. ActiveRecord's `count` ignores the block that is passed to it.
13
+ # `ActiveRecord` will ignore the block that is passed to `count`.
14
+ # Other methods, such as `select`, will convert the association to an
15
+ # array and then run the block on the array. A simple work around to
16
+ # make `count` work with a block is to call `to_a.count {...}`.
17
+ #
18
+ # For example:
19
+ #
20
+ # [source,ruby]
21
+ # ----
22
+ # `Model.where(id: [1, 2, 3]).select { |m| m.method == true }.size`
23
+ # ----
24
+ #
25
+ # becomes:
26
+ #
27
+ # [source,ruby]
28
+ # ----
29
+ # `Model.where(id: [1, 2, 3]).to_a.count { |m| m.method == true }`
30
+ # ----
31
+ #
10
32
  # @example
11
33
  # # bad
12
34
  # [1, 2, 3].select { |e| e > 2 }.size
@@ -24,19 +46,6 @@ module RuboCop
24
46
  # [1, 2, 3].count { |e| e < 2 && e.even? }
25
47
  # Model.select('field AS field_one').count
26
48
  # Model.select(:value).count
27
- #
28
- # `ActiveRecord` compatibility:
29
- # `ActiveRecord` will ignore the block that is passed to `count`.
30
- # Other methods, such as `select`, will convert the association to an
31
- # array and then run the block on the array. A simple work around to
32
- # make `count` work with a block is to call `to_a.count {...}`.
33
- #
34
- # Example:
35
- # `Model.where(id: [1, 2, 3]).select { |m| m.method == true }.size`
36
- #
37
- # becomes:
38
- #
39
- # `Model.where(id: [1, 2, 3]).to_a.count { |m| m.method == true }`
40
49
  class Count < Base
41
50
  include RangeHelp
42
51
  extend AutoCorrector
@@ -7,7 +7,6 @@ module RuboCop
7
7
  #
8
8
  # This cop identifies places where `gsub(/\Aprefix/, '')` and `sub(/\Aprefix/, '')`
9
9
  # can be replaced by `delete_prefix('prefix')`.
10
- # It is marked as unsafe by default because `Pathname` has `sub` but not `delete_prefix`.
11
10
  #
12
11
  # This cop has `SafeMultiline` configuration option that `true` by default because
13
12
  # `^prefix` is unsafe as it will behave incompatible with `delete_prefix`
@@ -15,6 +14,9 @@ module RuboCop
15
14
  #
16
15
  # The `delete_prefix('prefix')` method is faster than `gsub(/\Aprefix/, '')`.
17
16
  #
17
+ # @safety
18
+ # This cop is unsafe because `Pathname` has `sub` but not `delete_prefix`.
19
+ #
18
20
  # @example
19
21
  #
20
22
  # # bad
@@ -47,9 +49,6 @@ module RuboCop
47
49
  class DeletePrefix < Base
48
50
  include RegexpMetacharacter
49
51
  extend AutoCorrector
50
- extend TargetRubyVersion
51
-
52
- minimum_target_ruby_version 2.5
53
52
 
54
53
  MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
55
54
  RESTRICT_ON_SEND = %i[gsub gsub! sub sub!].freeze
@@ -7,7 +7,6 @@ module RuboCop
7
7
  #
8
8
  # This cop identifies places where `gsub(/suffix\z/, '')` and `sub(/suffix\z/, '')`
9
9
  # can be replaced by `delete_suffix('suffix')`.
10
- # It is marked as unsafe by default because `Pathname` has `sub` but not `delete_suffix`.
11
10
  #
12
11
  # This cop has `SafeMultiline` configuration option that `true` by default because
13
12
  # `suffix$` is unsafe as it will behave incompatible with `delete_suffix?`
@@ -15,6 +14,9 @@ module RuboCop
15
14
  #
16
15
  # The `delete_suffix('suffix')` method is faster than `gsub(/suffix\z/, '')`.
17
16
  #
17
+ # @safety
18
+ # This cop is unsafe because `Pathname` has `sub` but not `delete_suffix`.
19
+ #
18
20
  # @example
19
21
  #
20
22
  # # bad
@@ -47,9 +49,6 @@ module RuboCop
47
49
  class DeleteSuffix < Base
48
50
  include RegexpMetacharacter
49
51
  extend AutoCorrector
50
- extend TargetRubyVersion
51
-
52
- minimum_target_ruby_version 2.5
53
52
 
54
53
  MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
55
54
  RESTRICT_ON_SEND = %i[gsub gsub! sub sub!].freeze
@@ -7,6 +7,11 @@ module RuboCop
7
7
  # chained to `select`, `find_all` or `filter` and change them to use
8
8
  # `detect` instead.
9
9
  #
10
+ # @safety
11
+ # This cop is unsafe because is has known compatibility issues with `ActiveRecord` and other
12
+ # frameworks. `ActiveRecord` does not implement a `detect` method and `find` has its own
13
+ # meaning. Correcting `ActiveRecord` methods with this cop should be considered unsafe.
14
+ #
10
15
  # @example
11
16
  # # bad
12
17
  # [].select { |item| true }.first
@@ -22,10 +27,6 @@ module RuboCop
22
27
  # [].detect { |item| true }
23
28
  # [].reverse.detect { |item| true }
24
29
  #
25
- # `ActiveRecord` compatibility:
26
- # `ActiveRecord` does not implement a `detect` method and `find` has its
27
- # own meaning. Correcting ActiveRecord methods with this cop should be
28
- # considered unsafe.
29
30
  class Detect < Base
30
31
  extend AutoCorrector
31
32
 
@@ -90,7 +91,7 @@ module RuboCop
90
91
  end
91
92
 
92
93
  def replacement(method, index)
93
- if method == :last || method == :[] && index == -1
94
+ if method == :last || (method == :[] && index == -1)
94
95
  "reverse.#{preferred_method}"
95
96
  else
96
97
  preferred_method
@@ -7,6 +7,9 @@ module RuboCop
7
7
  # separated by `||`. In some cases such calls can be replaced
8
8
  # with an single `#start_with?`/`#end_with?` call.
9
9
  #
10
+ # `IncludeActiveSupportAliases` configuration option is used to check for
11
+ # `starts_with?` and `ends_with?`. These methods are defined by Active Support.
12
+ #
10
13
  # @example
11
14
  # # bad
12
15
  # str.start_with?("a") || str.start_with?(Some::CONST)
@@ -17,6 +20,24 @@ module RuboCop
17
20
  # str.start_with?("a", Some::CONST)
18
21
  # str.start_with?("a", "b", "c")
19
22
  # str.end_with?(var1, var2)
23
+ #
24
+ # @example IncludeActiveSupportAliases: false (default)
25
+ # # good
26
+ # str.starts_with?("a", "b") || str.starts_with?("c")
27
+ # str.ends_with?(var1) || str.ends_with?(var2)
28
+ #
29
+ # str.starts_with?("a", "b", "c")
30
+ # str.ends_with?(var1, var2)
31
+ #
32
+ # @example IncludeActiveSupportAliases: true
33
+ # # bad
34
+ # str.starts_with?("a", "b") || str.starts_with?("c")
35
+ # str.ends_with?(var1) || str.ends_with?(var2)
36
+ #
37
+ # # good
38
+ # str.starts_with?("a", "b", "c")
39
+ # str.ends_with?(var1, var2)
40
+ #
20
41
  class DoubleStartEndWith < Base
21
42
  extend AutoCorrector
22
43
 
@@ -9,6 +9,11 @@ module RuboCop
9
9
  # `end$` is unsafe as it will behave incompatible with `end_with?`
10
10
  # for receiver is multiline string.
11
11
  #
12
+ # @safety
13
+ # This will change to a new method call which isn't guaranteed to be on the
14
+ # object. Switching these methods has to be done with knowledge of the types
15
+ # of the variables which rubocop doesn't have.
16
+ #
12
17
  # @example
13
18
  # # bad
14
19
  # 'abc'.match?(/bc\Z/)
@@ -15,6 +15,9 @@ module RuboCop
15
15
  # both perform an O(n) search through all of the values, calling `values`
16
16
  # allocates a new array while using `value?` does not.
17
17
  #
18
+ # @safety
19
+ # This cop is unsafe because it can't tell whether the receiver is a hash object.
20
+ #
18
21
  # @example
19
22
  # # bad
20
23
  # { a: 1, b: 2 }.keys.include?(:a)
@@ -6,8 +6,10 @@ module RuboCop
6
6
  # In Ruby 2.7, `Enumerable#filter_map` has been added.
7
7
  #
8
8
  # This cop identifies places where `map { ... }.compact` can be replaced by `filter_map`.
9
- # It is marked as unsafe auto-correction by default because `map { ... }.compact`
10
- # that is not compatible with `filter_map`.
9
+ #
10
+ # @safety
11
+ # This cop's autocorrection is unsafe because `map { ... }.compact` that is not
12
+ # compatible with `filter_map`.
11
13
  #
12
14
  # [source,ruby]
13
15
  # ----
@@ -56,19 +58,19 @@ module RuboCop
56
58
 
57
59
  add_offense(range) do |corrector|
58
60
  corrector.replace(map_node.loc.selector, 'filter_map')
59
- remove_compact_method(corrector, node)
61
+ remove_compact_method(corrector, node, node.parent)
60
62
  end
61
63
  end
62
64
 
63
65
  private
64
66
 
65
- def remove_compact_method(corrector, compact_node)
66
- chained_method = compact_node.parent
67
+ def remove_compact_method(corrector, compact_node, chained_method)
67
68
  compact_method_range = compact_node.loc.selector
68
69
 
69
- if compact_node.multiline? && chained_method&.loc.respond_to?(:selector) &&
70
+ if compact_node.multiline? && chained_method&.loc.respond_to?(:selector) && chained_method.dot? &&
71
+ !map_method_and_compact_method_on_same_line?(compact_node) &&
70
72
  !invoke_method_after_map_compact_on_same_line?(compact_node, chained_method)
71
- compact_method_range = range_by_whole_lines(compact_method_range, include_final_newline: true)
73
+ compact_method_range = compact_method_with_final_newline_range(compact_method_range)
72
74
  else
73
75
  corrector.remove(compact_node.loc.dot)
74
76
  end
@@ -76,9 +78,21 @@ module RuboCop
76
78
  corrector.remove(compact_method_range)
77
79
  end
78
80
 
81
+ def map_method_and_compact_method_on_same_line?(compact_node)
82
+ return false unless compact_node.children.first.respond_to?(:send_node)
83
+
84
+ map_node = compact_node.children.first.send_node
85
+
86
+ compact_node.loc.selector.line == map_node.loc.selector.line
87
+ end
88
+
79
89
  def invoke_method_after_map_compact_on_same_line?(compact_node, chained_method)
80
90
  compact_node.loc.selector.line == chained_method.loc.selector.line
81
91
  end
92
+
93
+ def compact_method_with_final_newline_range(compact_method_range)
94
+ range_by_whole_lines(compact_method_range, include_final_newline: true)
95
+ end
82
96
  end
83
97
  end
84
98
  end
@@ -11,6 +11,10 @@ module RuboCop
11
11
  # especially in case of single-threaded
12
12
  # applications with multiple `OpenStruct` instantiations.
13
13
  #
14
+ # @safety
15
+ # This cop is unsafe because `OpenStruct.new` and `Struct.new`
16
+ # are not equivalent.
17
+ #
14
18
  # @example
15
19
  # # bad
16
20
  # class MyClass
@@ -9,8 +9,9 @@ module RuboCop
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?` (or `Range#member?`) and
13
- # `Range#cover?` are not equivalent behaviour.
12
+ # @safety
13
+ # This cop is unsafe because `Range#include?` (or `Range#member?`) and `Range#cover?`
14
+ # are not equivalent behaviour.
14
15
  #
15
16
  # @example
16
17
  # # bad
@@ -55,6 +55,7 @@ module RuboCop
55
55
  end
56
56
  end
57
57
  end
58
+ alias on_defs on_def
58
59
 
59
60
  private
60
61
 
@@ -9,8 +9,9 @@ module RuboCop
9
9
  # By default, `Object#===` behaves the same as `Object#==`, but this
10
10
  # behavior is appropriately overridden in subclass. For example,
11
11
  # `Range#===` returns `true` when argument is within the range.
12
- # Therefore, It is marked as unsafe by default because `===` and `==`
13
- # do not always behave the same.
12
+ #
13
+ # @safety
14
+ # This cop is unsafe because `===` and `==` do not always behave the same.
14
15
  #
15
16
  # @example
16
17
  # # bad
@@ -24,9 +25,6 @@ module RuboCop
24
25
  #
25
26
  class RedundantEqualityComparisonBlock < Base
26
27
  extend AutoCorrector
27
- extend TargetRubyVersion
28
-
29
- minimum_target_ruby_version 2.5
30
28
 
31
29
  MSG = 'Use `%<prefer>s` instead of block.'
32
30
 
@@ -74,12 +72,27 @@ module RuboCop
74
72
 
75
73
  def new_argument(block_argument, block_body)
76
74
  if block_argument.source == block_body.receiver.source
77
- block_body.first_argument.source
75
+ rhs = block_body.first_argument
76
+ return if use_block_argument_in_method_argument_of_operand?(block_argument, rhs)
77
+
78
+ rhs.source
78
79
  elsif block_argument.source == block_body.first_argument.source
79
- block_body.receiver.source
80
+ lhs = block_body.receiver
81
+ return if use_block_argument_in_method_argument_of_operand?(block_argument, lhs)
82
+
83
+ lhs.source
80
84
  end
81
85
  end
82
86
 
87
+ def use_block_argument_in_method_argument_of_operand?(block_argument, operand)
88
+ return false unless operand.send_type?
89
+
90
+ arguments = operand.arguments
91
+ arguments.inject(arguments.map(&:source)) do |operand_sources, argument|
92
+ operand_sources + argument.each_descendant(:lvar).map(&:source)
93
+ end.any?(block_argument.source)
94
+ end
95
+
83
96
  def offense_range(node)
84
97
  node.send_node.loc.selector.join(node.source_range.end)
85
98
  end
@@ -8,8 +8,9 @@ module RuboCop
8
8
  # You can set the maximum number of key-value pairs to consider
9
9
  # an offense with `MaxKeyValuePairs`.
10
10
  #
11
- # This cop is marked as unsafe because RuboCop cannot determine if the
12
- # receiver of `merge!` is actually a hash or not.
11
+ # @safety
12
+ # This cop is unsafe because RuboCop cannot determine if the
13
+ # receiver of `merge!` is actually a hash or not.
13
14
  #
14
15
  # @example
15
16
  # # bad
@@ -91,7 +92,7 @@ module RuboCop
91
92
  end
92
93
 
93
94
  def non_redundant_pairs?(receiver, pairs)
94
- pairs.size > 1 && !receiver.pure? || pairs.size > max_key_value_pairs
95
+ (pairs.size > 1 && !receiver.pure?) || pairs.size > max_key_value_pairs
95
96
  end
96
97
 
97
98
  def kwsplat_used?(pairs)
@@ -82,14 +82,10 @@ module RuboCop
82
82
 
83
83
  def build_good_method(method, args)
84
84
  case method
85
- when :[], :slice
85
+ when :slice
86
86
  "[#{build_call_args(args)}].chars"
87
- when :first
88
- if args.any?
89
- "[0...#{args.first.source}].chars"
90
- else
91
- '[0]'
92
- end
87
+ when :[], :first
88
+ build_good_method_for_brackets_or_first_method(method, args)
93
89
  when :take
94
90
  "[0...#{args.first.source}].chars"
95
91
  else
@@ -97,6 +93,18 @@ module RuboCop
97
93
  end
98
94
  end
99
95
 
96
+ def build_good_method_for_brackets_or_first_method(method, args)
97
+ first_arg = args.first
98
+
99
+ if first_arg&.range_type?
100
+ "[#{build_call_args(args)}].chars"
101
+ elsif method == :first && args.any?
102
+ "[0...#{args.first.source}].chars"
103
+ else
104
+ first_arg ? "[#{first_arg.source}]" : '[0]'
105
+ end
106
+ end
107
+
100
108
  def build_bad_method(method, args)
101
109
  case method
102
110
  when :[]
@@ -9,6 +9,11 @@ module RuboCop
9
9
  # `^start` is unsafe as it will behave incompatible with `start_with?`
10
10
  # for receiver is multiline string.
11
11
  #
12
+ # @safety
13
+ # This will change to a new method call which isn't guaranteed to be on the
14
+ # object. Switching these methods has to be done with knowledge of the types
15
+ # of the variables which rubocop doesn't have.
16
+ #
12
17
  # @example
13
18
  # # bad
14
19
  # 'abc'.match?(/\Aab/)
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # This cop identifies places where string identifier argument can be replaced
7
+ # by symbol identifier argument.
8
+ # It prevents the redundancy of the internal string-to-symbol conversion.
9
+ #
10
+ # This cop targets methods that take identifier (e.g. method name) argument
11
+ # and the following examples are parts of it.
12
+ #
13
+ # @example
14
+ #
15
+ # # bad
16
+ # send('do_something')
17
+ # attr_accessor 'do_something'
18
+ # instance_variable_get('@ivar')
19
+ #
20
+ # # good
21
+ # send(:do_something)
22
+ # attr_accessor :do_something
23
+ # instance_variable_get(:@ivar)
24
+ #
25
+ class StringIdentifierArgument < Base
26
+ extend AutoCorrector
27
+
28
+ MSG = 'Use `%<symbol_arg>s` instead of `%<string_arg>s`.'
29
+
30
+ # NOTE: `attr` method is not included in this list as it can cause false positives in Nokogiri API.
31
+ # And `attr` may not be used because `Style/Attr` registers an offense.
32
+ # https://github.com/rubocop/rubocop-performance/issues/278
33
+ RESTRICT_ON_SEND = %i[
34
+ alias_method attr_accessor attr_reader attr_writer autoload autoload?
35
+ class_variable_defined? const_defined? const_get const_set const_source_location
36
+ define_method instance_method method_defined? private_class_method? private_method_defined?
37
+ protected_method_defined? public_class_method public_instance_method public_method_defined?
38
+ remove_class_variable remove_method undef_method class_variable_get class_variable_set
39
+ deprecate_constant module_function private private_constant protected public public_constant
40
+ remove_const ruby2_keywords
41
+ define_singleton_method instance_variable_defined instance_variable_get instance_variable_set
42
+ method public_method public_send remove_instance_variable respond_to? send singleton_method
43
+ __send__
44
+ ].freeze
45
+
46
+ def on_send(node)
47
+ return unless (first_argument = node.first_argument)
48
+ return unless first_argument.str_type?
49
+ return if first_argument.value.include?(' ')
50
+
51
+ replacement = first_argument.value.to_sym.inspect
52
+
53
+ message = format(MSG, symbol_arg: replacement, string_arg: first_argument.source)
54
+
55
+ add_offense(first_argument, message: message) do |corrector|
56
+ corrector.replace(first_argument, replacement)
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -6,7 +6,8 @@ module RuboCop
6
6
  # This cop identifies unnecessary use of a regex where
7
7
  # `String#include?` would suffice.
8
8
  #
9
- # This cop's offenses are not safe to auto-correct if a receiver is nil.
9
+ # @safety
10
+ # This cop's offenses are not safe to auto-correct if a receiver is nil.
10
11
  #
11
12
  # @example
12
13
  # # bad
@@ -6,35 +6,46 @@ module RuboCop
6
6
  # This cop identifies places where custom code finding the sum of elements
7
7
  # in some Enumerable object can be replaced by `Enumerable#sum` method.
8
8
  #
9
- # This cop can change auto-correction scope depending on the value of
10
- # `SafeAutoCorrect`.
11
- # Its auto-correction is marked as safe by default (`SafeAutoCorrect: true`)
12
- # to prevent `TypeError` in auto-correced code when initial value is not
13
- # specified as shown below:
9
+ # @safety
10
+ # Auto-corrections are unproblematic wherever an initial value is provided explicitly:
14
11
  #
15
- # [source,ruby]
16
- # ----
17
- # ['a', 'b'].sum # => (String can't be coerced into Integer)
18
- # ----
12
+ # [source,ruby]
13
+ # ----
14
+ # [1, 2, 3].reduce(4, :+) # => 10
15
+ # [1, 2, 3].sum(4) # => 10
19
16
  #
20
- # Therefore if initial value is not specified, unsafe auto-corrected will not occur.
17
+ # [].reduce(4, :+) # => 4
18
+ # [].sum(4) # => 4
19
+ # ----
21
20
  #
22
- # If you always want to enable auto-correction, you can set `SafeAutoCorrect: false`.
21
+ # This also holds true for non-numeric types which implement a `:+` method:
23
22
  #
24
- # [source,yaml]
25
- # ----
26
- # Performance/Sum:
27
- # SafeAutoCorrect: false
28
- # ----
23
+ # [source,ruby]
24
+ # ----
25
+ # ['l', 'o'].reduce('Hel', :+) # => "Hello"
26
+ # ['l', 'o'].sum('Hel') # => "Hello"
27
+ # ----
29
28
  #
30
- # Please note that the auto-correction command line option will be changed from
31
- # `rubocop -a` to `rubocop -A`, which includes unsafe auto-correction.
29
+ # When no initial value is provided though, `Enumerable#reduce` will pick the first enumerated value
30
+ # as initial value and successively add all following values to it, whereas
31
+ # `Enumerable#sum` will set an initial value of `0` (`Integer`) which can lead to a `TypeError`:
32
32
  #
33
- # @example
33
+ # [source,ruby]
34
+ # ----
35
+ # [].reduce(:+) # => nil
36
+ # [1, 2, 3].reduce(:+) # => 6
37
+ # ['H', 'e', 'l', 'l', 'o'].reduce(:+) # => "Hello"
38
+ #
39
+ # [].sum # => 0
40
+ # [1, 2, 3].sum # => 6
41
+ # ['H', 'e', 'l', 'l', 'o'].sum # => in `+': String can't be coerced into Integer (TypeError)
42
+ # ----
43
+ #
44
+ # @example OnlySumOrWithInitialValue: false (default)
34
45
  # # bad
35
- # [1, 2, 3].inject(:+) # These bad cases with no initial value are unsafe and
36
- # [1, 2, 3].inject(&:+) # will not be auto-correced by default. If you want to
37
- # [1, 2, 3].reduce { |acc, elem| acc + elem } # auto-corrected, you can set `SafeAutoCorrect: false`.
46
+ # [1, 2, 3].inject(:+) # Auto-corrections for cases without initial value are unsafe
47
+ # [1, 2, 3].inject(&:+) # and will only be performed when using the `-A` option.
48
+ # [1, 2, 3].reduce { |acc, elem| acc + elem } # They can be prohibited completely using `SafeAutoCorrect: true`.
38
49
  # [1, 2, 3].reduce(10, :+)
39
50
  # [1, 2, 3].map { |elem| elem ** 2 }.sum
40
51
  # [1, 2, 3].collect(&:count).sum(10)
@@ -45,6 +56,17 @@ module RuboCop
45
56
  # [1, 2, 3].sum { |elem| elem ** 2 }
46
57
  # [1, 2, 3].sum(10, &:count)
47
58
  #
59
+ # @example OnlySumOrWithInitialValue: true
60
+ # # bad
61
+ # [1, 2, 3].reduce(10, :+)
62
+ # [1, 2, 3].map { |elem| elem ** 2 }.sum
63
+ # [1, 2, 3].collect(&:count).sum(10)
64
+ #
65
+ # # good
66
+ # [1, 2, 3].sum(10)
67
+ # [1, 2, 3].sum { |elem| elem ** 2 }
68
+ # [1, 2, 3].sum(10, &:count)
69
+ #
48
70
  class Sum < Base
49
71
  include RangeHelp
50
72
  extend AutoCorrector
@@ -103,6 +125,8 @@ module RuboCop
103
125
 
104
126
  def handle_sum_candidate(node)
105
127
  sum_candidate?(node) do |method, init, operation|
128
+ next if cop_config['OnlySumOrWithInitialValue'] && init.empty?
129
+
106
130
  range = sum_method_range(node)
107
131
  message = build_method_message(node, method, init, operation)
108
132
 
@@ -156,7 +180,7 @@ module RuboCop
156
180
  end
157
181
 
158
182
  def sum_method_range(node)
159
- range_between(node.loc.selector.begin_pos, node.loc.end.end_pos)
183
+ range_between(node.loc.selector.begin_pos, node.loc.expression.end_pos)
160
184
  end
161
185
 
162
186
  def sum_map_range(map, sum)
@@ -7,6 +7,18 @@ module RuboCop
7
7
  # In most cases such calls can be replaced
8
8
  # with an explicit array creation.
9
9
  #
10
+ # @safety
11
+ # This cop's autocorrection is unsafe because `Integer#times` does nothing if receiver is 0
12
+ # or less. However, `Array.new` raises an error if argument is less than 0.
13
+ #
14
+ # For example:
15
+ #
16
+ # [source,ruby]
17
+ # ----
18
+ # -1.times{} # does nothing
19
+ # Array.new(-1) # ArgumentError: negative array size
20
+ # ----
21
+ #
10
22
  # @example
11
23
  # # bad
12
24
  # 9.times.map do |i|
@@ -7,11 +7,11 @@ module RuboCop
7
7
  # literal instead of `String#dup` and `String.new`.
8
8
  # Unary plus operator is faster than `String#dup`.
9
9
  #
10
- # NOTE: `String.new` (without operator) is not exactly the same as `+''`.
11
- # These differ in encoding. `String.new.encoding` is always `ASCII-8BIT`.
12
- # However, `(+'').encoding` is the same as script encoding(e.g. `UTF-8`).
13
- # Therefore, auto-correction is unsafe.
14
- # So, if you expect `ASCII-8BIT` encoding, disable this cop.
10
+ # @safety
11
+ # This cop's autocorrection is unsafe because `String.new` (without operator) is not
12
+ # exactly the same as `+''`. These differ in encoding. `String.new.encoding` is always
13
+ # `ASCII-8BIT`. However, `(+'').encoding` is the same as script encoding(e.g. `UTF-8`).
14
+ # if you expect `ASCII-8BIT` encoding, disable this cop.
15
15
  #
16
16
  # @example
17
17
  # # bad
@@ -13,6 +13,7 @@ require_relative 'performance/case_when_splat'
13
13
  require_relative 'performance/casecmp'
14
14
  require_relative 'performance/collection_literal_in_loop'
15
15
  require_relative 'performance/compare_with_block'
16
+ require_relative 'performance/concurrent_monotonic_time'
16
17
  require_relative 'performance/constant_regexp'
17
18
  require_relative 'performance/count'
18
19
  require_relative 'performance/delete_prefix'
@@ -43,6 +44,7 @@ require_relative 'performance/size'
43
44
  require_relative 'performance/sort_reverse'
44
45
  require_relative 'performance/squeeze'
45
46
  require_relative 'performance/start_with'
47
+ require_relative 'performance/string_identifier_argument'
46
48
  require_relative 'performance/string_include'
47
49
  require_relative 'performance/string_replacement'
48
50
  require_relative 'performance/sum'
@@ -4,7 +4,7 @@ module RuboCop
4
4
  module Performance
5
5
  # This module holds the RuboCop Performance version information.
6
6
  module Version
7
- STRING = '1.11.4'
7
+ STRING = '1.13.1'
8
8
 
9
9
  def self.document_version
10
10
  STRING.match('\d+\.\d+').to_s
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop-performance
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.11.4
4
+ version: 1.13.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bozhidar Batsov
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2021-07-07 00:00:00.000000000 Z
13
+ date: 2022-01-01 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rubocop
@@ -74,6 +74,7 @@ files:
74
74
  - lib/rubocop/cop/performance/chain_array_allocation.rb
75
75
  - lib/rubocop/cop/performance/collection_literal_in_loop.rb
76
76
  - lib/rubocop/cop/performance/compare_with_block.rb
77
+ - lib/rubocop/cop/performance/concurrent_monotonic_time.rb
77
78
  - lib/rubocop/cop/performance/constant_regexp.rb
78
79
  - lib/rubocop/cop/performance/count.rb
79
80
  - lib/rubocop/cop/performance/delete_prefix.rb
@@ -104,6 +105,7 @@ files:
104
105
  - lib/rubocop/cop/performance/sort_reverse.rb
105
106
  - lib/rubocop/cop/performance/squeeze.rb
106
107
  - lib/rubocop/cop/performance/start_with.rb
108
+ - lib/rubocop/cop/performance/string_identifier_argument.rb
107
109
  - lib/rubocop/cop/performance/string_include.rb
108
110
  - lib/rubocop/cop/performance/string_replacement.rb
109
111
  - lib/rubocop/cop/performance/sum.rb
@@ -121,8 +123,9 @@ metadata:
121
123
  homepage_uri: https://docs.rubocop.org/rubocop-performance/
122
124
  changelog_uri: https://github.com/rubocop/rubocop-performance/blob/master/CHANGELOG.md
123
125
  source_code_uri: https://github.com/rubocop/rubocop-performance/
124
- documentation_uri: https://docs.rubocop.org/rubocop-performance/1.11/
126
+ documentation_uri: https://docs.rubocop.org/rubocop-performance/1.13/
125
127
  bug_tracker_uri: https://github.com/rubocop/rubocop-performance/issues
128
+ rubygems_mfa_required: 'true'
126
129
  post_install_message:
127
130
  rdoc_options: []
128
131
  require_paths:
@@ -138,7 +141,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
138
141
  - !ruby/object:Gem::Version
139
142
  version: '0'
140
143
  requirements: []
141
- rubygems_version: 3.2.12
144
+ rubygems_version: 3.3.3
142
145
  signing_key:
143
146
  specification_version: 4
144
147
  summary: Automatic performance checking tool for Ruby code.