Skip to content

Commit

Permalink
Add new Style/RedundantHeredocDelimiterQuotes cop
Browse files Browse the repository at this point in the history
Follow up #11522 (comment).

This cop checks for redundant heredoc delimiter quotes.

```ruby
# bad
do_something(<<~'EOS')
  no string interpolation style text
EOS

# good
do_something(<<~EOS)
  no string interpolation style text
EOS
```
  • Loading branch information
koic authored and bbatsov committed Feb 1, 2023
1 parent 1229ef2 commit 1b6aa94
Show file tree
Hide file tree
Showing 35 changed files with 392 additions and 173 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* [#11528](https://github.com/rubocop/rubocop/pull/11528): Add new `Style/RedundantHeredocDelimiterQuotes` cop. ([@koic][])
5 changes: 5 additions & 0 deletions config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4843,6 +4843,11 @@ Style/RedundantFreeze:
VersionAdded: '0.34'
VersionChanged: '0.66'

Style/RedundantHeredocDelimiterQuotes:
Description: 'Checks for redundant heredoc delimiter quotes.'
Enabled: pending
VersionAdded: '<<next>>'

Style/RedundantInitialize:
Description: 'Checks for redundant `initialize` methods.'
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 @@ -556,6 +556,7 @@
require_relative 'rubocop/cop/style/redundant_each'
require_relative 'rubocop/cop/style/redundant_fetch_block'
require_relative 'rubocop/cop/style/redundant_file_extension_in_require'
require_relative 'rubocop/cop/style/redundant_heredoc_delimiter_quotes'
require_relative 'rubocop/cop/style/redundant_initialize'
require_relative 'rubocop/cop/style/redundant_self_assignment'
require_relative 'rubocop/cop/style/redundant_self_assignment_branch'
Expand Down
57 changes: 57 additions & 0 deletions lib/rubocop/cop/style/redundant_heredoc_delimiter_quotes.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# frozen_string_literal: true

module RuboCop
module Cop
module Style
# Checks for redundant heredoc delimiter quotes.
#
# @example
#
# # bad
# do_something(<<~'EOS')
# no string interpolation style text
# EOS
#
# # good
# do_something(<<~EOS)
# no string interpolation style text
# EOS
#
# do_something(<<~'EOS')
# #{string_interpolation_style_text_not_evaluated}
# EOS
#
# do_something(<<~'EOS')
# escaped character\.
# EOS
#
class RedundantHeredocDelimiterQuotes < Base
include Heredoc
extend AutoCorrector

MSG = 'Remove the redundant heredoc delimiter quotes, use `%<replacement>s` instead.'
STRING_INTERPOLATION_OR_ESCAPED_CHARACTER_PATTERN = /#(\{|@|\$)|\\/.freeze

def on_heredoc(node)
return if need_heredoc_delimiter_quotes?(node)

replacement = "#{heredoc_type(node)}#{delimiter_string(node)}"

add_offense(node, message: format(MSG, replacement: replacement)) do |corrector|
corrector.replace(node, replacement)
end
end

private

def need_heredoc_delimiter_quotes?(node)
heredoc_delimiter = node.source.delete(heredoc_type(node))
return true unless heredoc_delimiter.start_with?("'", '"')

node.loc.heredoc_end.source.strip.match?(/\W/) ||
node.loc.heredoc_body.source.match?(STRING_INTERPOLATION_OR_ESCAPED_CHARACTER_PATTERN)
end
end
end
end
end
20 changes: 10 additions & 10 deletions spec/rubocop/cli/autocorrect_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -716,12 +716,12 @@ def method
end

it 'corrects Style/InverseMethods and Style/Not offenses' do
source = <<~'RUBY'
source = <<~RUBY
x.select {|y| not y.z }
RUBY
create_file('example.rb', source)
expect(cli.run(['--autocorrect-all', '--only', 'Style/InverseMethods,Style/Not'])).to eq(0)
corrected = <<~'RUBY'
corrected = <<~RUBY
x.reject {|y| y.z }
RUBY
expect(File.read('example.rb')).to eq(corrected)
Expand All @@ -732,7 +732,7 @@ def method
AllCops:
TargetRubyVersion: 2.6
YAML
source = <<~'RUBY'
source = <<~RUBY
until x
if foo
foo.some_method do
Expand All @@ -743,7 +743,7 @@ def method
RUBY
create_file('example.rb', source)
expect(cli.run(['--autocorrect-all', '--only', 'Style/Next,Style/SafeNavigation'])).to eq(0)
corrected = <<~'RUBY'
corrected = <<~RUBY
until x
next unless foo
foo.some_method do
Expand All @@ -755,7 +755,7 @@ def method
end

it 'corrects `Lint/Lambda` and `Lint/UnusedBlockArgument` offenses' do
source = <<~'RUBY'
source = <<~RUBY
c = -> event do
puts 'Hello world'
end
Expand All @@ -765,7 +765,7 @@ def method
'--autocorrect-all',
'--only', 'Lint/Lambda,Lint/UnusedBlockArgument'
])).to eq(0)
corrected = <<~'RUBY'
corrected = <<~RUBY
c = lambda do |_event|
puts 'Hello world'
end
Expand Down Expand Up @@ -1100,7 +1100,7 @@ def method
EnforcedStyle: indented_internal_methods
YAML

