Skip to content

Commit fca972f

Browse files
committed
[Fix #431] prefer << over push
1 parent 3ba5d91 commit fca972f

File tree

8 files changed

+127
-4
lines changed

8 files changed

+127
-4
lines changed

changelog/new_ArrayPushSingle.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* [#431](https://github.com/rubocop/rubocop-performance/issues/431): Add `ArrayPushSingle` cop, which replaces `array.push(x)` with `array << x`. ([@amomchilov][])

config/default.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,13 @@ Performance/AncestorsInclude:
1111
Safe: false
1212
VersionAdded: '1.7'
1313

14+
Performance/ArrayPushSingle:
15+
Description: 'Use `array << x` instead of `array.push(x)`.'
16+
Reference: 'https://github.com/rubocop/rubocop-performance/issues/431'
17+
Enabled: true
18+
Safe: false
19+
VersionAdded: '<<next>>'
20+
1421
Performance/ArraySemiInfiniteRangeSlice:
1522
Description: 'Identifies places where slicing arrays with semi-infinite ranges can be replaced by `Array#take` and `Array#drop`.'
1623
# This cop was created due to a mistake in microbenchmark.

lib/rubocop-performance.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
RuboCop::Cop::Lint::UnusedMethodArgument.singleton_class.prepend(
1414
Module.new do
1515
def autocorrect_incompatible_with
16-
super.push(RuboCop::Cop::Performance::BlockGivenWithExplicitBlock)
16+
super << RuboCop::Cop::Performance::BlockGivenWithExplicitBlock
1717
end
1818
end
1919
)
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# frozen_string_literal: true
2+
3+
module RuboCop
4+
module Cop
5+
module Performance
6+
# Identifies places where pushing a single element to an array
7+
# can be replaced by `Array#<<`.
8+
#
9+
# @safety
10+
# This cop is unsafe because not all objects that respond to `#push` also respond to `#<<`
11+
#
12+
# @example
13+
# # bad
14+
# array.push(1)
15+
#
16+
# # good
17+
# array << 1
18+
#
19+
# # good
20+
# array.push(1, 2, 3) # `<<` only works for one element
21+
#
22+
class ArrayPushSingle < Base
23+
extend AutoCorrector
24+
25+
MSG = 'Use `<<` instead of `%<current>s`.'
26+
27+
PUSH_METHODS = Set[:push, :append].freeze
28+
RESTRICT_ON_SEND = PUSH_METHODS
29+
30+
def_node_matcher :push_call?, <<~PATTERN
31+
(call $_receiver $%PUSH_METHODS $!(splat _))
32+
PATTERN
33+
34+
def on_send(node)
35+
push_call?(node) do |receiver, method_name, element|
36+
message = format(MSG, current: method_name)
37+
38+
add_offense(node, message: message) do |corrector|
39+
corrector.replace(node, "#{receiver.source} << #{element.source}")
40+
end
41+
end
42+
end
43+
44+
def on_csend(node)
45+
push_call?(node) do |receiver, method_name, element|
46+
message = format(MSG, current: method_name)
47+
48+
add_offense(node, message: message) do |corrector|
49+
corrector.replace(node, "#{receiver.source}&.<< #{element.source}")
50+
end
51+
end
52+
end
53+
end
54+
end
55+
end
56+
end

lib/rubocop/cop/performance/redundant_split_regexp_argument.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ def replacement(regexp_node)
4848
stack = []
4949
chars = regexp_content.chars.each_with_object([]) do |char, strings|
5050
if stack.empty? && char == '\\'
51-
stack.push(char)
51+
stack.push(char) # rubocop:disable Performance/ArrayPushSingle
5252
else
5353
strings << "#{stack.pop}#{char}"
5454
end

lib/rubocop/cop/performance_cops.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
require_relative 'mixin/sort_block'
55

66
require_relative 'performance/ancestors_include'
7+
require_relative 'performance/array_push_single'
78
require_relative 'performance/array_semi_infinite_range_slice'
89
require_relative 'performance/big_decimal_with_numeric_argument'
910
require_relative 'performance/bind_call'

spec/project_spec.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,12 +71,12 @@
7171
supported_key = RuboCop::Cop::Util.to_supported_styles(style_name)
7272
valid = config[name][supported_key]
7373
unless valid
74-
errors.push("#{supported_key} is missing for #{name}")
74+
errors << "#{supported_key} is missing for #{name}"
7575
next
7676
end
7777
next if valid.include?(style)
7878

79-
errors.push("invalid #{style_name} '#{style}' for #{name} found")
79+
errors << "invalid #{style_name} '#{style}' for #{name} found"
8080
end
8181
end
8282

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# frozen_string_literal: true
2+
3+
RSpec.describe RuboCop::Cop::Performance::ArrayPushSingle, :config do
4+
it 'registers an offense and corrects when using `push` with a single element' do
5+
expect_offense(<<~RUBY)
6+
array.push(element)
7+
^^^^^^^^^^^^^^^^^^^ Use `<<` instead of `push`.
8+
RUBY
9+
10+
expect_correction(<<~RUBY)
11+
array << element
12+
RUBY
13+
end
14+
15+
it 'registers an offense and corrects when using `push` with a single element and safe navigation operator' do
16+
expect_offense(<<~RUBY)
17+
array&.push(element)
18+
^^^^^^^^^^^^^^^^^^^^ Use `<<` instead of `push`.
19+
RUBY
20+
21+
# gross. TODO: make a configuration option?
22+
expect_correction(<<~RUBY)
23+
array&.<< element
24+
RUBY
25+
end
26+
27+
it 'does not register an offense when using `push` with multiple elements' do
28+
expect_no_offenses(<<~RUBY)
29+
array.push(1, 2, 3)
30+
RUBY
31+
end
32+
33+
it 'does not register an offense when using `push` with splatted elements' do
34+
expect_no_offenses(<<~RUBY)
35+
array.push(*elements)
36+
RUBY
37+
end
38+
39+
# rubocop:disable Performance/ArrayPushSingle
40+
describe 'replacing `push` with `<<`' do
41+
subject(:array) { [1, 2, 3] }
42+
43+
it 'returns the same result' do
44+
expect([1, 2, 3].push(4)).to eq([1, 2, 3] << 4)
45+
end
46+
47+
it 'has the same side-effect' do
48+
a = [1, 2, 3]
49+
a << 4
50+
51+
b = [1, 2, 3]
52+
b << 4
53+
54+
expect(a).to eq(b)
55+
end
56+
end
57+
# rubocop:enable Performance/ArrayPushSingle
58+
end

0 commit comments

Comments
 (0)