rubocop-performance 1.11.2 → 1.12.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 (30) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +11 -1
  3. data/lib/rubocop/cop/performance/ancestors_include.rb +4 -0
  4. data/lib/rubocop/cop/performance/array_semi_infinite_range_slice.rb +8 -6
  5. data/lib/rubocop/cop/performance/case_when_splat.rb +11 -13
  6. data/lib/rubocop/cop/performance/casecmp.rb +4 -2
  7. data/lib/rubocop/cop/performance/collection_literal_in_loop.rb +1 -1
  8. data/lib/rubocop/cop/performance/concurrent_monotonic_time.rb +42 -0
  9. data/lib/rubocop/cop/performance/count.rb +22 -13
  10. data/lib/rubocop/cop/performance/delete_prefix.rb +4 -4
  11. data/lib/rubocop/cop/performance/delete_suffix.rb +4 -4
  12. data/lib/rubocop/cop/performance/detect.rb +10 -9
  13. data/lib/rubocop/cop/performance/end_with.rb +5 -0
  14. data/lib/rubocop/cop/performance/inefficient_hash_search.rb +3 -0
  15. data/lib/rubocop/cop/performance/map_compact.rb +13 -9
  16. data/lib/rubocop/cop/performance/open_struct.rb +4 -0
  17. data/lib/rubocop/cop/performance/range_include.rb +3 -2
  18. data/lib/rubocop/cop/performance/redundant_block_call.rb +1 -0
  19. data/lib/rubocop/cop/performance/redundant_equality_comparison_block.rb +20 -7
  20. data/lib/rubocop/cop/performance/redundant_merge.rb +4 -3
  21. data/lib/rubocop/cop/performance/redundant_string_chars.rb +7 -17
  22. data/lib/rubocop/cop/performance/regexp_match.rb +1 -2
  23. data/lib/rubocop/cop/performance/start_with.rb +5 -0
  24. data/lib/rubocop/cop/performance/string_include.rb +2 -1
  25. data/lib/rubocop/cop/performance/sum.rb +1 -1
  26. data/lib/rubocop/cop/performance/times_map.rb +12 -0
  27. data/lib/rubocop/cop/performance/unfreeze_string.rb +9 -6
  28. data/lib/rubocop/cop/performance_cops.rb +1 -0
  29. data/lib/rubocop/performance/version.rb +1 -1
  30. metadata +5 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a26b6360b5df05f3a7a8dfe957e5ecad0922105024a2e49dae8c39a566f10d88
4
- data.tar.gz: 7591fd4d805f4d6dad68b83c8fbffff8e563bfa27bd58030efa48f332c392b2f
3
+ metadata.gz: c98065b3a22d03c0e9290990288d6b8a82a87d956bdf4165d94d3cd326ae7bf5
4
+ data.tar.gz: a9ee1493235e6584ad381941423c0cac234bfc36fb4a2417433e667a16bec054
5
5
  SHA512:
6
- metadata.gz: c8fb234710212fd000d0d695da71a1b1a36687550cf3738ce987264dad85f015d2f1f1da9b0f511c2147532ff7d6d444b35efb472b399646171bbb35e0ff79ad
7
- data.tar.gz: 8447f79bb69e546ff684ece819b626fbfc68db8f7154b1b839c22167c87dbbf58821f1abe626a2b6d486ccfc81601e0844e416a312703ce0be7bc5552f1160ab
6
+ metadata.gz: 35f36bff7bfe1b19496bfea773e7d0f7892731e57b06bb523ac192324a817f401207750abab20892ea58ec54595d1bdfd454bf250a4a00baf6ef0f4ac3f7b030
7
+ data.tar.gz: 84a6ebc3e13d7c24aa6f1219443ce2c6cf6cd6c6253d4f09c78056fb3906f08bcb44db5365b03231aae55170a653e504b4a3433b4ed4a4dea1ee21b23d7a1387
data/config/default.yml CHANGED
@@ -76,6 +76,12 @@ Performance/CompareWithBlock:
76
76
  Enabled: true
