Skip to content

Commit

Permalink
Merge pull request #474 from viralpraxis/add-new-cop-performance-stri…
Browse files Browse the repository at this point in the history
…ng-bytesize

Add new `Performance/StringBytesize` cop
  • Loading branch information
koic authored Nov 2, 2024
2 parents 1ae01bc + d3637be commit 3806fc4
Show file tree
Hide file tree
Showing 5 changed files with 142 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."
Safe: false
Enabled: 'pending'
VersionAdded: '<<next>>'

Performance/StringIdentifierArgument:
Description: 'Use symbol identifier argument instead of string identifier argument.'
Enabled: pending
Expand Down
45 changes: 45 additions & 0 deletions lib/rubocop/cop/performance/string_bytesize.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# 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? int} :bytes) {:size :length :count})
MATCHER

def on_send(node)
string_bytes_method?(node) do
range = node.receiver.loc.selector.begin.join(node.source_range.end)

add_offense(range) do |corrector|
corrector.replace(range, 'bytesize')
end
end
end
alias on_csend on_send
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
89 changes: 89 additions & 0 deletions spec/rubocop/cop/performance/string_bytesize_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# 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 offense without array size method' do
expect_no_offenses(<<~RUBY)
string.bytes
RUBY
end

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

it 'does not register an offense when the receiver is of type `int`' do
expect_no_offenses(<<~RUBY)
3.bytes.size
RUBY
end
end

0 comments on commit 3806fc4

Please sign in to comment.