rubocop-performance 1.18.0 → 1.20.2

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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -0
  3. data/config/default.yml +22 -16
  4. data/lib/rubocop/cop/mixin/sort_block.rb +2 -2
  5. data/lib/rubocop/cop/performance/array_semi_infinite_range_slice.rb +2 -1
  6. data/lib/rubocop/cop/performance/block_given_with_explicit_block.rb +4 -0
  7. data/lib/rubocop/cop/performance/count.rb +5 -5
  8. data/lib/rubocop/cop/performance/delete_prefix.rb +5 -2
  9. data/lib/rubocop/cop/performance/delete_suffix.rb +5 -2
  10. data/lib/rubocop/cop/performance/detect.rb +3 -2
  11. data/lib/rubocop/cop/performance/end_with.rb +4 -2
  12. data/lib/rubocop/cop/performance/fixed_size.rb +4 -3
  13. data/lib/rubocop/cop/performance/flat_map.rb +4 -3
  14. data/lib/rubocop/cop/performance/inefficient_hash_search.rb +16 -10
  15. data/lib/rubocop/cop/performance/map_compact.rb +5 -4
  16. data/lib/rubocop/cop/performance/map_method_chain.rb +89 -0
  17. data/lib/rubocop/cop/performance/range_include.rb +8 -6
  18. data/lib/rubocop/cop/performance/redundant_equality_comparison_block.rb +1 -1
  19. data/lib/rubocop/cop/performance/redundant_match.rb +30 -1
  20. data/lib/rubocop/cop/performance/redundant_merge.rb +5 -3
  21. data/lib/rubocop/cop/performance/redundant_split_regexp_argument.rb +2 -1
  22. data/lib/rubocop/cop/performance/reverse_each.rb +2 -1
  23. data/lib/rubocop/cop/performance/reverse_first.rb +6 -13
  24. data/lib/rubocop/cop/performance/select_map.rb +3 -6
  25. data/lib/rubocop/cop/performance/size.rb +4 -3
  26. data/lib/rubocop/cop/performance/sort_reverse.rb +5 -4
  27. data/lib/rubocop/cop/performance/squeeze.rb +5 -2
  28. data/lib/rubocop/cop/performance/start_with.rb +4 -2
  29. data/lib/rubocop/cop/performance/string_identifier_argument.rb +58 -10
  30. data/lib/rubocop/cop/performance/string_include.rb +9 -4
  31. data/lib/rubocop/cop/performance/string_replacement.rb +2 -1
  32. data/lib/rubocop/cop/performance/sum.rb +11 -9
  33. data/lib/rubocop/cop/performance/times_map.rb +14 -2
  34. data/lib/rubocop/cop/performance/unfreeze_string.rb +6 -3
  35. data/lib/rubocop/cop/performance/uri_default_parser.rb +1 -1
  36. data/lib/rubocop/cop/performance_cops.rb +1 -0
  37. data/lib/rubocop/performance/version.rb +1 -1
  38. data/lib/rubocop-performance.rb +8 -0
  39. metadata +15 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f4926be80598b4880037ffd36eef32c9025051af7f0398f69a34439c135c9d87
4
- data.tar.gz: ba3d738da529cbc70c33e8c5d4baa4c6c8013a5498c834f2e28dc87a8811c15a
3
+ metadata.gz: 1dad3fdab16d4dbcb3047e3319ab415b6e9ae3e2cbf14a73a1874831bf473701
4
+ data.tar.gz: ca98aec2aac71f768e63c5b5ed3c73c2a5ef8242433fc8fe078e9930f47a4d38
5
5
  SHA512:
6
- metadata.gz: 1b9a64379554a660c30bffea5a0fc8a8d36a9e31efef6ccb4576f4df432d2f270d91d76b0e7e7638740e6cf83a50b94b4dfe37d93e5366d0171b8a0f4e5bff50
7
- data.tar.gz: d27b9db647eb1a53f3e80b0ec20cd43b37b0244127a28f7fbdacefcf9477205a7f4d373c0a27d882fcb43f902d4ab398ad73ea3f51cdd21e5d422cd419d621e9
6
+ metadata.gz: 16536c625e9f66d73368d70153a139f72da96dee225807c1154c07e7a2df999be9f06278b1c6db95836baa6832524da39715f8240151236c5b29ec47ce99e91b
7
+ data.tar.gz: bfd72acdc38b7337c4b783fc8e5e14516aa8926980cfbf2051487350eb939648ac9af302e1f5c0abc889f3564efb98ff5a7551d107d8d28b2912c4c9a0128b79
data/README.md CHANGED
@@ -53,6 +53,8 @@ $ rubocop --require rubocop-performance
53
53
  ### Rake task