77
77
  VersionAdded: '0.46'
78
78
 
79
+ Performance/ConcurrentMonotonicTime:
80
+ Description: 'Use `Process.clock_gettime(Process::CLOCK_MONOTONIC)` instead of `Concurrent.monotonic_time`.'
81
+ Reference: 'https://github.com/rails/rails/pull/43502'
82
+ Enabled: pending
83
+ VersionAdded: '1.12'
84
+
79
85
  Performance/ConstantRegexp:
80
86
  Description: 'Finds regular expressions with dynamic components that are all constants.'
81
87
  Enabled: pending
@@ -96,14 +102,18 @@ Performance/Count:
96
102
  Performance/DeletePrefix:
97
103
  Description: 'Use `delete_prefix` instead of `gsub`.'
98
104
  Enabled: true
105
+ Safe: false
99
106
  SafeMultiline: true
100
107
  VersionAdded: '1.6'
108
+ VersionChanged: '1.11'
101
109
 
102
110
  Performance/DeleteSuffix:
103
111
  Description: 'Use `delete_suffix` instead of `gsub`.'
104
112
  Enabled: true
113
+ Safe: false
105
114
  SafeMultiline: true
106
115
  VersionAdded: '1.6'
116
+ VersionChanged: '1.11'
107
117
 
108
118
  Performance/Detect:
109
119
  Description: >-
@@ -305,9 +315,9 @@ Performance/StartWith:
305
315
  Performance/StringInclude:
306
316
  Description: 'Use `String#include?` instead of a regex match with literal-only pattern.'
307
317
  Enabled: 'pending'
308
- AutoCorrect: false
309
318
  SafeAutoCorrect: false
310
319
  VersionAdded: '1.7'
320
+ VersionChanged: '1.12'
311
321
 
312
322
  Performance/StringReplacement:
313
323
  Description: >-
@@ -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,15 +7,17 @@ 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
14
- # # array[..2]
15
- # # array[...2]
16
- # # array[2..]
17
- # # array[2...]
18
- # # array.slice(..2)
16
+ # array[..2]
17
+ # array[...2]
18
+ # array[2..]
19
+ # array[2...]
20
+ # array.slice(..2)
19
21
  #
20
22
  # # good
21
23
  # array.take(3)
@@ -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
@@ -58,10 +60,8 @@ module RuboCop
58
60
  include RangeHelp
59
61
  extend AutoCorrector
60
62
 
61
- MSG = 'Reordering `when` conditions with a splat to the end ' \
62
- 'of the `when` branches can improve performance.'
63
- ARRAY_MSG = 'Pass the contents of array literals ' \
64
- 'directly to `when` conditions.'
63
+ MSG = 'Reordering `when` conditions with a splat to the end of the `when` branches can improve performance.'
64
+ ARRAY_MSG = 'Pass the contents of array literals directly to `when` conditions.'
65
65
 
66
66
  def on_case(case_node)
67
67
  when_conditions = case_node.when_branches.flat_map(&:conditions)
@@ -134,13 +134,11 @@ module RuboCop
134
134
  end
135
135
 
136
136
  def new_condition_with_then(node, new_condition)
137
- "\n#{indent_for(node)}when " \
138
- "#{new_condition} then #{node.body.source}"
137
+ "\n#{indent_for(node)}when #{new_condition} then #{node.body.source}"
139
138
  end
140
139
 
141
140
  def new_branch_without_then(node, new_condition)
142
- "\n#{indent_for(node)}when #{new_condition}" \
143
- "\n#{indent_for(node.body)}#{node.body.source}"
141
+ "\n#{indent_for(node)}when #{new_condition}\n#{indent_for(node.body)}#{node.body.source}"
144
142
  end
145
143
 
146
144
  def indent_for(node)
@@ -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
@@ -33,7 +33,7 @@ module RuboCop
33
33
  #
34
34
  class CollectionLiteralInLoop < Base
