Skip to content

Commit

Permalink
Merge pull request rubocop#1205 from jonas054/835_percent_q_case_cop
Browse files Browse the repository at this point in the history
Add cop PercentQLiterals
  • Loading branch information
bbatsov committed Jul 12, 2014
2 parents 0bf1562 + 6bf924a commit 8aef42b
Show file tree
Hide file tree
Showing 10 changed files with 199 additions and 11 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Change log

### New features

* [#835](https://github.com/bbatsov/rubocop/issues/835): New cop `PercentQLiterals` checks if use of `%Q` and `%q` matches configuration. ([@jonas054][])

## master (unreleased)

### Bugs fixed
Expand Down
6 changes: 6 additions & 0 deletions config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,12 @@ Style/PercentLiteralDelimiters:
'%W': ()
'%x': ()

Style/PercentQLiterals:
EnforcedStyle: lower_case_q
SupportedStyles:
- lower_case_q # Use %q when possible, %Q when necessary
- upper_case_q # Always use %Q

Style/PredicateName:
NamePrefixBlacklist:
- is_
Expand Down
4 changes: 4 additions & 0 deletions config/enabled.yml
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,10 @@ Style/PercentLiteralDelimiters:
Description: 'Use `%`-literal delimiters consistently'
Enabled: true

Style/PercentQLiterals:
Description: 'Checks if uses of %Q/%q match the configured preference.'
Enabled: true

Style/PerlBackrefs:
Description: 'Avoid Perl-style regex back references.'
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 @@ -186,6 +186,7 @@
require 'rubocop/cop/style/parameter_lists'
require 'rubocop/cop/style/parentheses_around_condition'
require 'rubocop/cop/style/percent_literal_delimiters'
require 'rubocop/cop/style/percent_q_literals'
require 'rubocop/cop/style/perl_backrefs'
require 'rubocop/cop/style/predicate_name'
require 'rubocop/cop/style/proc'
Expand Down
3 changes: 2 additions & 1 deletion lib/rubocop/cop/mixin/percent_literal.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ def percent_literal?(node)
end

def process(node, *types)
on_percent_literal(node, types) if percent_literal?(node)
return unless percent_literal?(node) && types.include?(type(node))
on_percent_literal(node)
end

def begin_source(node)
Expand Down
7 changes: 3 additions & 4 deletions lib/rubocop/cop/style/percent_literal_delimiters.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,10 @@ def autocorrect(node)
end
end

def on_percent_literal(node, types)
def on_percent_literal(node)
type = type(node)
return unless types.include?(type) &&
!uses_preferred_delimiter?(node, type) &&
!contains_preferred_delimiter?(node, type)
return if uses_preferred_delimiter?(node, type) ||
contains_preferred_delimiter?(node, type)

add_offense(node, :expression)
end
Expand Down
53 changes: 53 additions & 0 deletions lib/rubocop/cop/style/percent_q_literals.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# encoding: utf-8

module RuboCop
module Cop
module Style
# This cop checks for usage of the %Q() syntax when %q() would do.
class PercentQLiterals < Cop
include PercentLiteral
include ConfigurableEnforcedStyle

def on_str(node)
process(node, '%Q', '%q')
end

private

def on_percent_literal(node)
if style == :lower_case_q
if type(node) == '%Q'
check(node,
'Do not use `%Q` unless interpolation is needed. ' \
'Use `%q`.')
end
elsif type(node) == '%q'
check(node, 'Use `%Q` instead of `%q`.')
end
end

def check(node, msg)
src = node.loc.expression.source
# Report offense only if changing case doesn't change semantics,
# i.e., if the string would become dynamic or has special characters.
return if node.children !=
ProcessedSource.new(corrected(src)).ast.children

add_offense(node, :begin, msg)
end

def autocorrect(node)
src = node.loc.expression.source

@corrections << lambda do |corrector|
corrector.replace(node.loc.expression, corrected(src))
end
end

def corrected(src)
src.sub(src[1], src[1].swapcase)
end
end
end
end
end
6 changes: 2 additions & 4 deletions lib/rubocop/cop/style/unneeded_capital_w.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,8 @@ def on_array(node)

private

def on_percent_literal(node, types)
type = type(node)
return unless types.include?(type) &&
node.children.none? { |x| x.type == :dstr }
def on_percent_literal(node)
return unless node.children.none? { |x| x.type == :dstr }

add_offense(node, :expression)
end
Expand Down
122 changes: 122 additions & 0 deletions spec/rubocop/cop/style/percent_q_literals_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# encoding: utf-8

require 'spec_helper'

describe RuboCop::Cop::Style::PercentQLiterals, :config do
subject(:cop) { described_class.new(config) }

shared_examples 'accepts quote characters' do
it 'accepts single quotes' do
inspect_source(cop, ["'hi'"])
expect(cop.offenses).to be_empty
end

it 'accepts double quotes' do
inspect_source(cop, ['"hi"'])
expect(cop.offenses).to be_empty
end
end

shared_examples 'accepts any q string with backslash t' do
context 'with special characters' do
it 'accepts %q' do
inspect_source(cop, ['%q(\t)'])
expect(cop.offenses).to be_empty
end

it 'accepts %Q' do
inspect_source(cop, ['%Q(\t)'])
expect(cop.offenses).to be_empty
end
end
end

context 'when EnforcedStyle is lower_case_q' do
let(:cop_config) { { 'EnforcedStyle' => 'lower_case_q' } }

context 'without interpolation' do
it 'accepts %q' do
inspect_source(cop, ['%q(hi)'])
expect(cop.offenses).to be_empty
end

it 'registers offense for %Q' do
inspect_source(cop, ['%Q(hi)'])
expect(cop.messages)
.to eq(['Do not use `%Q` unless interpolation is needed. Use `%q`.'])
expect(cop.highlights).to eq(['%Q('])
end

it 'auto-corrects' do
new_source = autocorrect_source(cop, '%Q(hi)')
expect(new_source).to eq('%q(hi)')
end

include_examples 'accepts quote characters'
include_examples 'accepts any q string with backslash t'
end

context 'with interpolation' do
it 'accepts %Q' do
inspect_source(cop, ['%Q(#{1 + 2})'])
expect(cop.offenses).to be_empty
end

it 'accepts %q' do
# This is most probably a mistake, but not this cop's responisibility.
inspect_source(cop, ['%q(#{1 + 2})'])
expect(cop.offenses).to be_empty
end

include_examples 'accepts quote characters'
end
end

context 'when EnforcedStyle is upper_case_q' do
let(:cop_config) { { 'EnforcedStyle' => 'upper_case_q' } }

context 'without interpolation' do
it 'registers offense for %q' do
inspect_source(cop, ['%q(hi)'])
expect(cop.messages).to eq(['Use `%Q` instead of `%q`.'])
expect(cop.highlights).to eq(['%q('])
end

it 'accepts %Q' do
inspect_source(cop, ['%Q(hi)'])
expect(cop.offenses).to be_empty
end

it 'auto-corrects' do
new_source = autocorrect_source(cop, '%q[hi]')
expect(new_source).to eq('%Q[hi]')
end

include_examples 'accepts quote characters'
include_examples 'accepts any q string with backslash t'
end

context 'with interpolation' do
it 'accepts %Q' do
inspect_source(cop, ['%Q(#{1 + 2})'])
expect(cop.offenses).to be_empty
end

it 'accepts %q' do
# It's strange if interpolation syntax appears inside a static string,
# but we can't be sure if it's a mistake or not. Changing it to %Q
# would alter semantics, so we leave it as it is.
inspect_source(cop, ['%q(#{1 + 2})'])
expect(cop.offenses).to be_empty
end

it 'does not auto-correct' do
source = '%q(#{1 + 2})'
new_source = autocorrect_source(cop, source)
expect(new_source).to eq(source)
end

include_examples 'accepts quote characters'
end
end
end
4 changes: 2 additions & 2 deletions spec/rubocop/cop/style/unneeded_percent_q_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
end

it 'accepts a string with single quotes and double quotes' do
inspect_source(cop, %Q(%q('"hi"')))
inspect_source(cop, %q(%q('"hi"')))
expect(cop.offenses).to be_empty
end

Expand All @@ -43,7 +43,7 @@
end

it 'accepts a string with single quotes and double quotes' do
inspect_source(cop, %Q(%Q('"hi"')))
inspect_source(cop, %q(%Q('"hi"')))
expect(cop.offenses).to be_empty
end

Expand Down

0 comments on commit 8aef42b

Please sign in to comment.