Skip to content

Commit

Permalink
Add new Lint/TripleQuotes cop.
Browse files Browse the repository at this point in the history
  • Loading branch information
dvandersluis authored and bbatsov committed Jan 26, 2021
1 parent 233e4e5 commit 1ac510e
Show file tree
Hide file tree
Showing 5 changed files with 191 additions and 0 deletions.
1 change: 1 addition & 0 deletions changelog/new_added_new_linttriplequotes_cop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* [#9402](https://github.com/rubocop-hq/rubocop/pull/9402): Add new `Lint/TripleQuotes` cop. ([@dvandersluis][])
5 changes: 5 additions & 0 deletions config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2073,6 +2073,11 @@ Lint/TrailingCommaInAttributeDeclaration:
Enabled: true
VersionAdded: '0.90'

Lint/TripleQuotes:
Description: 'Checks for useless triple quote constructs.'
Enabled: pending
VersionAdded: <<next>>

Lint/UnderscorePrefixedVariableName:
Description: 'Do not use prefix `_` for a variable that is used.'
Enabled: true
Expand Down
1 change: 1 addition & 0 deletions lib/rubocop.rb
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,7 @@
require_relative 'rubocop/cop/lint/to_json'
require_relative 'rubocop/cop/lint/top_level_return_with_argument'
require_relative 'rubocop/cop/lint/trailing_comma_in_attribute_declaration'
require_relative 'rubocop/cop/lint/triple_quotes'
require_relative 'rubocop/cop/lint/underscore_prefixed_variable_name'
require_relative 'rubocop/cop/lint/unexpected_block_arity'
require_relative 'rubocop/cop/lint/unified_integer'
Expand Down
71 changes: 71 additions & 0 deletions lib/rubocop/cop/lint/triple_quotes.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# frozen_string_literal: true

module RuboCop
module Cop
module Lint
# This cop checks for "triple quotes" (strings delimted by any odd number
# of quotes greater than 1).
#
# Ruby allows multiple strings to be implicitly concatenated by just
# being adjacent in a statement (ie. `"foo""bar" == "foobar"`). This sometimes
# gives the impression that there is something special about triple quotes, but
# in fact it is just extra unnecessary quotes and produces the same string. Each
# pair of quotes produces an additional concatenated empty string, so the result
# is still only the "actual" string within the delimiters.
#
# NOTE: Although this cop is called triple quotes, the same behavior is present
# for strings delimited by 5, 7, etc. quotation marks.
#
# @example
# # bad
# """
# A string
# """
#
# # bad
# '''
# A string
# '''
#
# # good
# "
# A string
# "
#
# # good
# <<STRING
# A string
# STRING
#
# # good (but not the same spacing as the bad case)
# 'A string'
class TripleQuotes < Base
extend AutoCorrector

MSG = 'Delimiting a string with multiple quotes has no effect, use a single quote instead.'

def on_dstr(node)
return if (empty_str_nodes = empty_str_nodes(node)).none?

opening_quotes = node.source.scan(/(?<=\A)['"]*/)[0]
return if opening_quotes.size < 3

# If the node is composed of only empty `str` nodes, keep one
empty_str_nodes.shift if empty_str_nodes.size == node.child_nodes.size

add_offense(node) do |corrector|
empty_str_nodes.each do |str|
corrector.remove(str)
end
end
end

private

def empty_str_nodes(node)
node.each_child_node(:str).select { |str| str.value == '' }
end
end
end
end
end
113 changes: 113 additions & 0 deletions spec/rubocop/cop/lint/triple_quotes_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# frozen_string_literal: true

RSpec.describe RuboCop::Cop::Lint::TripleQuotes, :config do
context 'triple quotes' do
context 'on one line' do
it 'registers an offense and corrects' do
expect_offense(<<~RUBY)
"""a string"""
^^^^^^^^^^^^^^ Delimiting a string with multiple quotes has no effect, use a single quote instead.
RUBY

expect_correction(<<~RUBY)
"a string"
RUBY
end
end

context 'on multiple lines' do
it 'registers an offense and corrects' do
expect_offense(<<~RUBY)
"""
^^^ Delimiting a string with multiple quotes has no effect, use a single quote instead.
a string
"""
RUBY

expect_correction(<<~RUBY)
"
a string
"
RUBY
end
end

context 'when only quotes' do
it 'registers an offense and corrects to a single empty quote' do
expect_offense(<<~RUBY)
""""""
^^^^^^ Delimiting a string with multiple quotes has no effect, use a single quote instead.
RUBY

expect_correction(<<~RUBY)
""
RUBY
end
end

context 'with only whitespace' do
it 'does not register' do
expect_no_offenses(<<~RUBY)
" " " " " "
RUBY
end
end
end

context 'quintuple quotes' do
it 'registers an offense and corrects' do
expect_offense(<<~RUBY)
"""""
^^^^^ Delimiting a string with multiple quotes has no effect, use a single quote instead.
a string
"""""
RUBY

expect_correction(<<~RUBY)
"
a string
"
RUBY
end
end

context 'string interpolation' do
it 'does not register an offense' do
expect_no_offenses(<<~'RUBY')
str = "#{abc}"
RUBY
end

context 'with nested extra quotes' do
it 'registers an offense and corrects' do
expect_offense(<<~'RUBY')
str = "#{'''abc'''}"
^^^^^^^^^ Delimiting a string with multiple quotes has no effect, use a single quote instead.
RUBY

expect_correction(<<~'RUBY')
str = "#{'abc'}"
RUBY
end
end
end

context 'heredocs' do
it 'does not register an offense' do
expect_no_offenses(<<~'RUBY')
str = <<~STRING
a string
#{interpolation}
STRING
RUBY
end
end

it 'does not register an offense for implicit concatenation' do
expect_no_offenses(<<~RUBY)
'' ''
'a''b''c'
'a''''b'
RUBY
end
end

0 comments on commit 1ac510e

Please sign in to comment.