35
35
  MSG = 'Avoid immutable %<literal_class>s literals in loops. '\
36
- 'It is better to extract it into a local variable or a constant.'
36
+ 'It is better to extract it into a local variable or a constant.'
37
37
 
38
38
  POST_CONDITION_LOOP_TYPES = %i[while_post until_post].freeze
39
39
  LOOP_TYPES = (POST_CONDITION_LOOP_TYPES + %i[while until for]).freeze
@@ -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
@@ -14,6 +14,9 @@ module RuboCop
14
14
  #
15
15
  # The `delete_prefix('prefix')` method is faster than `gsub(/\Aprefix/, '')`.
16
16
  #
17
+ # @safety
18
+ # This cop is unsafe because `Pathname` has `sub` but not `delete_prefix`.
19
+ #
17
20
  # @example
18
21
  #
19
22
  # # bad
@@ -46,9 +49,6 @@ module RuboCop
46
49
  class DeletePrefix < Base
47
50
  include RegexpMetacharacter
48
51
  extend AutoCorrector
49
- extend TargetRubyVersion
50
-
51
- minimum_target_ruby_version 2.5
52
52
 
53
53
  MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
54
54
  RESTRICT_ON_SEND = %i[gsub gsub! sub sub!].freeze
@@ -66,7 +66,7 @@ module RuboCop
66
66
 
67
67
  def on_send(node)
68
68
  return unless (receiver, bad_method, regexp_str, replace_string = delete_prefix_candidate?(node))
69
- return unless replace_string.blank?
69
+ return unless replace_string.empty?
70
70
 
71
71
  good_method = PREFERRED_METHODS[bad_method]
72
72
 
@@ -14,6 +14,9 @@ module RuboCop
14
14
  #
15
15
  # The `delete_suffix('suffix')` method is faster than `gsub(/suffix\z/, '')`.
16
16
  #
17
+ # @safety
18
+ # This cop is unsafe because `Pathname` has `sub` but not `delete_suffix`.
19
+ #
17
20
  # @example
18
21
  #
19
22
  # # bad
@@ -46,9 +49,6 @@ module RuboCop
46
49
  class DeleteSuffix < Base
47
50
  include RegexpMetacharacter
48
51
  extend AutoCorrector
49
- extend TargetRubyVersion
50
-
51
- minimum_target_ruby_version 2.5
52
52
 
53
53
  MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
54
54
  RESTRICT_ON_SEND = %i[gsub gsub! sub sub!].freeze
@@ -66,7 +66,7 @@ module RuboCop
66
66
 
67
67
  def on_send(node)
68
68
  return unless (receiver, bad_method, regexp_str, replace_string = delete_suffix_candidate?(node))
69
- return unless replace_string.blank?
69
+ return unless replace_string.empty?
70
70
 
71
71
  good_method = PREFERRED_METHODS[bad_method]
72
72
 
@@ -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
@@ -31,14 +36,10 @@ module RuboCop
31
36
 
32
37
  CANDIDATE_METHODS = Set[:select, :find_all, :filter].freeze
33
38
 
34
- MSG = 'Use `%<prefer>s` instead of ' \
35
- '`%<first_method>s.%<second_method>s`.'
36
- REVERSE_MSG = 'Use `reverse.%<prefer>s` instead of ' \
37
- '`%<first_method>s.%<second_method>s`.'
38
- INDEX_MSG = 'Use `%<prefer>s` instead of ' \
39
- '`%<first_method>s[%<index>i]`.'
40
- INDEX_REVERSE_MSG = 'Use `reverse.%<prefer>s` instead of ' \
41
- '`%<first_method>s[%<index>i]`.'
39
+ MSG = 'Use `%<prefer>s` instead of `%<first_method>s.%<second_method>s`.'
40
+ REVERSE_MSG = 'Use `reverse.%<prefer>s` instead of `%<first_method>s.%<second_method>s`.'
41
+ INDEX_MSG = 'Use `%<prefer>s` instead of `%<first_method>s[%<index>i]`.'
42
+ INDEX_REVERSE_MSG = 'Use `reverse.%<prefer>s` instead of `%<first_method>s[%<index>i]`.'
42
43
  RESTRICT_ON_SEND = %i[first last []].freeze