54
54
 
55
55
  ```ruby
56
+ require 'rubocop/rake_task'
57
+
56
58
  RuboCop::RakeTask.new do |task|
57
59
  task.requires << 'rubocop-performance'
58
60
  end
data/config/default.yml CHANGED
@@ -6,7 +6,7 @@ Performance:
6
6
 
7
7
  Performance/AncestorsInclude:
8
8
  Description: 'Use `A <= B` instead of `A.ancestors.include?(B)`.'
9
- Reference: 'https://github.com/JuanitoFatas/fast-ruby#ancestorsinclude-vs--code'
9
+ Reference: 'https://github.com/fastruby/fast-ruby#ancestorsinclude-vs--code'
10
10
  Enabled: 'pending'
11
11
  Safe: false
12
12
  VersionAdded: '1.7'
@@ -54,7 +54,7 @@ Performance/CaseWhenSplat:
54
54
  Performance/Casecmp:
55
55
  Description: >-
56
56
  Use `casecmp` rather than `downcase ==`, `upcase ==`, `== downcase`, or `== upcase`..
57
- Reference: 'https://github.com/JuanitoFatas/fast-ruby#stringcasecmp-vs-stringdowncase---code'
57
+ Reference: 'https://github.com/fastruby/fast-ruby#stringcasecmp-vs--stringcasecmp-vs-stringdowncase---code'
58
58
  Enabled: true
59
59
  Safe: false
60
60
  VersionAdded: '0.36'
@@ -122,7 +122,7 @@ Performance/Detect:
122
122
  Description: >-
123
123
  Use `detect` instead of `select.first`, `find_all.first`, `filter.first`,
124
124
  `select.last`, `find_all.last`, and `filter.last`.
125
- Reference: 'https://github.com/JuanitoFatas/fast-ruby#enumerabledetect-vs-enumerableselectfirst-code'
125
+ Reference: 'https://github.com/fastruby/fast-ruby#enumerabledetect-vs-enumerableselectfirst-code'
126
126
  # This cop has known compatibility issues with `ActiveRecord` and other
127
127
  # frameworks. `ActiveRecord` does not implement a `detect` method and `find`
128
128
  # has its own meaning. Correcting `ActiveRecord` methods with this cop
@@ -145,7 +145,7 @@ Performance/DoubleStartEndWith:
145
145
 
146
146
  Performance/EndWith:
147
147
  Description: 'Use `end_with?` instead of a regex match anchored to the end of a string.'
148
- Reference: 'https://github.com/JuanitoFatas/fast-ruby#stringmatch-vs-stringstart_withstringend_with-code-start-code-end'
148
+ Reference: 'https://github.com/fastruby/fast-ruby#stringmatch-vs-stringmatch-vs-stringstart_withstringend_with-code-start-code-end'
149
149
  # This will change to a new method call which isn't guaranteed to be on the
150
150
  # object. Switching these methods has to be done with knowledge of the types
151
151
  # of the variables which rubocop doesn't have.
@@ -165,7 +165,7 @@ Performance/FlatMap:
165
165
  Use `Enumerable#flat_map`
166
166
  instead of `Enumerable#map...Array#flatten(1)`
167
167
  or `Enumerable#collect..Array#flatten(1)`.
168
- Reference: 'https://github.com/JuanitoFatas/fast-ruby#enumerablemaparrayflatten-vs-enumerableflat_map-code'
168
+ Reference: 'https://github.com/fastruby/fast-ruby#enumerablemaparrayflatten-vs-enumerableflat_map-code'
169
169
  Enabled: true
170
170
  VersionAdded: '0.30'
171
171
  EnabledForFlattenWithoutParams: false
@@ -176,7 +176,7 @@ Performance/FlatMap:
176
176
 
177
177
  Performance/InefficientHashSearch:
178
178
  Description: 'Use `key?` or `value?` instead of `keys.include?` or `values.include?`.'
179
- Reference: 'https://github.com/JuanitoFatas/fast-ruby#hashkey-instead-of-hashkeysinclude-code'
179
+ Reference: 'https://github.com/fastruby/fast-ruby#hashkey-instead-of-hashkeysinclude-code'
180
180
  Enabled: true
181
181
  VersionAdded: '0.56'
182
182
  Safe: false
@@ -193,9 +193,15 @@ Performance/MapCompact:
193
193
  SafeAutoCorrect: false
194
194
  VersionAdded: '1.11'
195
195
 
196
+ Performance/MapMethodChain:
197
+ Description: 'Checks if the `map` method is used in a chain.'
198
+ Enabled: pending
199
+ Safe: false
200
+ VersionAdded: '1.19'
201
+
196
202
  Performance/MethodObjectAsBlock:
197
203
  Description: 'Use block explicitly instead of block-passing a method object.'
198
- Reference: 'https://github.com/JuanitoFatas/fast-ruby#normal-way-to-apply-method-vs-method-code'
204
+ Reference: 'https://github.com/fastruby/fast-ruby#normal-way-to-apply-method-vs-method-code'
199
205
  Enabled: pending
200
206
  VersionAdded: '1.9'
201
207
 
@@ -207,7 +213,7 @@ Performance/OpenStruct:
207
213
 
208
214
  Performance/RangeInclude:
209
215
  Description: 'Use `Range#cover?` instead of `Range#include?` (or `Range#member?`).'
210
- Reference: 'https://github.com/JuanitoFatas/fast-ruby#cover-vs-include-code'
216
+ Reference: 'https://github.com/fastruby/fast-ruby#cover-vs-include-code'
211
217
  Enabled: true
212
218
  VersionAdded: '0.36'
213
219
  VersionChanged: '1.7'
@@ -215,7 +221,7 @@ Performance/RangeInclude:
215
221
 
216
222
  Performance/RedundantBlockCall:
217
223
  Description: 'Use `yield` instead of `block.call`.'
218
- Reference: 'https://github.com/JuanitoFatas/fast-ruby#proccall-and-block-arguments-vs-yieldcode'
224
+ Reference: 'https://github.com/fastruby/fast-ruby#proccall-and-block-arguments-vs-yieldcode'
219
225
  Enabled: true
220
226
  VersionAdded: '0.36'
221
227
 
@@ -238,7 +244,7 @@ Performance/RedundantMatch:
238
244
 
239
245
  Performance/RedundantMerge:
240
246
  Description: 'Use Hash#[]=, rather than Hash#merge! with a single key-value pair.'
241
- Reference: 'https://github.com/JuanitoFatas/fast-ruby#hashmerge-vs-hash-code'
247
+ Reference: 'https://github.com/fastruby/fast-ruby#hashmerge-vs-hash-code'
242
248
  Enabled: true
243
249
  Safe: false
244
250
  VersionAdded: '0.36'
@@ -265,13 +271,13 @@ Performance/RegexpMatch:
265
271
  Description: >-
266
272
  Use `match?` instead of `Regexp#match`, `String#match`, `Symbol#match`,
267
273
  `Regexp#===`, or `=~` when `MatchData` is not used.
268
- Reference: 'https://github.com/JuanitoFatas/fast-ruby#regexp-vs-stringmatch-vs-string-vs-stringmatch-code-'
274
+ Reference: 'https://github.com/fastruby/fast-ruby#regexp-vs-regexpmatch-vs-regexpmatch-vs-stringmatch-vs-string-vs-stringmatch-code-'
269
275
  Enabled: true
270
276
  VersionAdded: '0.47'
271
277
 
272
278
  Performance/ReverseEach:
273
279
  Description: 'Use `reverse_each` instead of `reverse.each`.'
274
- Reference: 'https://github.com/JuanitoFatas/fast-ruby#enumerablereverseeach-vs-enumerablereverse_each-code'
280
+ Reference: 'https://github.com/fastruby/fast-ruby#enumerablereverseeach-vs-enumerablereverse_each-code'
275
281
  Enabled: true
276
282
  VersionAdded: '0.30'
277
283
 
@@ -289,7 +295,7 @@ Performance/Size:
289
295
  Description: >-
