Skip to content

Commit

Permalink
[Fix rubocop#3058] Add space inside percent literal style cops (ruboc…
Browse files Browse the repository at this point in the history
…op#3214)

Add `Style/SpaceInsideArrayPercentLiteral` and
`Style/SpaceInsidePercentLiteralDelimiters`
  • Loading branch information
owst authored and Neodelf committed Oct 15, 2016
1 parent 8b69b60 commit 1488e2c
Show file tree
Hide file tree
Showing 9 changed files with 352 additions and 0 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
* [#3173](https://github.com/bbatsov/rubocop/pull/3173): Make `Style/ModuleFunction` configurable with `module_function` and `extend_self` styles. ([@tjwp][])
* [#3105](https://github.com/bbatsov/rubocop/issues/3105): Add new `Style/RequestReferer` cop. ([@giannileggio][])
* [#3200](https://github.com/bbatsov/rubocop/pull/3200): Add autocorrect for `Style/EachForSimpleLoop` cop. ([@tejasbubane][])
* [#3058](https://github.com/bbatsov/rubocop/issues/3058): Add new `Style/SpaceInsideArrayPercentLiteral` cop. ([@owst][])
* [#3058](https://github.com/bbatsov/rubocop/issues/3058): Add new `Style/SpaceInsidePercentLiteralDelimiters` cop. ([@owst][])
* [#3179](https://github.com/bbatsov/rubocop/pull/3179): Expose files to support testings Cops using RSpec. ([@tjwp][])
* [#3191](https://github.com/bbatsov/rubocop/issues/3191): Allow arbitrary comments after cop names in CommentConfig lines (e.g. rubocop:enable). ([@owst][])
* [#3177](https://github.com/bbatsov/rubocop/pull/3177): Add new `Style/NumericLiteralPrefix` cop. ([@tejasbubane][])
Expand Down
8 changes: 8 additions & 0 deletions config/enabled.yml
Original file line number Diff line number Diff line change
Expand Up @@ -760,6 +760,14 @@ Style/SpaceAroundOperators:
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators'
Enabled: true

Style/SpaceInsideArrayPercentLiteral:
Description: 'No unnecessary additional spaces between elements in %i/%w literals.'
Enabled: true

Style/SpaceInsidePercentLiteralDelimiters:
Description: 'No unnecessary spaces inside delimiters of %i/%w/%x literals.'
Enabled: true

Style/SpaceInsideBrackets:
Description: 'No spaces after [ or before ].'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-spaces-braces'
Expand Down
3 changes: 3 additions & 0 deletions lib/rubocop.rb
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
require 'rubocop/cop/mixin/if_node'
require 'rubocop/cop/mixin/integer_node'
require 'rubocop/cop/mixin/on_method_def'
require 'rubocop/cop/mixin/match_range'
require 'rubocop/cop/mixin/method_complexity' # relies on on_method_def
require 'rubocop/cop/mixin/method_preference'
require 'rubocop/cop/mixin/min_body_length'
Expand Down Expand Up @@ -329,10 +330,12 @@
require 'rubocop/cop/style/space_before_comment'
require 'rubocop/cop/style/space_before_first_arg'
require 'rubocop/cop/style/space_before_semicolon'
require 'rubocop/cop/style/space_inside_array_percent_literal'
require 'rubocop/cop/style/space_inside_block_braces'
require 'rubocop/cop/style/space_inside_brackets'
require 'rubocop/cop/style/space_inside_hash_literal_braces'
require 'rubocop/cop/style/space_inside_parens'
require 'rubocop/cop/style/space_inside_percent_literal_delimiters'
require 'rubocop/cop/style/space_inside_range_literal'
require 'rubocop/cop/style/space_inside_string_interpolation'
require 'rubocop/cop/style/special_global_vars'
Expand Down
26 changes: 26 additions & 0 deletions lib/rubocop/cop/mixin/match_range.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# encoding: utf-8
# frozen_string_literal: true

module RuboCop
module Cop
# Common functionality for obtaining source ranges from regexp matches
module MatchRange
# Return a new `Range` covering the first matching group number for each
# match of `regex` inside `range`
def each_match_range(range, regex)
range.source.scan(regex) do
yield match_range(range, Regexp.last_match)
end
end

# For a `match` inside `range`, return a new `Range` covering the match
def match_range(range, match)
Parser::Source::Range.new(
range.source_buffer,
range.begin_pos + match.begin(1),
range.begin_pos + match.end(1)
)
end
end
end
end
10 changes: 10 additions & 0 deletions lib/rubocop/cop/mixin/percent_literal.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@ def begin_source(node)
def type(node)
node.loc.begin.source[0..-2]
end

# A range containing only the contents of the percent literal (e.g. in
# %i{1 2 3} this will be the range covering '1 2 3' only)
def contents_range(node)
Parser::Source::Range.new(
node.loc.expression.source_buffer,
node.loc.begin.end_pos,
node.loc.end.begin_pos
)
end
end
end
end
53 changes: 53 additions & 0 deletions lib/rubocop/cop/style/space_inside_array_percent_literal.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# encoding: utf-8
# frozen_string_literal: true

module RuboCop
module Cop
module Style
# Checks for unnecessary additional spaces inside array percent literals
# (i.e. %i/%w).
#
# @good
# %i(foo bar baz)
#
# @bad
# %w(foo bar baz)
class SpaceInsideArrayPercentLiteral < Cop
include MatchRange
include PercentLiteral

MSG = 'Use only a single space inside array percent literal.'.freeze
MULTIPLE_SPACES_BETWEEN_ITEMS_REGEX =
/(?:[\S&&[^\\]](?:\\ )*)( {2,})(?=\S)/

def on_array(node)
process(node, *%w(%i %I %w %W))
end

def on_percent_literal(node)
each_unnecessary_space_match(node) do |range|
add_offense(node, range, MSG)
end
end

def autocorrect(node)
lambda do |corrector|
each_unnecessary_space_match(node) do |range|
corrector.replace(range, ' ')
end
end
end

private

def each_unnecessary_space_match(node, &blk)
each_match_range(
contents_range(node),
MULTIPLE_SPACES_BETWEEN_ITEMS_REGEX,
&blk
)
end
end
end
end
end
64 changes: 64 additions & 0 deletions lib/rubocop/cop/style/space_inside_percent_literal_delimiters.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# encoding: utf-8
# frozen_string_literal: true

module RuboCop
module Cop
module Style
# Checks for unnecessary additional spaces inside the delimiters of
# %i/%w/%x literals.
#
# @good
# %i(foo bar baz)
#
# @bad
# %w( foo bar baz )
#
# @bad
# %x( ls -l )
class SpaceInsidePercentLiteralDelimiters < Cop
include MatchRange
include PercentLiteral

MSG = 'Do not use spaces inside percent literal delimiters.'.freeze
BEGIN_REGEX = /\A( +)/
END_REGEX = /(?<!\\)( +)\z/

def on_array(node)
process(node, *%w(%i %I %w %W))
end

def on_percent_literal(node)
add_offenses_for_unnecessary_spaces(node)
end

def on_xstr(node)
add_offenses_for_unnecessary_spaces(node)
end

def autocorrect(node)
lambda do |corrector|
regex_matches(node) do |match_range|
corrector.remove(match_range)
end
end
end

private

def add_offenses_for_unnecessary_spaces(node)
return unless node.single_line?

regex_matches(node) do |match_range|
add_offense(node, match_range, MSG)
end
end

def regex_matches(node, &blk)
[BEGIN_REGEX, END_REGEX].each do |regex|
each_match_range(contents_range(node), regex, &blk)
end
end
end
end
end
end
86 changes: 86 additions & 0 deletions spec/rubocop/cop/style/space_inside_array_percent_literal_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# encoding: utf-8
# frozen_string_literal: true

require 'spec_helper'

describe RuboCop::Cop::Style::SpaceInsideArrayPercentLiteral do
subject(:cop) { described_class.new }

%w(i I w W).each do |type|
[%w({ }), %w{( )}, %w([ ]), %w(! !)].each do |(ldelim, rdelim)|
context "for #{type} type and #{[ldelim, rdelim]} delimiters" do
define_method(:example) do |content|
['%', type, ldelim, content, rdelim].join
end

def expect_corrected(source, expected)
expect(autocorrect_source(cop, source)).to eq expected
end

it 'registers an offense for unnecessary spaces' do
source = example('1 2')
inspect_source(cop, source)
expect(cop.offenses.size).to eq(1)
expect(cop.highlights).to eq([' '])
expect(cop.messages).to eq([described_class::MSG])
expect_corrected(source, example('1 2'))
end

it 'registers an offense for multiple spaces between items' do
source = example('1 2 3')
inspect_source(cop, source)
expect(cop.offenses.size).to eq(2)
expect_corrected(source, example('1 2 3'))
end

it 'accepts literals with escaped and additional spaces' do
source = example('a\ b \ c')
inspect_source(cop, source)
expect(cop.offenses.size).to eq(1)
expect_corrected(source, example('a\ b \ c'))
end

it 'accepts literals without additional spaces' do
inspect_source(cop, example('a b c'))
expect(cop.messages).to be_empty
end

it 'accepts literals with escaped spaces' do
inspect_source(cop, example('a\ b\ \ c'))
expect(cop.messages).to be_empty
end

it 'accepts multi-line literals' do
inspect_source(cop, ["%#{type}(",
' a',
' b',
' c',
')'])
expect(cop.messages).to be_empty
end

it 'accepts multi-line literals within a method' do
inspect_source(cop, ['def foo',
" %#{type}(",
' a',
' b',
' c',
' )',
'end'])
expect(cop.messages).to be_empty
end

it 'accepts newlines and additional following alignment spaces' do
inspect_source(cop, ["%#{type}(a b",
' c)'])
expect(cop.messages).to be_empty
end
end
end
end

it 'accepts non array percent literals' do
inspect_source(cop, '%q( a b c )')
expect(cop.messages).to be_empty
end
end
100 changes: 100 additions & 0 deletions spec/rubocop/cop/style/space_inside_percent_literal_delimiters_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# encoding: utf-8
# frozen_string_literal: true

require 'spec_helper'

describe RuboCop::Cop::Style::SpaceInsidePercentLiteralDelimiters do
subject(:cop) { described_class.new }

%w(i I w W x).each do |type|
[%w({ }), %w{( )}, %w([ ]), %w(! !)].each do |(ldelim, rdelim)|
context "for #{type} type and #{[ldelim, rdelim]} delimiters" do
define_method(:example) do |content|
['%', type, ldelim, content, rdelim].join
end

def expect_corrected(source, expected)
expect(autocorrect_source(cop, source)).to eq expected
end

it 'registers an offense for unnecessary spaces' do
source = example(' 1 2 ')
inspect_source(cop, source)
expect(cop.offenses.size).to eq(2)
expect(cop.messages.uniq).to eq([described_class::MSG])
expect(cop.highlights).to eq([' ', ' '])
expect_corrected(source, example('1 2'))
end

it 'registers an offense for spaces after first delimiter' do
source = example(' 1 2')
inspect_source(cop, source)
expect(cop.offenses.size).to eq(1)
expect_corrected(source, example('1 2'))
end

it 'registers an offense for spaces before final delimiter' do
source = example('1 2 ')
inspect_source(cop, source)
expect(cop.offenses.size).to eq(1)
expect_corrected(source, example('1 2'))
end

it 'registers an offense for literals with escaped and other spaces' do
source = example(' \ a b c\ ')
inspect_source(cop, source)
expect(cop.offenses.size).to eq(2)
expect_corrected(source, example('\ a b c\ '))
end

it 'accepts literals without additional spaces' do
inspect_source(cop, example('a b c'))
expect(cop.messages).to be_empty
end

it 'accepts literals with escaped spaces' do
inspect_source(cop, example('\ a b c\ '))
expect(cop.messages).to be_empty
end

it 'accepts multi-line literals' do
inspect_source(cop, ["%#{type}(",
' a',
' b',
' c',
')'])
expect(cop.messages).to be_empty
end

it 'accepts multi-line literals within a method' do
inspect_source(cop, ['def foo',
" %#{type}(",
' a',
' b',
' c',
' )',
'end'])
expect(cop.messages).to be_empty
end

it 'accepts newlines and additional following alignment spaces' do
inspect_source(cop, ["%#{type}(a b",
' c)'])
expect(cop.messages).to be_empty
end

it 'accepts spaces between entries' do
inspect_source(cop, example('a b c'))
expect(cop.messages).to be_empty
end
end
end
end

it 'accepts other percent literals' do
%w(q r s).each do |type|
inspect_source(cop, "%#{type}( a b c )")
expect(cop.messages).to be_empty
end
end
end

0 comments on commit 1488e2c

Please sign in to comment.