Skip to content

Commit 9e1e493

Browse files
Earlopainbbatsov
authored andcommitted
[Fix #13061] Add new Style/RedundantInterpolationUnfreeze cop
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 ```
1 parent 0431023 commit 9e1e493

6 files changed

+174
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
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][])

config/default.yml

+5
Original file line numberDiff line numberDiff line change
@@ -5057,6 +5057,11 @@ Style/RedundantInterpolation:
50575057
VersionAdded: '0.76'
50585058
VersionChanged: '1.30'
50595059

5060+
Style/RedundantInterpolationUnfreeze:
5061+
Description: 'Checks for redundant unfreezing of interpolated strings.'
5062+
Enabled: pending
5063+
VersionAdded: '<<next>>'
5064+
50605065
Style/RedundantLineContinuation:
50615066
Description: 'Check for redundant line continuation.'
50625067
Enabled: pending

lib/rubocop.rb

+1
Original file line numberDiff line numberDiff line change
@@ -583,6 +583,7 @@
583583
require_relative 'rubocop/cop/style/redundant_filter_chain'
584584
require_relative 'rubocop/cop/style/redundant_heredoc_delimiter_quotes'
585585
require_relative 'rubocop/cop/style/redundant_initialize'
586+
require_relative 'rubocop/cop/style/redundant_interpolation_unfreeze'
586587
require_relative 'rubocop/cop/style/redundant_line_continuation'
587588
require_relative 'rubocop/cop/style/redundant_regexp_argument'
588589
require_relative 'rubocop/cop/style/redundant_regexp_constructor'

lib/rubocop/cop/mixin/frozen_string_literal.rb

+3-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ def frozen_string_literal_comment_exists?
2020

2121
def frozen_string_literal?(node)
2222
frozen_string = if target_ruby_version >= 3.0
23-
uninterpolated_string?(node) || frozen_heredoc?(node)
23+
uninterpolated_string?(node) || uninterpolated_heredoc?(node)
2424
else
2525
FROZEN_STRING_LITERAL_TYPES_RUBY27.include?(node.type)
2626
end
@@ -32,11 +32,12 @@ def uninterpolated_string?(node)
3232
node.str_type? || (node.dstr_type? && node.each_descendant(:begin).none?)
3333
end
3434

35-
def frozen_heredoc?(node)
35+
def uninterpolated_heredoc?(node)
3636
return false unless node.dstr_type? && node.heredoc?
3737

3838
node.children.all?(&:str_type?)
3939
end
40+
alias frozen_heredoc? uninterpolated_heredoc?
4041

4142
def frozen_string_literals_enabled?
4243
ruby_version = processed_source.ruby_version
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# frozen_string_literal: true
2+
3+
module RuboCop
4+
module Cop
5+
module Style
6+
# Before Ruby 3.0, interpolated strings followed the frozen string literal
7+
# magic comment which sometimes made it necessary to explicitly unfreeze them.
8+
# Ruby 3.0 changed interpolated strings to always be unfrozen which makes
9+
# unfreezing them redundant.
10+
#
11+
# @example
12+
# # bad
13+
# +"#{foo} bar"
14+
#
15+
# # bad
16+
# "#{foo} bar".dup
17+
#
18+
# # good
19+
# "#{foo} bar"
20+
#
21+
class RedundantInterpolationUnfreeze < Base
22+
include FrozenStringLiteral
23+
extend AutoCorrector
24+
extend TargetRubyVersion
25+
26+
MSG = "Don't unfreeze interpolated strings as they are already unfrozen."
27+
28+
RESTRICT_ON_SEND = %i[+@ dup].freeze
29+
30+
minimum_target_ruby_version 3.0
31+
32+
def on_send(node)
33+
return if node.arguments?
34+
return unless (receiver = node.receiver)
35+
return unless receiver.dstr_type?
36+
return if uninterpolated_string?(receiver) || uninterpolated_heredoc?(receiver)
37+
38+
add_offense(node.loc.selector) do |corrector|
39+
corrector.remove(node.loc.selector)
40+
corrector.remove(node.loc.dot) unless node.unary_operation?
41+
end
42+
end
43+
end
44+
end
45+
end
46+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
# frozen_string_literal: true
2+
3+
RSpec.describe RuboCop::Cop::Style::RedundantInterpolationUnfreeze, :config do
4+
context 'target_ruby_version >= 3.0', :ruby30 do
5+
it 'registers an offense for `@+`' do
6+
expect_offense(<<~'RUBY')
7+
+"#{foo} bar"
8+
^ Don't unfreeze interpolated strings as they are already unfrozen.
9+
RUBY
10+
11+
expect_correction(<<~'RUBY')
12+
"#{foo} bar"
13+
RUBY
14+
end
15+
16+
it 'registers an offense for `@+` as a normal method call' do
17+
expect_offense(<<~'RUBY')
18+
"#{foo} bar".+@
19+
^^ Don't unfreeze interpolated strings as they are already unfrozen.
20+
RUBY
21+
22+
expect_correction(<<~'RUBY')
23+
"#{foo} bar"
24+
RUBY
25+
end
26+
27+
it 'registers an offense for `dup`' do
28+
expect_offense(<<~'RUBY')
29+
"#{foo} bar".dup
30+
^^^ Don't unfreeze interpolated strings as they are already unfrozen.
31+
RUBY
32+
33+
expect_correction(<<~'RUBY')
34+
"#{foo} bar"
35+
RUBY
36+
end
37+
38+
it 'registers an offense for interpolated heredoc with `@+`' do
39+
expect_offense(<<~'RUBY')
40+
foo(+<<~MSG)
41+
^ Don't unfreeze interpolated strings as they are already unfrozen.
42+
foo #{bar}
43+
baz
44+
MSG
45+
RUBY
46+
47+
expect_correction(<<~'RUBY')
48+
foo(<<~MSG)
49+
foo #{bar}
50+
baz
51+
MSG
52+
RUBY
53+
end
54+
55+
it 'registers an offense for interpolated heredoc with `dup`' do
56+
expect_offense(<<~'RUBY')
57+
foo(<<~MSG.dup)
58+
^^^ Don't unfreeze interpolated strings as they are already unfrozen.
59+
foo #{bar}
60+
baz
61+
MSG
62+
RUBY
63+
64+
expect_correction(<<~'RUBY')
65+
foo(<<~MSG)
66+
foo #{bar}
67+
baz
68+
MSG
69+
RUBY
70+
end
71+
72+
it 'registers no offense for uninterpolated heredoc' do
73+
expect_no_offenses(<<~'RUBY')
74+
foo(+<<~'MSG')
75+
foo #{bar}
76+
baz
77+
MSG
78+
RUBY
79+
end
80+
81+
it 'registers no offense for plain string literals' do
82+
expect_no_offenses(<<~RUBY)
83+
"foo".dup
84+
RUBY
85+
end
86+
87+
it 'registers no offense for other types' do
88+
expect_no_offenses(<<~RUBY)
89+
local.dup
90+
RUBY
91+
end
92+
93+
it 'registers no offense when the method has arguments' do
94+
expect_no_offenses(<<~'RUBY')
95+
"#{foo} bar".dup(baz)
96+
RUBY
97+
end
98+
99+
it 'registers no offense for multiline string literals' do
100+
expect_no_offenses(<<~RUBY)
101+
+'foo' \
102+
'bar'
103+
RUBY
104+
end
105+
106+
it 'registers no offense when there is no receiver' do
107+
expect_no_offenses(<<~RUBY)
108+
dup
109+
RUBY
110+
end
111+
end
112+
113+
context 'target_ruby_version < 3.0', :ruby27, unsupported_on: :prism do
114+
it 'accepts unfreezing an interpolated string' do
115+
expect_no_offenses('+"#{foo} bar"')
116+
end
117+
end
118+
end

0 commit comments

Comments
 (0)