290
296
  Use `size` instead of `count` for counting
291
297
  the number of elements in `Array` and `Hash`.
292
- Reference: 'https://github.com/JuanitoFatas/fast-ruby#arraylength-vs-arraysize-vs-arraycount-code'
298
+ Reference: 'https://github.com/fastruby/fast-ruby#arraylength-vs-arraysize-vs-arraycount-code'
293
299
  Enabled: true
294
300
  VersionAdded: '0.30'
295
301
 
@@ -300,13 +306,13 @@ Performance/SortReverse:
300
306
 
301
307
  Performance/Squeeze:
302
308
  Description: "Use `squeeze('a')` instead of `gsub(/a+/, 'a')`."
303
- Reference: 'https://github.com/JuanitoFatas/fast-ruby#remove-extra-spaces-or-other-contiguous-characters-code'
309
+ Reference: 'https://github.com/fastruby/fast-ruby#remove-extra-spaces-or-other-contiguous-characters-code'
304
310
  Enabled: 'pending'
305
311
  VersionAdded: '1.7'
306
312
 
307
313
  Performance/StartWith:
308
314
  Description: 'Use `start_with?` instead of a regex match anchored to the beginning of a string.'
309
- Reference: 'https://github.com/JuanitoFatas/fast-ruby#stringmatch-vs-stringstart_withstringend_with-code-start-code-end'
315
+ Reference: 'https://github.com/fastruby/fast-ruby#stringmatch-vs-stringmatch-vs-stringstart_withstringend_with-code-start-code-end'
310
316
  # This will change to a new method call which isn't guaranteed to be on the
311
317
  # object. Switching these methods has to be done with knowledge of the types
312
318
  # of the variables which rubocop doesn't have.
@@ -333,7 +339,7 @@ Performance/StringReplacement:
333
339
  Use `tr` instead of `gsub` when you are replacing the same
334
340
  number of characters. Use `delete` instead of `gsub` when
335
341
  you are deleting characters.
336
- Reference: 'https://github.com/JuanitoFatas/fast-ruby#stringgsub-vs-stringtr-code'
342
+ Reference: 'https://github.com/fastruby/fast-ruby#stringgsub-vs-stringtr-code'
337
343
  Enabled: true
338
344
  VersionAdded: '0.33'
339
345
 
@@ -9,14 +9,14 @@ module RuboCop
9
9
 
10
10
  def_node_matcher :sort_with_block?, <<~PATTERN
11
11
  (block
12
- $(send _ :sort)
12
+ $(call _ :sort)
13
13
  (args (arg $_a) (arg $_b))
14
14
  $send)
15
15
  PATTERN
16
16
 
17
17
  def_node_matcher :sort_with_numblock?, <<~PATTERN
18
18
  (numblock
19
- $(send _ :sort)
19
+ $(call _ :sort)
20
20
  $_arg_count
21
21
  $send)
22
22
  PATTERN
@@ -39,7 +39,7 @@ module RuboCop
39
39
  RESTRICT_ON_SEND = SLICE_METHODS
40
40
 
41
41
  def_node_matcher :endless_range_slice?, <<~PATTERN