source = <<~'RUBY'
source = <<~RUBY
class Foo
private
Expand All @@ -1121,7 +1121,7 @@ def do_something
].join(',')
])).to eq(0)

corrected = <<~'RUBY'
corrected = <<~RUBY
class Foo
private
Expand All @@ -1135,7 +1135,7 @@ def do_something

it 'corrects IndentationWidth and IndentationConsistency offenses' \
'without correcting `Style/TrailingBodyOnClass`' do
source = <<~'RUBY'
source = <<~RUBY
class Test foo
def func1
end
Expand All @@ -1151,7 +1151,7 @@ def func2
['Layout/IndentationConsistency', 'Layout/IndentationWidth'].join(',')
])).to eq(0)

corrected = <<~'RUBY'
corrected = <<~RUBY
class Test foo
def func1
end
Expand Down
2 changes: 1 addition & 1 deletion spec/rubocop/config_loader_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -786,7 +786,7 @@ def enabled?(cop)
end

if custom_dept_to_disable == 'Foo'
message = <<~'OUTPUT'.chomp
message = <<~OUTPUT.chomp
unrecognized cop or department Foo found in parent_rubocop.yml
Foo is not a department. Use `Foo/Bar`.
OUTPUT
Expand Down
4 changes: 2 additions & 2 deletions spec/rubocop/cop/layout/class_structure_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ def do_something
end

it 'registers an offense and corrects when xstr heredoc constant is defined after public method' do
expect_offense(<<~'RUBY')
expect_offense(<<~RUBY)
class Foo
def do_something
end
Expand All @@ -375,7 +375,7 @@ def do_something
end
RUBY

expect_correction(<<~'RUBY')
expect_correction(<<~RUBY)
class Foo
CONSTANT = <<~`EOS`
str
Expand Down
8 changes: 4 additions & 4 deletions spec/rubocop/cop/layout/dot_position_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@

context 'with multiple heredocs' do
it 'registers an offense' do
expect_offense(<<~'RUBY')
expect_offense(<<~RUBY)
my_method.
^ Place the . on the next line, together with the method name.
something(<<~HERE, <<~THERE).
Expand All @@ -246,7 +246,7 @@
somethingelse
RUBY

expect_correction(<<~'RUBY')
expect_correction(<<~RUBY)
my_method
.something(<<~HERE, <<~THERE)
something
Expand Down Expand Up @@ -440,7 +440,7 @@

context 'with multiple heredocs' do
it 'registers an offense' do
expect_offense(<<~'RUBY')
expect_offense(<<~RUBY)
my_method
.something(<<~HERE, <<~THERE)
^ Place the . on the previous line, together with the method call receiver.
Expand All @@ -452,7 +452,7 @@
^ Place the . on the previous line, together with the method call receiver.
RUBY

expect_correction(<<~'RUBY')
expect_correction(<<~RUBY)
my_method.
something(<<~HERE, <<~THERE).
something
Expand Down
2 changes: 1 addition & 1 deletion spec/rubocop/cop/layout/else_alignment_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,7 @@
end

it 'accepts case match without else' do
expect_no_offenses(<<~'RUBY')
expect_no_offenses(<<~RUBY)
case 0
in a
p a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ def foo

it 'accepts a `raise` guard clause not followed by empty line when guard ' \
'clause is after condition without method invocation' do
expect_no_offenses(<<~'RUBY')
expect_no_offenses(<<~RUBY)
def foo
raise unless $1 == o
Expand Down
6 changes: 3 additions & 3 deletions spec/rubocop/cop/layout/indentation_consistency_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
end

