Skip to content

Commit

Permalink
[Fix rubocop#9793] Add Style/QuotedSymbols to enforce consistency i…
Browse files Browse the repository at this point in the history
…n quoted symbols.

Like `Style/StringLiterals`, this cop checks for the quoting style for symbols to be consistent. Checking whether a symbol _should_ be quoted or not is still handled by `Lint/SymbolConversion`.

This cop shares as much as possible with `Style/StringLiterals` for ease of maintainabilty. As well, for simple configuration, the default "style" is `same_as_string_literals` which will automatically apply the same configuration set up for `Style/StringLiterals` to this cop as well (it defaults to `single_quotes` if `StringLiterals` is not enabled).
  • Loading branch information
dvandersluis committed May 17, 2021
1 parent ac00a83 commit 2fabcd8
Show file tree
Hide file tree
Showing 10 changed files with 367 additions and 8 deletions.
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][])
10 changes: 10 additions & 0 deletions config/default.yml
Expand Up @@ -4186,6 +4186,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
5 changes: 1 addition & 4 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 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

0 comments on commit 2fabcd8

Please sign in to comment.