43
44
 
44
45
  def_node_matcher :detect_candidate?, <<~PATTERN
@@ -94,7 +95,7 @@ module RuboCop
94
95
  end
95
96
 
96
97
  def replacement(method, index)
97
- if method == :last || method == :[] && index == -1
98
+ if method == :last || (method == :[] && index == -1)
98
99
  "reverse.#{preferred_method}"
99
100
  else
100
101
  preferred_method
@@ -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
  # ----
@@ -23,6 +25,7 @@ module RuboCop
23
25
  # # good
24
26
  # ary.filter_map(&:foo)
25
27
  # ary.map(&:foo).compact!
28
+ # ary.compact.map(&:foo)
26
29
  #
27
30
  class MapCompact < Base
28
31
  include RangeHelp
@@ -55,23 +58,24 @@ module RuboCop
55
58
 
56
59
  add_offense(range) do |corrector|
57
60
  corrector.replace(map_node.loc.selector, 'filter_map')
58
- corrector.remove(compact_loc.dot)
59
- corrector.remove(compact_method_range(node))
61
+ remove_compact_method(corrector, node)
60
62
  end
61
63
  end
62
64
 
63
65
  private
64
66
 
65
- def compact_method_range(compact_node)
67
+ def remove_compact_method(corrector, compact_node)
66
68
  chained_method = compact_node.parent
67
69
  compact_method_range = compact_node.loc.selector
68
70
 
69
- if compact_node.multiline? &&
70
- chained_method && !invoke_method_after_map_compact_on_same_line?(compact_node, chained_method)
71
- range_by_whole_lines(compact_method_range, include_final_newline: true)
71
+ if compact_node.multiline? && chained_method&.loc.respond_to?(:selector) && chained_method.dot? &&
72
+ !invoke_method_after_map_compact_on_same_line?(compact_node, chained_method)
73
+ compact_method_range = range_by_whole_lines(compact_method_range, include_final_newline: true)
72
74
  else
73
- compact_method_range
75
+ corrector.remove(compact_node.loc.dot)
74
76
  end
77
+
78
+ corrector.remove(compact_method_range)
75
79
  end
76
80
 
77
81
  def invoke_method_after_map_compact_on_same_line?(compact_node, chained_method)
@@ -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)
@@ -16,35 +16,35 @@ module RuboCop
16
16
  # # bad
17
17
  # str.chars.first
18
18
  # str.chars.first(2)
19
- # str.chars.last
20
- # str.chars.last(2)
21
19
  #
22
20
  # # good
23
21
  # str[0]
24
22
  # str[0...2].chars
25
- # str[-1]
26
- # str[-2..-1].chars
27
23
  #
28
24
  # # bad
29
25
  # str.chars.take(2)
30
- # str.chars.drop(2)
31
26
  # str.chars.length
32
27
  # str.chars.size
33
28
  # str.chars.empty?
34
29
  #
35
30
  # # good
36
31
  # str[0...2].chars
37
- # str[2..-1].chars
38
32
  # str.length
39
33
  # str.size
40
34
  # str.empty?
41
35
  #
36
+ # # For example, if the receiver is a blank string, it will be incompatible.
37
+ # # If a negative value is specified for the receiver, `nil` is returned.
38
+ # str.chars.last # Incompatible with `str[-1]`.
39
+ # str.chars.last(2) # Incompatible with `str[-2..-1].chars`.
40
+ # str.chars.drop(2) # Incompatible with `str[2..-1].chars`.
41
+ #
42
42
  class RedundantStringChars < Base
43
43
  include RangeHelp
44
44
  extend AutoCorrector
45
45
 
46
46
  MSG = 'Use `%<good_method>s` instead of `%<bad_method>s`.'