it 'accepts when using access modifier at the top level' do
expect_no_offenses(<<~'RUBY')
expect_no_offenses(<<~RUBY)
public
def foo
Expand All @@ -21,15 +21,15 @@ def foo

it 'registers and corrects an offense when using access modifier and indented method definition ' \
'at the top level' do
expect_offense(<<~'RUBY')
expect_offense(<<~RUBY)
public
def foo
^^^^^^^ Inconsistent indentation detected.
end
RUBY

expect_correction(<<~'RUBY')
expect_correction(<<~RUBY)
public
def foo
Expand Down
4 changes: 2 additions & 2 deletions spec/rubocop/cop/layout/indentation_width_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -905,7 +905,7 @@ def x
end

it 'accepts aligned values in `in` clause' do
expect_no_offenses(<<~'RUBY')
expect_no_offenses(<<~RUBY)
case condition
in [42]
foo
Expand All @@ -916,7 +916,7 @@ def x
end

it 'accepts aligned value in `in` clause and `else` is empty' do
expect_no_offenses(<<~'RUBY')
expect_no_offenses(<<~RUBY)
case x
in 42
foo
Expand Down
6 changes: 3 additions & 3 deletions spec/rubocop/cop/layout/leading_comment_space_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@
let(:cop_config) { { 'AllowGemfileRubyComment' => false } }

it 'registers an offense when using ruby config as comment' do
expect_offense(<<~'RUBY')
expect_offense(<<~RUBY)
# Specific version (comment) will be used by RVM
#ruby=2.7.0
^^^^^^^^^^^ Missing space after `#`.
Expand All @@ -184,7 +184,7 @@

context 'file not named Gemfile' do
it 'registers an offense when using ruby config as comment' do
expect_offense(<<~'RUBY', 'test/test_case.rb')
expect_offense(<<~RUBY, 'test/test_case.rb')
# Specific version (comment) will be used by RVM
#ruby=2.7.0
^^^^^^^^^^^ Missing space after `#`.
Expand All @@ -197,7 +197,7 @@

context 'file named Gemfile' do
it 'does not register an offense when using ruby config as comment' do
expect_no_offenses(<<~'RUBY', 'Gemfile')
expect_no_offenses(<<~RUBY, 'Gemfile')
# Specific version (comment) will be used by RVM
#ruby=2.7.0
#ruby-gemset=myproject
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
end

it 'accepts a multiline string literal' do
expect_no_offenses(<<~'RUBY')
expect_no_offenses(<<~RUBY)
puts %(
foo
bar
Expand Down Expand Up @@ -128,7 +128,7 @@ def some_method
end

it 'accepts a heredoc string ...' do
expect_no_offenses(<<~'RUBY')
expect_no_offenses(<<~RUBY)
let(:source) do
<<~CODE
func({
Expand All @@ -152,7 +152,7 @@ def some_method
end

it 'accepts an empty heredoc string with interpolation' do
expect_no_offenses(<<~'RUBY')
expect_no_offenses(<<~RUBY)
puts(<<~TEXT)
TEXT
RUBY
Expand Down
2 changes: 1 addition & 1 deletion spec/rubocop/cop/layout/space_inside_parens_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
end

it 'registers an offense for space around heredoc start' do
expect_offense(<<~'RUBY')
expect_offense(<<~RUBY)
f( <<~HEREDOC )
^ Space inside parentheses detected.
^ Space inside parentheses detected.
Expand Down
4 changes: 2 additions & 2 deletions spec/rubocop/cop/lint/empty_interpolation_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
^^^ Empty interpolation detected.
RUBY

expect_correction(<<~'RUBY')
expect_correction(<<~RUBY)
"this is the "
RUBY
end
Expand All @@ -18,7 +18,7 @@
^^^^ Empty interpolation detected.
RUBY

expect_correction(<<~'RUBY')
expect_correction(<<~RUBY)
"this is the "
RUBY
end
Expand Down
2 changes: 1 addition & 1 deletion spec/rubocop/cop/lint/mixed_regexp_capture_types_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@

# See https://github.com/rubocop/rubocop/issues/8083
it 'does not register offense when using a Regexp cannot be processed by regexp_parser gem' do
expect_no_offenses(<<~'RUBY')
expect_no_offenses(<<~RUBY)
/data = ({"words":.+}}}[^}]*})/m
RUBY
end
Expand Down

0 comments on commit 1b6aa94

Please sign in to comment.