Skip to content

Commit

Permalink
Add spaces as EnforcedStyle to SpaceInsideParens (#5672)
Browse files Browse the repository at this point in the history
Simply supply `EnforcedStyle: space` in your rubocop.yml configuration and instead of enforcing no spaces inside parens, it will enforce spaces.

Even supports auto-correct!

Stylistic choice, to be sure, but one that can help alleviate cluttered code and parentheses hell.
  • Loading branch information
joshuapinter authored and bbatsov committed Apr 16, 2018
1 parent ed013be commit c6efac6
Show file tree
Hide file tree
Showing 5 changed files with 210 additions and 44 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

* [#5752](https://github.com/bbatsov/rubocop/pull/5752): Add `String#delete_{prefix,suffix}` to Lint/Void cop. ([@bdewater][])
* [#5734](https://github.com/bbatsov/rubocop/pull/5734): Add `by`, `on`, `in` and `at` to allowed names of `Naming/UncommunicativeMethodParamName` cop in default config. ([@AlexWayfer][])
* [#5666](https://github.com/bbatsov/rubocop/issues/5666): Add spaces as an `EnforcedStyle` option to `Layout/SpaceInsideParens`, allowing you to enforce spaces inside of parentheses. ([@joshuapinter][])
* [#4257](https://github.com/bbatsov/rubocop/issues/4257): Allow specifying module name in `Metrics/BlockLength`'s `ExcludedMethods` configuration option. ([@akhramov][])
* [#4753](https://github.com/bbatsov/rubocop/issues/4753): Add `IgnoredMethods` option to `Style/MethodCallWithoutArgsParentheses` cop. ([@Darhazer][])
* [#5652](https://github.com/bbatsov/rubocop/issues/5652): Make `Style/OptionHash` aware of implicit parameter passing to super. ([@Wei-LiangChew][])
Expand Down Expand Up @@ -3302,5 +3303,6 @@
[@mcfisch]: https://github.com/mcfisch
[@istateside]: https://github.com/istateside
[@parkerfinch]: https://github.com/parkerfinch
[@joshuapinter]: https://github.com/joshuapinter
[@Darhazer]: https://github.com/Darhazer
[@Wei-LiangChew]: https://github.com/Wei-LiangChew
6 changes: 6 additions & 0 deletions config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,12 @@ Layout/SpaceInsideHashLiteralBraces:
- space
- no_space

Layout/SpaceInsideParens:
EnforcedStyle: no_space
SupportedStyles:
- space
- no_space

Layout/SpaceInsideReferenceBrackets:
EnforcedStyle: no_space
SupportedStyles:
Expand Down
68 changes: 64 additions & 4 deletions lib/rubocop/cop/layout/space_inside_parens.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ module Cop
module Layout
# Checks for spaces inside ordinary round parentheses.
#
# @example EnforcedStyle: no_space (default)
# # The `no_space` style enforces that parentheses do not have spaces
#
# @example
# # bad
# f( 3)
Expand All @@ -13,21 +16,52 @@ module Layout
# # good
# f(3)
# g = (a + 3)
#
# @example EnforcedStyle: space
# # The `space` style enforces that parentheses have a space at the
# # beginning and end.
# # Note: Empty paraentheses should not have spaces.
#
# # bad
# f(3)
# g = (a + 3)
# y( )
#
# # good
# f( 3 )
# g = ( a + 3 )
# y()
#
class SpaceInsideParens < Cop
include SurroundingSpace
include RangeHelp

MSG = 'Space inside parentheses detected.'.freeze
MSG = 'Space inside parentheses detected.'.freeze
MSG_SPACE = 'No space inside parentheses detected.'.freeze

def investigate(processed_source)
@processed_source = processed_source
each_extraneous_space(processed_source.tokens) do |range|
add_offense(range, location: range)

if cop_config['EnforcedStyle'] == 'space'
each_missing_space(processed_source.tokens) do |range|
add_offense(range, location: range, message: MSG_SPACE)
end

else
each_extraneous_space(processed_source.tokens) do |range|
add_offense(range, location: range)
end
end
end

def autocorrect(range)
->(corrector) { corrector.remove(range) }
lambda do |corrector|
if cop_config['EnforcedStyle'] == 'space'
corrector.insert_before(range, ' ')
else
corrector.remove(range)
end
end
end

private
Expand All @@ -45,9 +79,35 @@ def each_extraneous_space(tokens)
end
end

def each_missing_space(tokens)
tokens.each_cons(2) do |token1, token2|
next if can_be_ignored?(token1, token2)

next unless token2.line == token1.line && !token1.space_after?

if token1.left_parens?
yield range_between(token2.begin_pos, token2.begin_pos + 1)

elsif token2.right_parens?
yield range_between(token2.begin_pos, token2.end_pos)
end
end
end

def parens?(token1, token2)
token1.left_parens? || token2.right_parens?
end

def can_be_ignored?(token1, token2)
return true unless parens?(token1, token2)

# If the second token is a comment, that means that a line break
# follows, and that the rules for space inside don't apply.
return true if token2.comment?

# Ignore empty parens. # TODO: Could be configurable.
return true if token1.left_parens? && token2.right_parens?
end
end
end
end
Expand Down
28 changes: 28 additions & 0 deletions manual/cops_layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -3777,6 +3777,11 @@ Checks for spaces inside ordinary round parentheses.
### Examples
#### EnforcedStyle: no_space (default)
```ruby
# The `no_space` style enforces that parentheses do not have spaces
```
```ruby
# bad
f( 3)
Expand All @@ -3786,6 +3791,29 @@ g = (a + 3 )
f(3)
g = (a + 3)
```
#### EnforcedStyle: space

```ruby
# The `space` style enforces that parentheses have a space at the
# beginning and end.
# Note: Empty paraentheses should not have spaces.

# bad
f(3)
g = (a + 3)
y( )

# good
f( 3 )
g = ( a + 3 )
y()
```

### Configurable attributes

Name | Default value | Configurable values
--- | --- | ---
EnforcedStyle | `no_space` | `space`, `no_space`

### References

Expand Down
150 changes: 110 additions & 40 deletions spec/rubocop/cop/layout/space_inside_parens_spec.rb
Original file line number Diff line number Diff line change
@@ -1,50 +1,120 @@
# frozen_string_literal: true

RSpec.describe RuboCop::Cop::Layout::SpaceInsideParens do
subject(:cop) { described_class.new }

it 'registers an offense for spaces inside parens' do
expect_offense(<<-RUBY.strip_indent)
f( 3)
^ Space inside parentheses detected.
g = (a + 3 )
^ Space inside parentheses detected.
RUBY
end
RSpec.describe RuboCop::Cop::Layout::SpaceInsideParens, :config do
subject(:cop) { described_class.new(config) }

it 'accepts parentheses in block parameter list' do
expect_no_offenses(<<-RUBY.strip_indent)
list.inject(Tms.new) { |sum, (label, item)|
}
RUBY
end
context 'when EnforcedStyle is no_space' do
let(:cop_config) { { 'EnforcedStyle' => 'no_space' } }

it 'accepts parentheses with no spaces' do
expect_no_offenses('split("\\n")')
end
it 'registers an offense for spaces inside parens' do
expect_offense(<<-RUBY.strip_indent)
f( 3)
^ Space inside parentheses detected.
g = (a + 3 )
^ Space inside parentheses detected.
RUBY
end

it 'accepts parentheses with line break' do
expect_no_offenses(<<-RUBY.strip_indent)
f(
1)
RUBY
end
it 'accepts parentheses in block parameter list' do
expect_no_offenses(<<-RUBY.strip_indent)
list.inject(Tms.new) { |sum, (label, item)|
}
RUBY
end

it 'accepts parentheses with no spaces' do
expect_no_offenses('split("\\n")')
end

it 'accepts parentheses with line break' do
expect_no_offenses(<<-RUBY.strip_indent)
f(
1)
RUBY
end

it 'accepts parentheses with comment and line break' do
expect_no_offenses(<<-RUBY.strip_indent)
f( # Comment
1)
RUBY
end

it 'accepts parentheses with comment and line break' do
expect_no_offenses(<<-RUBY.strip_indent)
f( # Comment
1)
RUBY
it 'auto-corrects unwanted space' do
new_source = autocorrect_source(<<-RUBY.strip_indent)
f( 3)
g = ( a + 3 )
RUBY
expect(new_source).to eq(<<-RUBY.strip_indent)
f(3)
g = (a + 3)
RUBY
end
end

it 'auto-corrects unwanted space' do
new_source = autocorrect_source(<<-RUBY.strip_indent)
f( 3)
g = ( a + 3 )
RUBY
expect(new_source).to eq(<<-RUBY.strip_indent)
f(3)
g = (a + 3)
RUBY
context 'when EnforcedStyle is space' do
let(:cop_config) { { 'EnforcedStyle' => 'space' } }

it 'registers an offense for no spaces inside parens' do
expect_offense(<<-RUBY.strip_indent)
f( 3)
^ No space inside parentheses detected.
g = (a + 3 )
^ No space inside parentheses detected.
split("\\n")
^ No space inside parentheses detected.
^ No space inside parentheses detected.
RUBY
end

it 'accepts parentheses in block parameter list with no spaces' do
expect_offense(<<-RUBY.strip_indent)
list.inject( Tms.new ) { |sum, (label, item)|
^ No space inside parentheses detected.
^ No space inside parentheses detected.
}
RUBY
end

it 'accepts parentheses with spaces' do
expect_no_offenses(<<-RUBY.strip_indent)
f( 3 )
g = ( a + 3 )
split( "\\n" )
RUBY
end

it 'accepts parentheses with line break' do
expect_no_offenses(<<-RUBY.strip_indent)
f(
1 )
RUBY
end

it 'accepts parentheses with comment and line break' do
expect_no_offenses(<<-RUBY.strip_indent)
f( # Comment
1 )
RUBY
end

it 'auto-corrects wanted space' do
new_source = autocorrect_source(<<-RUBY.strip_indent)
f(3)
f( 3)
f(3 )
g = (a + 3)
g = ( a + 3)
g = (a + 3 )
RUBY
expect(new_source).to eq(<<-RUBY.strip_indent)
f( 3 )
f( 3 )
f( 3 )
g = ( a + 3 )
g = ( a + 3 )
g = ( a + 3 )
RUBY
end
end
end

0 comments on commit c6efac6

Please sign in to comment.