Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fix #9793] Add Style/QuotedSymbols to enforce consistency in quoted symbols #9809

Merged
merged 2 commits into from May 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/change_change_lintsymbolconversion_to_only.md
@@ -0,0 +1 @@
* [#9809](https://github.com/rubocop/rubocop/pull/9809): Change `Lint/SymbolConversion` to only quote with double quotes, since `Style/QuotedSymbols` can now correct those to the correct quotes as per configuration. ([@dvandersluis][])
1 change: 1 addition & 0 deletions changelog/new_add_stylequotedsymbols_to_enforce.md
@@ -0,0 +1 @@
* [#9793](https://github.com/rubocop/rubocop/issues/9793): Add `Style/QuotedSymbols` to enforce consistency in quoted symbols. ([@dvandersluis][])
11 changes: 11 additions & 0 deletions config/default.yml
Expand Up @@ -2092,6 +2092,7 @@ Lint/SymbolConversion:
Description: 'Checks for unnecessary symbol conversions.'
Enabled: pending
VersionAdded: '1.9'
VersionChanged: '<<next>>'
EnforcedStyle: strict
SupportedStyles:
- strict
Expand Down Expand Up @@ -4186,6 +4187,16 @@ Style/Proc:
VersionAdded: '0.9'
VersionChanged: '0.18'

Style/QuotedSymbols:
Description: 'Use a consistent style for quoted symbols.'
Enabled: pending
VersionAdded: '<<next>>'
EnforcedStyle: same_as_string_literals
SupportedStyles:
- same_as_string_literals
- single_quotes
- double_quotes

Style/RaiseArgs:
Description: 'Checks the arguments passed to raise/fail.'
StyleGuide: '#exception-class-messages'
Expand Down
2 changes: 2 additions & 0 deletions lib/rubocop.rb
Expand Up @@ -119,6 +119,7 @@
require_relative 'rubocop/cop/mixin/statement_modifier'
require_relative 'rubocop/cop/mixin/string_help'
require_relative 'rubocop/cop/mixin/string_literals_help'
require_relative 'rubocop/cop/mixin/symbol_help'
require_relative 'rubocop/cop/mixin/target_ruby_version'
require_relative 'rubocop/cop/mixin/trailing_body'
require_relative 'rubocop/cop/mixin/trailing_comma'
Expand Down Expand Up @@ -550,6 +551,7 @@
require_relative 'rubocop/cop/style/perl_backrefs'
require_relative 'rubocop/cop/style/preferred_hash_methods'
require_relative 'rubocop/cop/style/proc'
require_relative 'rubocop/cop/style/quoted_symbols'
require_relative 'rubocop/cop/style/raise_args'
require_relative 'rubocop/cop/style/random_with_offset'
require_relative 'rubocop/cop/style/redundant_argument'
Expand Down
14 changes: 2 additions & 12 deletions lib/rubocop/cop/lint/symbol_conversion.rb
Expand Up @@ -66,6 +66,7 @@ module Lint
class SymbolConversion < Base
extend AutoCorrector
include ConfigurableEnforcedStyle
include SymbolHelp

MSG = 'Unnecessary symbol conversion; use `%<correction>s` instead.'
MSG_CONSISTENCY = 'Symbol hash key should be quoted for consistency; ' \
Expand Down Expand Up @@ -138,10 +139,6 @@ def in_percent_literal_array?(node)
node.parent&.array_type? && node.parent&.percent_literal?
end

def hash_key?(node)
node.parent&.pair_type? && node == node.parent.child_nodes.first
end

def correct_hash_key(node)
# Although some operators can be converted to symbols normally
# (ie. `:==`), these are not accepted as hash keys and will
Expand All @@ -167,21 +164,14 @@ def correct_inconsistent_hash_keys(keys)
next if requires_quotes?(key)
next if properly_quoted?(key.source, %("#{key.value}"))

correction = "#{quote_type}#{key.value}#{quote_type}"
correction = %("#{key.value}")
register_offense(
key,
correction: correction,
message: format(MSG_CONSISTENCY, correction: "#{correction}:")
)
end
end

def quote_type
# Use the `Style/StringLiterals` configuration for quoting symbols
return '"' unless config.for_cop('Style/StringLiterals')['Enabled']

config.for_cop('Style/StringLiterals')['EnforcedStyle'] == 'single_quotes' ? "'" : '"'
end
end
end
end
Expand Down
6 changes: 2 additions & 4 deletions lib/rubocop/cop/mixin/string_literals_help.rb
Expand Up @@ -4,12 +4,10 @@ module RuboCop
module Cop
# Common functionality for cops checking single/double quotes.
module StringLiteralsHelp
include StringHelp

private

def wrong_quotes?(node)
src = node.source
def wrong_quotes?(src_or_node)
src = src_or_node.is_a?(RuboCop::AST::Node) ? src_or_node.source : src_or_node
return false if src.start_with?('%', '?')

if style == :single_quotes
Expand Down
13 changes: 13 additions & 0 deletions lib/rubocop/cop/mixin/symbol_help.rb
@@ -0,0 +1,13 @@
# frozen_string_literal: true

module RuboCop
module Cop
# Classes that include this module just implement functions for working
# with symbol nodes.
module SymbolHelp
def hash_key?(node)
node.parent&.pair_type? && node == node.parent.child_nodes.first
end
end
end
end
105 changes: 105 additions & 0 deletions lib/rubocop/cop/style/quoted_symbols.rb
@@ -0,0 +1,105 @@
# frozen_string_literal: true

module RuboCop
module Cop
module Style
# Checks if the quotes used for quoted symbols match the configured defaults.
# By default uses the same configuration as `Style/StringLiterals`.
#
# String interpolation is always kept in double quotes.
#
# Note: `Lint/SymbolConversion` can be used in parallel to ensure that symbols
# are not quoted that don't need to be. This cop is for configuring the quoting
# style to use for symbols that require quotes.
#
# @example EnforcedStyle: same_as_string_literals (default) / single_quotes
# # bad
# :"abc-def"
#
# # good
# :'abc-def'
# :"#{str}"
# :"a\'b"
#
# @example EnforcedStyle: double_quotes
# # bad
# :'abc-def'
#
# # good
# :"abc-def"
# :"#{str}"
# :"a\'b"
class QuotedSymbols < Base
include ConfigurableEnforcedStyle
include SymbolHelp
include StringLiteralsHelp
extend AutoCorrector

MSG_SINGLE = "Prefer single-quoted symbols when you don't need string interpolation " \
'or special symbols.'
MSG_DOUBLE = 'Prefer double-quoted symbols unless you need single quotes to ' \
'avoid extra backslashes for escaping.'

def on_sym(node)
return unless quoted?(node)

message = style == :single_quotes ? MSG_SINGLE : MSG_DOUBLE

if wrong_quotes?(node)
add_offense(node, message: message) do |corrector|
opposite_style_detected
autocorrect(corrector, node)
end
else
correct_style_detected
end
end

private

def autocorrect(corrector, node)
str = if hash_key?(node)
# strip quotes
correct_quotes(node.source[1..-2])
else
# strip leading `:` and quotes
":#{correct_quotes(node.source[2..-2])}"
end

corrector.replace(node, str)
end

def correct_quotes(str)
if style == :single_quotes
to_string_literal(str)
else
str.inspect
end
end

def style
return super unless super == :same_as_string_literals

string_literals_config = config.for_cop('Style/StringLiterals')
return :single_quotes unless string_literals_config['Enabled']

string_literals_config['EnforcedStyle'].to_sym
end

def alternative_style
(supported_styles - [style, :same_as_string_literals]).first
end

def quoted?(sym_node)
sym_node.source.match?(/\A:?(['"]).*?\1\z/m)
end

def wrong_quotes?(node)
return super if hash_key?(node)

super(node.source[1..-1])
end
end
end
end
end
1 change: 1 addition & 0 deletions lib/rubocop/cop/style/string_literals.rb
Expand Up @@ -29,6 +29,7 @@ module Style
class StringLiterals < Base
include ConfigurableEnforcedStyle
include StringLiteralsHelp
include StringHelp
extend AutoCorrector

MSG_INCONSISTENT = 'Inconsistent quote style.'
Expand Down
1 change: 1 addition & 0 deletions lib/rubocop/cop/style/string_literals_in_interpolation.rb
Expand Up @@ -22,6 +22,7 @@ module Style
class StringLiteralsInInterpolation < Base
include ConfigurableEnforcedStyle
include StringLiteralsHelp
include StringHelp
extend AutoCorrector

def autocorrect(corrector, node)
Expand Down
29 changes: 29 additions & 0 deletions spec/rubocop/cli/autocorrect_spec.rb
Expand Up @@ -2019,4 +2019,33 @@ def my_func
end
RUBY
end

it 'consistently quotes symbol keys in a hash using `Lint/SymbolConversion` ' \
'with `EnforcedStyle: consistent` and `Style/QuotedSymbols`' do
source_file = Pathname('example.rb')
create_file(source_file, <<~RUBY)
{
a: 1,
b: 2,
'c-d': 3
}
RUBY

create_file('.rubocop.yml', <<~YAML)
Lint/SymbolConversion:
EnforcedStyle: consistent
Style/QuotedSymbols:
EnforcedStyle: double_quotes
YAML

status = cli.run(['--auto-correct', '--only', 'Lint/SymbolConversion,Style/QuotedSymbols'])
expect(status).to eq(0)
expect(source_file.read).to eq(<<~RUBY)
{
"a": 1,
"b": 2,
"c-d": 3
}
RUBY
end
end
61 changes: 6 additions & 55 deletions spec/rubocop/cop/lint/symbol_conversion_spec.rb
Expand Up @@ -170,18 +170,7 @@
end

context 'EnforcedStyle: consistent' do
let(:string_literals_enabled) { true }
let(:string_literals_style) { 'single_quotes' }

let(:cop_config) { { 'EnforcedStyle' => 'consistent' } }
let(:other_cops) do
{
'Style/StringLiterals' => {
'Enabled' => string_literals_enabled,
'EnforcedStyle' => string_literals_style
}
}
end

context 'hash where no keys need to be quoted' do
it 'does not register an offense' do
Expand Down Expand Up @@ -218,18 +207,18 @@
expect_offense(<<~RUBY)
{
a: 1,
^ Symbol hash key should be quoted for consistency; use `'a':` instead.
^ Symbol hash key should be quoted for consistency; use `"a":` instead.
b: 2,
^ Symbol hash key should be quoted for consistency; use `'b':` instead.
^ Symbol hash key should be quoted for consistency; use `"b":` instead.
'c': 3,
'd-e': 4
}
RUBY

expect_correction(<<~RUBY)
{
'a': 1,
'b': 2,
"a": 1,
"b": 2,
'c': 3,
'd-e': 4
}
Expand All @@ -243,14 +232,14 @@
{
'a=': 1,
b: 2
^ Symbol hash key should be quoted for consistency; use `'b':` instead.
^ Symbol hash key should be quoted for consistency; use `"b":` instead.
}
RUBY

expect_correction(<<~RUBY)
{
'a=': 1,
'b': 2
"b": 2
}
RUBY
end
Expand Down Expand Up @@ -279,43 +268,5 @@
RUBY
end
end

context 'quote style' do
let(:source) do
<<~RUBY
{ a: 1, 'b-c': 2 }
RUBY
end

context 'when Style/StringLiterals is not enabled' do
let(:string_literals_enabled) { false }

it 'uses double quotes to correct' do
expect_correction(<<~RUBY, source: source)
{ "a": 1, 'b-c': 2 }
RUBY
end
end

context 'when Style/StringLiterals uses single_quotes style' do
let(:string_literals_style) { 'single_quotes' }

it 'uses double quotes to correct' do
expect_correction(<<~RUBY, source: source)
{ 'a': 1, 'b-c': 2 }
RUBY
end
end

context 'when Style/StringLiterals uses double_quotes style' do
let(:string_literals_style) { 'double_quotes' }

it 'uses double quotes to correct' do
expect_correction(<<~RUBY, source: source)
{ "a": 1, 'b-c': 2 }
RUBY
end
end
end
end
end