Skip to content

Commit

Permalink
[Fix #13061] Add new Style/RedundantInterpolationUnfreeze cop
Browse files Browse the repository at this point in the history
In Ruby >= 3.0, interpolated strings are always unfrozen: https://bugs.ruby-lang.org/issues/17104

On Ruby 2.7 they followed the magic comment literal which made it necessary to sometimes unfreeze them manually.

This could also apply on Ruby 2.7 by looking at the magic comment but it doesn't seem worth the effort.

RuboCop itself has 5 offenses:
```
Offenses:

lib/rubocop/cop/lint/unused_method_argument.rb:99:21: C: [Correctable] Style/RedundantInterpolationUnfreeze: Don't unfreeze interpolated strings as they are already unfrozen
          message = +"Unused method argument - `#{variable.name}`."
                    ^
lib/rubocop/cops_documentation_generator.rb:133:15: C: [Correctable] Style/RedundantInterpolationUnfreeze: Don't unfreeze interpolated strings as they are already unfrozen
    content = +"==== #{title}\n"
              ^
lib/rubocop/cops_documentation_generator.rb:259:15: C: [Correctable] Style/RedundantInterpolationUnfreeze: Don't unfreeze interpolated strings as they are already unfrozen
    content = +<<~HEADER
              ^
lib/rubocop/cops_documentation_generator.rb:307:15: C: [Correctable] Style/RedundantInterpolationUnfreeze: Don't unfreeze interpolated strings as they are already unfrozen
    content = +"=== Department xref:#{filename}[#{type_title}]\n\n"
              ^
spec/rubocop/cop/layout/end_of_line_spec.rb:21:16: C: [Correctable] Style/RedundantInterpolationUnfreeze: Don't unfreeze interpolated strings as they are already unfrozen
      input = (+<<~RUBY).force_encoding(encoding)
               ^

1546 files inspected, 5 offenses detected, 5 offenses autocorrectable
```
  • Loading branch information
Earlopain authored and bbatsov committed Aug 31, 2024
1 parent 0431023 commit 9e1e493
Show file tree
Hide file tree
Showing 6 changed files with 174 additions and 2 deletions.
1 change: 1 addition & 0 deletions changelog/new_style_redundant_interpolation_unfreeze.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* [#13061](https://github.com/rubocop/rubocop/issues/13061): Add new `Style/RedundantInterpolationUnfreeze` cop to check for `dup` and `@+` on interpolated strings in Ruby >= 3.0. ([@earlopain][])
5 changes: 5 additions & 0 deletions config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5057,6 +5057,11 @@ Style/RedundantInterpolation:
VersionAdded: '0.76'
VersionChanged: '1.30'

Style/RedundantInterpolationUnfreeze:
Description: 'Checks for redundant unfreezing of interpolated strings.'
Enabled: pending
VersionAdded: '<<next>>'

Style/RedundantLineContinuation:
Description: 'Check for redundant line continuation.'
Enabled: pending
Expand Down
1 change: 1 addition & 0 deletions lib/rubocop.rb
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,7 @@
require_relative 'rubocop/cop/style/redundant_filter_chain'
require_relative 'rubocop/cop/style/redundant_heredoc_delimiter_quotes'
require_relative 'rubocop/cop/style/redundant_initialize'
require_relative 'rubocop/cop/style/redundant_interpolation_unfreeze'
require_relative 'rubocop/cop/style/redundant_line_continuation'
require_relative 'rubocop/cop/style/redundant_regexp_argument'
require_relative 'rubocop/cop/style/redundant_regexp_constructor'
Expand Down
5 changes: 3 additions & 2 deletions lib/rubocop/cop/mixin/frozen_string_literal.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def frozen_string_literal_comment_exists?

def frozen_string_literal?(node)
frozen_string = if target_ruby_version >= 3.0
uninterpolated_string?(node) || frozen_heredoc?(node)
uninterpolated_string?(node) || uninterpolated_heredoc?(node)
else
FROZEN_STRING_LITERAL_TYPES_RUBY27.include?(node.type)
end
Expand All @@ -32,11 +32,12 @@ def uninterpolated_string?(node)
node.str_type? || (node.dstr_type? && node.each_descendant(:begin).none?)
end

def frozen_heredoc?(node)
def uninterpolated_heredoc?(node)
return false unless node.dstr_type? && node.heredoc?

node.children.all?(&:str_type?)
end
alias frozen_heredoc? uninterpolated_heredoc?

def frozen_string_literals_enabled?
ruby_version = processed_source.ruby_version
Expand Down
46 changes: 46 additions & 0 deletions lib/rubocop/cop/style/redundant_interpolation_unfreeze.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# frozen_string_literal: true

module RuboCop
module Cop
module Style
# Before Ruby 3.0, interpolated strings followed the frozen string literal
# magic comment which sometimes made it necessary to explicitly unfreeze them.
# Ruby 3.0 changed interpolated strings to always be unfrozen which makes
# unfreezing them redundant.
#
# @example
# # bad
# +"#{foo} bar"
#
# # bad
# "#{foo} bar".dup
#
# # good
# "#{foo} bar"
#
class RedundantInterpolationUnfreeze < Base
include FrozenStringLiteral
extend AutoCorrector
extend TargetRubyVersion

MSG = "Don't unfreeze interpolated strings as they are already unfrozen."

RESTRICT_ON_SEND = %i[+@ dup].freeze

minimum_target_ruby_version 3.0

def on_send(node)
return if node.arguments?
return unless (receiver = node.receiver)
return unless receiver.dstr_type?
return if uninterpolated_string?(receiver) || uninterpolated_heredoc?(receiver)

add_offense(node.loc.selector) do |corrector|
corrector.remove(node.loc.selector)
corrector.remove(node.loc.dot) unless node.unary_operation?
end
end
end
end
end
end
118 changes: 118 additions & 0 deletions spec/rubocop/cop/style/redundant_interpolation_unfreeze_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# frozen_string_literal: true

RSpec.describe RuboCop::Cop::Style::RedundantInterpolationUnfreeze, :config do
context 'target_ruby_version >= 3.0', :ruby30 do
it 'registers an offense for `@+`' do
expect_offense(<<~'RUBY')
+"#{foo} bar"
^ Don't unfreeze interpolated strings as they are already unfrozen.
RUBY

expect_correction(<<~'RUBY')
"#{foo} bar"
RUBY
end

it 'registers an offense for `@+` as a normal method call' do
expect_offense(<<~'RUBY')
"#{foo} bar".+@
^^ Don't unfreeze interpolated strings as they are already unfrozen.
RUBY

expect_correction(<<~'RUBY')
"#{foo} bar"
RUBY
end

it 'registers an offense for `dup`' do
expect_offense(<<~'RUBY')
"#{foo} bar".dup
^^^ Don't unfreeze interpolated strings as they are already unfrozen.
RUBY

expect_correction(<<~'RUBY')
"#{foo} bar"
RUBY
end

it 'registers an offense for interpolated heredoc with `@+`' do
expect_offense(<<~'RUBY')
foo(+<<~MSG)
^ Don't unfreeze interpolated strings as they are already unfrozen.
foo #{bar}
baz
MSG
RUBY

expect_correction(<<~'RUBY')
foo(<<~MSG)
foo #{bar}
baz
MSG
RUBY
end

it 'registers an offense for interpolated heredoc with `dup`' do
expect_offense(<<~'RUBY')
foo(<<~MSG.dup)
^^^ Don't unfreeze interpolated strings as they are already unfrozen.
foo #{bar}
baz
MSG
RUBY

expect_correction(<<~'RUBY')
foo(<<~MSG)
foo #{bar}
baz
MSG
RUBY
end

it 'registers no offense for uninterpolated heredoc' do
expect_no_offenses(<<~'RUBY')
foo(+<<~'MSG')
foo #{bar}
baz
MSG
RUBY
end

it 'registers no offense for plain string literals' do
expect_no_offenses(<<~RUBY)
"foo".dup
RUBY
end

it 'registers no offense for other types' do
expect_no_offenses(<<~RUBY)
local.dup
RUBY
end

it 'registers no offense when the method has arguments' do
expect_no_offenses(<<~'RUBY')
"#{foo} bar".dup(baz)
RUBY
end

it 'registers no offense for multiline string literals' do
expect_no_offenses(<<~RUBY)
+'foo' \
'bar'
RUBY
end

it 'registers no offense when there is no receiver' do
expect_no_offenses(<<~RUBY)
dup
RUBY
end
end

context 'target_ruby_version < 3.0', :ruby27, unsupported_on: :prism do
it 'accepts unfreezing an interpolated string' do
expect_no_offenses('+"#{foo} bar"')
end
end
end

0 comments on commit 9e1e493

Please sign in to comment.