42
- (send $_ $%SLICE_METHODS $#endless_range?)
42
+ (call $!{str dstr xstr} $%SLICE_METHODS $#endless_range?)
43
43
  PATTERN
44
44
 
45
45
  def_node_matcher :endless_range?, <<~PATTERN
@@ -59,6 +59,7 @@ module RuboCop
59
59
  end
60
60
  end
61
61
  end
62
+ alias on_csend on_send
62
63
 
63
64
  private
64
65
 
@@ -47,6 +47,10 @@ module RuboCop
47
47
  corrector.replace(node, block_arg_name)
48
48
  end
49
49
  end
50
+
51
+ def self.autocorrect_incompatible_with
52
+ [Lint::UnusedMethodArgument]
53
+ end
50
54
  end
51
55
  end
52
56
  end
@@ -9,8 +9,7 @@ module RuboCop
9
9
  #
10
10
  # @safety
11
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`.
12
+ # frameworks. Before Rails 5.1, `ActiveRecord` will ignore the block that is passed to `count`.
14
13
  # Other methods, such as `select`, will convert the association to an
15
14
  # array and then run the block on the array. A simple work around to
16
15
  # make `count` work with a block is to call `to_a.count {...}`.
@@ -55,8 +54,8 @@ module RuboCop
55
54
 
56
55
  def_node_matcher :count_candidate?, <<~PATTERN
57
56
  {
58
- (send (block $(send _ ${:select :filter :find_all :reject}) ...) ${:count :length :size})
59
- (send $(send _ ${:select :filter :find_all :reject} (:block_pass _)) ${:count :length :size})
57
+ (call (block $(call _ ${:select :filter :find_all :reject}) ...) ${:count :length :size})
58
+ (call $(call _ ${:select :filter :find_all :reject} (:block_pass _)) ${:count :length :size})
60
59
  }
61
60
  PATTERN
62
61
 
@@ -73,6 +72,7 @@ module RuboCop
73
72
  end
74
73
  end
75
74
  end
75
+ alias on_csend on_send
76
76
 
77
77
  private
78
78
 
@@ -101,7 +101,7 @@ module RuboCop
101
101
  end
102
102
 
103
103
  def negate_reject(corrector, node)
104
- if node.receiver.send_type?
104
+ if node.receiver.call_type?
105
105
  negate_block_pass_reject(corrector, node)
106
106
  else
107
107
  negate_block_reject(corrector, node)
@@ -64,9 +64,10 @@ module RuboCop
64
64
  }.freeze
65
65
 
66
66
  def_node_matcher :delete_prefix_candidate?, <<~PATTERN
67
- (send $!nil? ${:gsub :gsub! :sub :sub!} (regexp (str $#literal_at_start?) (regopt)) (str $_))
67
+ (call $!nil? ${:gsub :gsub! :sub :sub!} (regexp (str $#literal_at_start?) (regopt)) (str $_))
68
68
  PATTERN
69
69
 
70
+ # rubocop:disable Metrics/AbcSize
70
71
  def on_send(node)
71
72
  return unless (receiver, bad_method, regexp_str, replace_string = delete_prefix_candidate?(node))
72
73
  return unless replace_string.empty?
@@ -80,11 +81,13 @@ module RuboCop
80
81
  regexp_str = interpret_string_escapes(regexp_str)
81
82
  string_literal = to_string_literal(regexp_str)
82
83
 
83
- new_code = "#{receiver.source}.#{good_method}(#{string_literal})"
84
+ new_code = "#{receiver.source}#{node.loc.dot.source}#{good_method}(#{string_literal})"
84
85
 
85
86
  corrector.replace(node, new_code)
86
87
  end
87
88
  end
89
+ # rubocop:enable Metrics/AbcSize
90
+ alias on_csend on_send
88
91
  end
89
92
  end
90
93
  end
@@ -64,9 +64,10 @@ module RuboCop
64
64
  }.freeze
65
65
 
66
66
  def_node_matcher :delete_suffix_candidate?, <<~PATTERN
67
- (send $!nil? ${:gsub :gsub! :sub :sub!} (regexp (str $#literal_at_end?) (regopt)) (str $_))
67
+ (call $!nil? ${:gsub :gsub! :sub :sub!} (regexp (str $#literal_at_end?) (regopt)) (str $_))
68
68
  PATTERN
69
69
 
70
+ # rubocop:disable Metrics/AbcSize
70
71
  def on_send(node)
71
72
  return unless (receiver, bad_method, regexp_str, replace_string = delete_suffix_candidate?(node))
72
73
  return unless replace_string.empty?
@@ -80,11 +81,13 @@ module RuboCop
80
81
  regexp_str = interpret_string_escapes(regexp_str)
81
82
  string_literal = to_string_literal(regexp_str)
82
83
 
83
- new_code = "#{receiver.source}.#{good_method}(#{string_literal})"
84
+ new_code = "#{receiver.source}#{node.loc.dot.source}#{good_method}(#{string_literal})"
84
85
 
85
86
  corrector.replace(node, new_code)
86
87
  end
87
88
  end
89
+ # rubocop:enable Metrics/AbcSize
90
+ alias on_csend on_send
88
91
  end
89
92
  end
90
93
  end
@@ -40,9 +40,9 @@ module RuboCop
40
40
 
41
41
  def_node_matcher :detect_candidate?, <<~PATTERN
42
42
  {
43
- (send $(block (send _ %CANDIDATE_METHODS) ...) ${:first :last} $...)
43
+ (send $(block (call _ %CANDIDATE_METHODS) ...) ${:first :last} $...)
44
44
  (send $(block (send _ %CANDIDATE_METHODS) ...) $:[] (int ${0 -1}))
45
- (send $(send _ %CANDIDATE_METHODS ...) ${:first :last} $...)
45
+ (send $(call _ %CANDIDATE_METHODS ...) ${:first :last} $...)
46
46
  (send $(send _ %CANDIDATE_METHODS ...) $:[] (int ${0 -1}))
47
47
  }
48
48
  PATTERN
@@ -63,6 +63,7 @@ module RuboCop
63
63
  register_offense(node, receiver, second_method, index)
64
64
  end
65
65
  end
66
+ alias on_csend on_send
66
67
 
67
68
  private
68
69
 
@@ -54,7 +54,7 @@ module RuboCop
54
54
  RESTRICT_ON_SEND = %i[match =~ match?].freeze
55
55
 
56
56
  def_node_matcher :redundant_regex?, <<~PATTERN
57
- {(send $!nil? {:match :=~ :match?} (regexp (str $#literal_at_end?) (regopt)))
57
+ {(call $!nil? {:match :=~ :match?} (regexp (str $#literal_at_end?) (regopt)))
58
58
  (send (regexp (str $#literal_at_end?) (regopt)) {:match :match?} $_)
59
59
  (match-with-lvasgn (regexp (str $#literal_at_end?) (regopt)) $_)}
60
60
  PATTERN
@@ -66,12 +66,14 @@ module RuboCop
66
66
  receiver, regex_str = regex_str, receiver if receiver.is_a?(String)
67
67
  regex_str = drop_end_metacharacter(regex_str)
68
68
  regex_str = interpret_string_escapes(regex_str)
69
+ dot = node.loc.dot ? node.loc.dot.source : '.'
69
70
 
70
- new_source = "#{receiver.source}.end_with?(#{to_string_literal(regex_str)})"
71
+ new_source = "#{receiver.source}#{dot}end_with?(#{to_string_literal(regex_str)})"
71
72
 
72
73
  corrector.replace(node, new_source)
73
74
  end
74
75
  end
76
+ alias on_csend on_send
75
77
  alias on_match_with_lvasgn on_send
76
78
  end
77
79
  end
@@ -50,7 +50,7 @@ module RuboCop
50
50
  RESTRICT_ON_SEND = %i[count length size].freeze
51
51
 
52
52
  def_node_matcher :counter, <<~MATCHER
53
- (send ${array hash str sym} {:count :length :size} $...)
53
+ (call ${array hash str sym} {:count :length :size} $...)
54
54
  MATCHER
55
55
 
56
56
  def on_send(node)
@@ -62,6 +62,7 @@ module RuboCop
62
62
  add_offense(node)
63
63
  end
64
64
  end
65
+ alias on_csend on_send
65
66
 
66
67
  private
67
68
 
@@ -78,13 +79,13 @@ module RuboCop
78
79
  end
79
80
 
80
81
  def contains_splat?(node)
81
- return unless node.array_type?
82
+ return false unless node.array_type?
82
83
 
83
84
  node.each_child_node(:splat).any?
84
85
  end
85
86
 
86
87
  def contains_double_splat?(node)
87
- return unless node.hash_type?
88
+ return false unless node.hash_type?
88
89
 
89
90
  node.each_child_node(:kwsplat).any?
90
91
  end
@@ -26,10 +26,10 @@ module RuboCop
26
26
  'multiple levels.'
27
27
 
28
28
  def_node_matcher :flat_map_candidate?, <<~PATTERN
29
- (send
29
+ (call
30
30
  {
31
- $(block (send _ ${:collect :map}) ...)
32
- $(send _ ${:collect :map} (block_pass _))
31
+ $(block (call _ ${:collect :map}) ...)
32
+ $(call _ ${:collect :map} (block_pass _))
33
33
  }
34
34
  ${:flatten :flatten!}
35
35
  $...
@@ -46,6 +46,7 @@ module RuboCop
46
46
  end
47
47
  end
48
48
  end
49
+ alias on_csend on_send
49
50
 
50
51
  private
51
52
 
@@ -45,7 +45,7 @@ module RuboCop
45
45
  RESTRICT_ON_SEND = %i[include?].freeze
46
46
 
47
47
  def_node_matcher :inefficient_include?, <<~PATTERN
48
- (send (send $_ {:keys :values}) :include? _)
48
+ (call (call $_ {:keys :values}) :include? _)
49
49
  PATTERN
50
50
 
51
51
  def on_send(node)
@@ -56,21 +56,23 @@ module RuboCop
56
56
  add_offense(node, message: message) do |corrector|
57
57
  # Replace `keys.include?` or `values.include?` with the appropriate
58
58
  # `key?`/`value?` method.
59
- corrector.replace(
60
- node,
61
- "#{autocorrect_hash_expression(node)}.#{autocorrect_method(node)}(#{autocorrect_argument(node)})"
62
- )
59
+ corrector.replace(node, replacement(node))
63
60
  end
64
61
  end
65
62
  end
63
+ alias on_csend on_send
66
64
 
67
65
  private
68
66
 
69
67
  def message(node)
70
- "Use `##{autocorrect_method(node)}` instead of `##{current_method(node)}.include?`."
68
+ "Use `##{correct_method(node)}` instead of `##{current_method(node)}.include?`."
71
69
  end
72
70
 
73
- def autocorrect_method(node)
71
+ def replacement(node)
72
+ "#{correct_hash_expression(node)}#{correct_dot(node)}#{correct_method(node)}(#{correct_argument(node)})"
73
+ end
74
+
75
+ def correct_method(node)
74
76
  case current_method(node)
75
77
  when :keys then use_long_method ? 'has_key?' : 'key?'
76
78
  when :values then use_long_method ? 'has_value?' : 'value?'
@@ -86,13 +88,17 @@ module RuboCop
86
88
  preferred_config && preferred_config['EnforcedStyle'] == 'long' && preferred_config['Enabled']
87
89
  end
88
90
 
89
- def autocorrect_argument(node)
90
- node.arguments.first.source
91
+ def correct_argument(node)
92
+ node.first_argument.source
91
93
  end
92
94
 
93
- def autocorrect_hash_expression(node)
95
+ def correct_hash_expression(node)
94
96
  node.receiver.receiver.source
95
97
  end
98
+
99
+ def correct_dot(node)
100
+ node.receiver.loc.dot.source
101
+ end
96
102
  end
97
103
  end
98
104
  end
@@ -39,13 +39,13 @@ module RuboCop
39
39
 
40
40
  def_node_matcher :map_compact, <<~PATTERN
41
41
  {
42
- (send
43
- $(send _ {:map :collect}
42
+ (call
43
+ $(call _ {:map :collect}
44
44
  (block_pass
45
45
  (sym _))) _)
46
- (send
46
+ (call
47
47
  (block
48
- $(send _ {:map :collect})
48
+ $(call _ {:map :collect})
49
49
  (args ...) _) _)
50
50
  }
51
51
  PATTERN
@@ -61,6 +61,7 @@ module RuboCop
61
61
  remove_compact_method(corrector, map_node, node, node.parent)
62
62
  end
63
63
  end
64
+ alias on_csend on_send
64
65
 
65
66
  private
66
67
 
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # Checks if the map method is used in a chain.
7
+ #
8
+ # Autocorrection is not supported because an appropriate block variable name cannot be determined automatically.
9
+ #
10
+ # @safety
11
+ # This cop is unsafe because false positives occur if the number of times the first method is executed
12
+ # affects the return value of subsequent methods.
13
+ #
14
+ # [source,ruby]
15
+ # ----
16
+ # class X
17
+ # def initialize
18
+ # @@num = 0
19
+ # end
20
+ #
21
+ # def foo
22
+ # @@num += 1
23
+ # self
24
+ # end
25
+ #
26
+ # def bar
27
+ # @@num * 2
28
+ # end
29
+ # end
30
+ #
31
+ # [X.new, X.new].map(&:foo).map(&:bar) # => [4, 4]
32
+ # [X.new, X.new].map { |x| x.foo.bar } # => [2, 4]
33
+ # ----
34
+ #
35
+ # @example
36
+ #
37
+ # # bad
38
+ # array.map(&:foo).map(&:bar)
39
+ #
40
+ # # good
41
+ # array.map { |item| item.foo.bar }
42
+ #
43
+ class MapMethodChain < Base
44
+ include IgnoredNode
45
+
46
+ MSG = 'Use `%<method_name>s { |x| x.%<map_args>s }` instead of `%<method_name>s` method chain.'
47
+ RESTRICT_ON_SEND = %i[map collect].freeze
48
+
49
+ def_node_matcher :block_pass_with_symbol_arg?, <<~PATTERN
50
+ (:block_pass (:sym $_))
51
+ PATTERN
52
+
53
+ def on_send(node)
54
+ return if part_of_ignored_node?(node)
55
+ return unless (map_arg = block_pass_with_symbol_arg?(node.first_argument))
56
+
57
+ map_args = [map_arg]
58
+
59
+ return unless (begin_of_chained_map_method = find_begin_of_chained_map_method(node, map_args))
60
+
61
+ range = begin_of_chained_map_method.loc.selector.begin.join(node.source_range.end)
62
+ message = format(MSG, method_name: begin_of_chained_map_method.method_name, map_args: map_args.join('.'))
63
+
64
+ add_offense(range, message: message)
65
+
66
+ ignore_node(node)
67
+ end
68
+
69
+ private
70
+
71
+ # rubocop:disable Metrics/CyclomaticComplexity
72
+ def find_begin_of_chained_map_method(node, map_args)
73
+ return unless (chained_map_method = node.receiver)
74
+ return if !chained_map_method.call_type? || !RESTRICT_ON_SEND.include?(chained_map_method.method_name)
75
+ return unless (map_arg = block_pass_with_symbol_arg?(chained_map_method.first_argument))
76
+
77
+ map_args.unshift(map_arg)
78
+
79
+ receiver = chained_map_method.receiver
80
+
81
+ return chained_map_method unless receiver&.call_type? && block_pass_with_symbol_arg?(receiver.first_argument)
82
+
83
+ find_begin_of_chained_map_method(chained_map_method, map_args)
84
+ end
85
+ # rubocop:enable Metrics/CyclomaticComplexity
86
+ end
87
+ end
88
+ end
89
+ end
@@ -12,6 +12,12 @@ module RuboCop
12
12
  # @safety
13
13
  # This cop is unsafe because `Range#include?` (or `Range#member?`) and `Range#cover?`
14
14
  # are not equivalent behavior.
15
+ # Example of a case where `Range#cover?` may not provide the desired result:
16
+ #
17
+ # [source,ruby]
18
+ # ----
19
+ # ('a'..'z').cover?('yellow') # => true
20
+ # ----
15
21
  #
16
22
  # @example
17
23
  # # bad
@@ -20,11 +26,6 @@ module RuboCop
20
26
  #
21
27
  # # good
22
28
  # ('a'..'z').cover?('b') # => true
23
- #
24
- # # Example of a case where `Range#cover?` may not provide
25
- # # the desired result:
26
- #
27
- # ('a'..'z').cover?('yellow') # => true
28
29
  class RangeInclude < Base
29
30
  extend AutoCorrector
30
31
 
@@ -37,7 +38,7 @@ module RuboCop
37
38
  # (We don't even catch it if the Range is in double parens)
38
39
 
39
40
  def_node_matcher :range_include, <<~PATTERN
40
- (send {irange erange (begin {irange erange})} ${:include? :member?} ...)
41
+ (call {irange erange (begin {irange erange})} ${:include? :member?} ...)
41
42
  PATTERN
42
43
 
43
44
  def on_send(node)
@@ -49,6 +50,7 @@ module RuboCop
49
50
  end
50
51
  end
51
52
  end
53
+ alias on_csend on_send
52
54
  end
53
55
  end
54
56
  end
@@ -63,7 +63,7 @@ module RuboCop
63
63
  return unless TARGET_METHODS.include?(node.method_name)
64
64
  return unless one_block_argument?(node.arguments)
65
65
 
66
- block_argument = node.arguments.first
66
+ block_argument = node.first_argument
67
67
  block_body = node.body
68
68
  return unless use_equality_comparison_block?(block_body)
69
69
  return if same_block_argument_and_is_a_argument?(block_body, block_argument)