47
- RESTRICT_ON_SEND = %i[[] slice first last take drop length size empty?].freeze
47
+ RESTRICT_ON_SEND = %i[[] slice first take length size empty?].freeze
48
48
 
49
49
  def_node_matcher :redundant_chars_call?, <<~PATTERN
50
50
  (send $(send _ :chars) $_ $...)
@@ -80,7 +80,6 @@ module RuboCop
80
80
  format(MSG, good_method: good_method, bad_method: bad_method)
81
81
  end
82
82
 
83
- # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
84
83
  def build_good_method(method, args)
85
84
  case method
86
85
  when :[], :slice
@@ -91,21 +90,12 @@ module RuboCop
91
90
  else
92
91
  '[0]'
93
92
  end
94
- when :last
95
- if args.any?
96
- "[-#{args.first.source}..-1].chars"
97
- else
98
- '[-1]'
99
- end
100
93
  when :take
101
94
  "[0...#{args.first.source}].chars"
102
- when :drop
103
- "[#{args.first.source}..-1].chars"
104
95
  else
105
96
  ".#{method}"
106
97
  end
107
98
  end
108
- # rubocop:enable Metrics/CyclomaticComplexity, Metrics/MethodLength
109
99
 
110
100
  def build_bad_method(method, args)
111
101
  case method
@@ -78,8 +78,7 @@ module RuboCop
78
78
  # Constants are included in this list because it is unlikely that
79
79
  # someone will store `nil` as a constant and then use it for comparison
80
80
  TYPES_IMPLEMENTING_MATCH = %i[const regexp str sym].freeze
81
- MSG = 'Use `match?` instead of `%<current>s` when `MatchData` ' \
82
- 'is not used.'
81
+ MSG = 'Use `match?` instead of `%<current>s` when `MatchData` is not used.'
83
82
 
84
83
  def_node_matcher :match_method?, <<~PATTERN
85
84
  {
@@ -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/)
@@ -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
@@ -156,7 +156,7 @@ module RuboCop
156
156
  end
157
157
 
158
158
  def sum_method_range(node)
159
- range_between(node.loc.selector.begin_pos, node.loc.end.end_pos)
159
+ range_between(node.loc.selector.begin_pos, node.loc.expression.end_pos)
160
160
  end
161
161
 
162
162
  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
@@ -45,7 +45,10 @@ module RuboCop
45
45
  return unless dup_string?(node) || string_new?(node)
46
46
 
47
47
  add_offense(node) do |corrector|
48
- corrector.replace(node, "+#{string_value(node)}")
48
+ string_value = "+#{string_value(node)}"
49
+ string_value = "(#{string_value})" if node.parent&.send_type?
50
+
51
+ corrector.replace(node, string_value)
49
52
  end
50
53
  end
51
54
 
@@ -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'
@@ -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.2'
7
+ STRING = '1.12.0'
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.2
4
+ version: 1.12.0
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-05-05 00:00:00.000000000 Z
13
+ date: 2021-10-31 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
@@ -121,7 +122,7 @@ metadata:
121
122
  homepage_uri: https://docs.rubocop.org/rubocop-performance/
122
123
  changelog_uri: https://github.com/rubocop/rubocop-performance/blob/master/CHANGELOG.md
123
124
  source_code_uri: https://github.com/rubocop/rubocop-performance/
124
- documentation_uri: https://docs.rubocop.org/rubocop-performance/1.11/
125
+ documentation_uri: https://docs.rubocop.org/rubocop-performance/1.12/
125
126
  bug_tracker_uri: https://github.com/rubocop/rubocop-performance/issues
126
127
  post_install_message:
127
128
  rdoc_options: []
@@ -138,7 +139,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
138
139
  - !ruby/object:Gem::Version
139
140
  version: '0'
140
141
  requirements: []
141
- rubygems_version: 3.2.12
142
+ rubygems_version: 3.3.0.dev
142
143
  signing_key:
143
144
  specification_version: 4
144
145
  summary: Automatic performance checking tool for Ruby code.