Skip to content

Commit

Permalink
[Fix #475] Add new Performance/StringBytesize cop.
Browse files Browse the repository at this point in the history
  • Loading branch information
viralpraxis committed Oct 22, 2024
1 parent 1ae01bc commit 896ba88
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 0 deletions.
1 change: 1 addition & 0 deletions changelog/new_string_bytesize_cop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* [#474](https://github.com/rubocop/rubocop-performance/pull/474): Add new `Performance/StringBytesize` cop. ([@viralpraxis][])
6 changes: 6 additions & 0 deletions config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,12 @@ Performance/StartWith:
VersionAdded: '0.36'
VersionChanged: '1.10'

Performance/StringBytesize:
Description: "Use `String#bytesize` instead of calculating the size of the bytes array."
SafeAutoCorrect: false
Enabled: 'pending'
VersionAdded: '<<next>>'

Performance/StringIdentifierArgument:
Description: 'Use symbol identifier argument instead of string identifier argument.'
Enabled: pending
Expand Down
49 changes: 49 additions & 0 deletions lib/rubocop/cop/performance/string_bytesize.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# frozen_string_literal: true

module RuboCop
module Cop
module Performance
# Checks for calls to `#bytes` counting method and suggests using `bytesize` instead.
# The `bytesize` method is more efficient and directly returns the size in bytes,
# avoiding the intermediate array allocation that `bytes.size` incurs.
#
# @safety
# This cop is unsafe because it assumes that the receiver
# responds to `#bytesize` method.
#
# @example
# # bad
# string_var.bytes.count
# "foobar".bytes.size
#
# # good
# string_var.bytesize
# "foobar".bytesize
class StringBytesize < Base
extend AutoCorrector

MSG = 'Use `String#bytesize` instead of calculating the size of the bytes array.'
RESTRICT_ON_SEND = %i[size length count].freeze

def_node_matcher :string_bytes_method?, <<~MATCHER
(call (call !nil? :bytes) {:size :length :count})
MATCHER

def on_send(node)
string_bytes_method?(node) do
add_offense(node) do |corrector|
corrector.replace(node, replacement(node))
end
end
end
alias on_csend on_send

def replacement(node)
receiver = node.receiver

"#{receiver.receiver.source}#{receiver.loc.dot.source}bytesize"
end
end
end
end
end
1 change: 1 addition & 0 deletions lib/rubocop/cop/performance_cops.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
require_relative 'performance/size'
require_relative 'performance/sort_reverse'
require_relative 'performance/squeeze'
require_relative 'performance/string_bytesize'
require_relative 'performance/start_with'
require_relative 'performance/string_identifier_argument'
require_relative 'performance/string_include'
Expand Down
83 changes: 83 additions & 0 deletions spec/rubocop/cop/performance/string_bytesize_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# frozen_string_literal: true

RSpec.describe RuboCop::Cop::Performance::StringBytesize, :config do
let(:msg) { 'Use `String#bytesize` instead of calculating the size of the bytes array.' }

it 'registers an offense with `size` method' do
expect_offense(<<~RUBY)
string.bytes.size
^^^^^^^^^^^^^^^^^ #{msg}
RUBY

expect_correction(<<~RUBY)
string.bytesize
RUBY
end

it 'registers an offense with `length` method' do
expect_offense(<<~RUBY)
string.bytes.length
^^^^^^^^^^^^^^^^^^^ #{msg}
RUBY

expect_correction(<<~RUBY)
string.bytesize
RUBY
end

it 'registers an offense with `count` method' do
expect_offense(<<~RUBY)
string.bytes.count
^^^^^^^^^^^^^^^^^^ #{msg}
RUBY

expect_correction(<<~RUBY)
string.bytesize
RUBY
end

it 'registers an offense with string literal' do
expect_offense(<<~RUBY)
"foobar".bytes.count
^^^^^^^^^^^^^^^^^^^^ #{msg}
RUBY

expect_correction(<<~RUBY)
"foobar".bytesize
RUBY
end

it 'registers an offense and autocorrects with safe navigation' do
expect_offense(<<~RUBY)
string&.bytes&.count
^^^^^^^^^^^^^^^^^^^^ #{msg}
RUBY

expect_correction(<<~RUBY)
string&.bytesize
RUBY
end

it 'registers an offense and autocorrects with partial safe navigation' do
expect_offense(<<~RUBY)
string&.bytes.count
^^^^^^^^^^^^^^^^^^^ #{msg}
RUBY

expect_correction(<<~RUBY)
string&.bytesize
RUBY
end

it 'does not register an offenses without array size method' do
expect_no_offenses(<<~RUBY)
string.bytes
RUBY
end

it 'does not register an offenses with `bytes` without explicit receiver' do
expect_no_offenses(<<~RUBY)
bytes.size
RUBY
end
end

0 comments on commit 896ba88

Please sign in to comment.