Skip to content

Commit 3806fc4

Browse files
authored
Merge pull request #474 from viralpraxis/add-new-cop-performance-string-bytesize
Add new `Performance/StringBytesize` cop
2 parents 1ae01bc + d3637be commit 3806fc4

File tree

5 files changed

+142
-0
lines changed

5 files changed

+142
-0
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* [#474](https://github.com/rubocop/rubocop-performance/pull/474): Add new `Performance/StringBytesize` cop. ([@viralpraxis][])

config/default.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,12 @@ Performance/StartWith:
326326
VersionAdded: '0.36'
327327
VersionChanged: '1.10'
328328

329+
Performance/StringBytesize:
330+
Description: "Use `String#bytesize` instead of calculating the size of the bytes array."
331+
Safe: false
332+
Enabled: 'pending'
333+
VersionAdded: '<<next>>'
334+
329335
Performance/StringIdentifierArgument:
330336
Description: 'Use symbol identifier argument instead of string identifier argument.'
331337
Enabled: pending
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# frozen_string_literal: true
2+
3+
module RuboCop
4+
module Cop
5+
module Performance
6+
# Checks for calls to `#bytes` counting method and suggests using `bytesize` instead.
7+
# The `bytesize` method is more efficient and directly returns the size in bytes,
8+
# avoiding the intermediate array allocation that `bytes.size` incurs.
9+
#
10+
# @safety
11+
# This cop is unsafe because it assumes that the receiver
12+
# responds to `#bytesize` method.
13+
#
14+
# @example
15+
# # bad
16+
# string_var.bytes.count
17+
# "foobar".bytes.size
18+
#
19+
# # good
20+
# string_var.bytesize
21+
# "foobar".bytesize
22+
class StringBytesize < Base
23+
extend AutoCorrector
24+
25+
MSG = 'Use `String#bytesize` instead of calculating the size of the bytes array.'
26+
RESTRICT_ON_SEND = %i[size length count].freeze
27+
28+
def_node_matcher :string_bytes_method?, <<~MATCHER
29+
(call (call !{nil? int} :bytes) {:size :length :count})
30+
MATCHER
31+
32+
def on_send(node)
33+
string_bytes_method?(node) do
34+
range = node.receiver.loc.selector.begin.join(node.source_range.end)
35+
36+
add_offense(range) do |corrector|
37+
corrector.replace(range, 'bytesize')
38+
end
39+
end
40+
end
41+
alias on_csend on_send
42+
end
43+
end
44+
end
45+
end

lib/rubocop/cop/performance_cops.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
require_relative 'performance/size'
4545
require_relative 'performance/sort_reverse'
4646
require_relative 'performance/squeeze'
47+
require_relative 'performance/string_bytesize'
4748
require_relative 'performance/start_with'
4849
require_relative 'performance/string_identifier_argument'
4950
require_relative 'performance/string_include'
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# frozen_string_literal: true
2+
3+
RSpec.describe RuboCop::Cop::Performance::StringBytesize, :config do
4+
let(:msg) { 'Use `String#bytesize` instead of calculating the size of the bytes array.' }
5+
6+
it 'registers an offense with `size` method' do
7+
expect_offense(<<~RUBY)
8+
string.bytes.size
9+
^^^^^^^^^^ #{msg}
10+
RUBY
11+
12+
expect_correction(<<~RUBY)
13+
string.bytesize
14+
RUBY
15+
end
16+
17+
it 'registers an offense with `length` method' do
18+
expect_offense(<<~RUBY)
19+
string.bytes.length
20+
^^^^^^^^^^^^ #{msg}
21+
RUBY
22+
23+
expect_correction(<<~RUBY)
24+
string.bytesize
25+
RUBY
26+
end
27+
28+
it 'registers an offense with `count` method' do
29+
expect_offense(<<~RUBY)
30+
string.bytes.count
31+
^^^^^^^^^^^ #{msg}
32+
RUBY
33+
34+
expect_correction(<<~RUBY)
35+
string.bytesize
36+
RUBY
37+
end
38+
39+
it 'registers an offense with string literal' do
40+
expect_offense(<<~RUBY)
41+
"foobar".bytes.count
42+
^^^^^^^^^^^ #{msg}
43+
RUBY
44+
45+
expect_correction(<<~RUBY)
46+
"foobar".bytesize
47+
RUBY
48+
end
49+
50+
it 'registers an offense and autocorrects with safe navigation' do
51+
expect_offense(<<~RUBY)
52+
string&.bytes&.count
53+
^^^^^^^^^^^^ #{msg}
54+
RUBY
55+
56+
expect_correction(<<~RUBY)
57+
string&.bytesize
58+
RUBY
59+
end
60+
61+
it 'registers an offense and autocorrects with partial safe navigation' do
62+
expect_offense(<<~RUBY)
63+
string&.bytes.count
64+
^^^^^^^^^^^ #{msg}
65+
RUBY
66+
67+
expect_correction(<<~RUBY)
68+
string&.bytesize
69+
RUBY
70+
end
71+
72+
it 'does not register an offense without array size method' do
73+
expect_no_offenses(<<~RUBY)
74+
string.bytes
75+
RUBY
76+
end
77+
78+
it 'does not register an offense with `bytes` without explicit receiver' do
79+
expect_no_offenses(<<~RUBY)
80+
bytes.size
81+
RUBY
82+
end
83+
84+
it 'does not register an offense when the receiver is of type `int`' do
85+
expect_no_offenses(<<~RUBY)
86+
3.bytes.size
87+
RUBY
88+
end
89+
end

0 commit comments

Comments
 (0)