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 #3631] Add cop to check for spaces in '-> (' format lambdas #3656

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
* [#3646](https://github.com/bbatsov/rubocop/pull/3646): Add new `Lint/EmptyWhen` cop. ([@drenmi][])
* [#3246](https://github.com/bbatsov/rubocop/issues/3246): Add list of all cops to the manual (generated automatically from a rake task). ([@sihu][])
* [#3647](https://github.com/bbatsov/rubocop/issues/3647): Add `--force-default-config` option. ([@jawshooah][])
* [#3631](https://github.com/bbatsov/rubocop/issues/3631): Add new `Style/SpaceInLambdaLiteral` cop to check for spaces in lambda literals. ([@swcraig][])

### Bug fixes

Expand Down
6 changes: 6 additions & 0 deletions config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -629,6 +629,12 @@ Style/Lambda:
- lambda
- literal

Style/SpaceInLambdaLiteral:
EnforcedStyle: require_no_space
SupportedStyles:
- require_no_space
- require_space

Style/LambdaCall:
EnforcedStyle: call
SupportedStyles:
Expand Down
4 changes: 4 additions & 0 deletions config/enabled.yml
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,10 @@ Style/Lambda:
StyleGuide: '#lambda-multi-line'
Enabled: true

Style/SpaceInLambdaLiteral:
Description: 'Checks for spaces in lambda literals.'
Enabled: true

Style/LambdaCall:
Description: 'Use lambda.call(...) instead of lambda.(...).'
StyleGuide: '#proc-call'
Expand Down
1 change: 1 addition & 0 deletions lib/rubocop.rb
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,7 @@
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_in_lambda_literal'
require 'rubocop/cop/style/space_inside_array_percent_literal'
require 'rubocop/cop/style/space_inside_block_braces'
require 'rubocop/cop/style/space_inside_brackets'
Expand Down
86 changes: 86 additions & 0 deletions lib/rubocop/cop/style/space_in_lambda_literal.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# frozen_string_literal: true

module RuboCop
module Cop
module Style
# This cop checks for spaces in lambda literals.
#
# @example
#
# EnforcedStyle: require_no_space (default)
#
# @bad
# a = -> (x, y) { x + y }
#
# @good
# a = ->(x, y) { x + y }
#
# @example
#
# EnforcedStyle: require_space
#
# @bad
# a = ->(x, y) { x + y }
#
# @good
# a = -> (x, y) { x + y }
class SpaceInLambdaLiteral < Cop
include ConfigurableEnforcedStyle

ARROW = '->'.freeze
MSG_REQUIRE_SPACE = 'Use a space between -> and opening bracket ' \
'in lambda literals'.freeze
MSG_REQUIRE_NO_SPACE = 'Do not use spaces between -> and opening ' \
'bracket in lambda literals'.freeze

def on_send(node)
return unless arrow_lambda_with_args?(node)
if style == :require_space && !space_after_arrow?(node)
add_offense(node, node.parent.loc.expression, MSG_REQUIRE_SPACE)
elsif style == :require_no_space && space_after_arrow?(node)
add_offense(node, node.parent.loc.expression, MSG_REQUIRE_NO_SPACE)
end
end

def autocorrect(lambda_node)
children = lambda_node.parent.children
lambda do |corrector|
if style == :require_space
corrector.insert_before(children[1].source_range, ' ')
else
space_range = range_between(children[0].source_range.end_pos,
children[1].source_range.begin_pos)
corrector.remove(space_range)
end
end
end

private

def arrow_lambda_with_args?(node)
lambda_node?(node) && arrow_form?(node) && args?(node)
end

def lambda_node?(node)
receiver, call = *node
receiver.nil? && call == :lambda
end

def arrow_form?(lambda_node)
lambda_node.loc.selector.source == ARROW
end

def args?(lambda_node)
_call, args, _body = *lambda_node.parent
!args.children.empty?
end

def space_after_arrow?(lambda_node)
arrow = lambda_node.parent.children[0]
parentheses = lambda_node.parent.children[1]
parentheses.source_range.begin_pos - arrow.source_range.end_pos > 0
end
end
end
end
end
39 changes: 38 additions & 1 deletion manual/cops_style.md
Original file line number Diff line number Diff line change
Expand Up @@ -3797,7 +3797,7 @@ parameters.

Attribute | Value
--- | ---
Methods | {"reduce"=>["a", "e"]}, {"inject"=>["a", "e"]}
Methods | {"reduce"=>["acc", "elem"]}, {"inject"=>["acc", "elem"]}


## Style/SingleLineMethods
Expand Down Expand Up @@ -4037,6 +4037,43 @@ Enabled | Yes

Checks for semicolon (;) preceded by space.

## Style/SpaceInLambdaLiteral

Enabled by default | Supports autocorrection
--- | ---
Enabled | Yes

This cop checks for spaces in lambda literals.

### Example

```ruby
EnforcedStyle: require_no_space (default)

# bad
a = -> (x, y) { x + y }

# good
a = ->(x, y) { x + y }
```
```ruby
EnforcedStyle: require_space

# bad
a = ->(x, y) { x + y }

# good
a = -> (x, y) { x + y }
```

### Important attributes

Attribute | Value
--- | ---
EnforcedStyle | require_no_space
SupportedStyles | require_no_space, require_space


## Style/SpaceInsideArrayPercentLiteral

Enabled by default | Supports autocorrection
Expand Down
150 changes: 150 additions & 0 deletions spec/rubocop/cop/style/space_in_lambda_literal_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# frozen_string_literal: true

require 'spec_helper'

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

context 'when configured to enforce spaces' do
let(:cop_config) { { 'EnforcedStyle' => 'require_space' } }

it 'registers an offense for no space between -> and (' do
inspect_source(cop, 'a = ->(b, c) { b + c }')
expect(cop.offenses.size).to eq(1)
end

it 'does not register an offense for a space between -> and (' do
inspect_source(cop, 'a = -> (b, c) { b + c }')
expect(cop.offenses).to be_empty
end

it 'does not register an offense for multi-line lambdas' do
inspect_source(cop, ['l = lambda do |a, b|',
' tmp = a * 7',
' tmp * b / 50',
'end'])
expect(cop.offenses).to be_empty
end

it 'does not register an offense for no space between -> and {' do
inspect_source(cop, 'a = ->{ b + c }')
expect(cop.offenses).to be_empty
end

it 'registers an offense for no space in the inner nested lambda' do
inspect_source(cop, 'a = -> (b = ->(c) {}, d) { b + d }')
expect(cop.offenses.size).to eq(1)
end

it 'registers an offense for no space in the outer nested lambda' do
inspect_source(cop, 'a = ->(b = -> (c) {}, d) { b + d }')
expect(cop.offenses.size).to eq(1)
end

it 'registers an offense for no space in both lambdas when nested' do
inspect_source(cop, 'a = ->(b = ->(c) {}, d) { b + d }')
expect(cop.offenses.size).to eq(2)
end

it 'autocorrects an offense for no space between -> and (' do
code = 'a = ->(b, c) { b + c }'
expected = 'a = -> (b, c) { b + c }'
expect(autocorrect_source(cop, code)).to eq(expected)
end

it 'autocorrects an offense for no space in the inner nested lambda' do
code = 'a = -> (b = ->(c) {}, d) { b + d }'
expected = 'a = -> (b = -> (c) {}, d) { b + d }'
expect(autocorrect_source(cop, code)).to eq(expected)
end

it 'autocorrects an offense for no space in the outer nested lambda' do
code = 'a = ->(b = -> (c) {}, d) { b + d }'
expected = 'a = -> (b = -> (c) {}, d) { b + d }'
expect(autocorrect_source(cop, code)).to eq(expected)
end

it 'autocorrects an offense for no space in both lambdas when nested' do
code = 'a = ->(b = ->(c) {}, d) { b + d }'
expected = 'a = -> (b = -> (c) {}, d) { b + d }'
expect(autocorrect_source(cop, code)).to eq(expected)
end
end

context 'when configured to enforce no space' do
let(:cop_config) { { 'EnforcedStyle' => 'require_no_space' } }

it 'registers an offense for a space between -> and (' do
inspect_source(cop, 'a = -> (b, c) { b + c }')
expect(cop.offenses.size).to eq(1)
end

it 'does not register an offense for no space between -> and (' do
inspect_source(cop, 'a = ->(b, c) { b + c }')
expect(cop.offenses).to be_empty
end

it 'does not register an offense for multi-line lambdas' do
inspect_source(cop, ['l = lambda do |a, b|',
' tmp = a * 7',
' tmp * b / 50',
'end'])
expect(cop.offenses).to be_empty
end

it 'does not register an offense for a space between -> and {' do
inspect_source(cop, 'a = -> { b + c }')
expect(cop.offenses).to be_empty
end

it 'registers an offense for spaces between -> and (' do
inspect_source(cop, 'a = -> (b, c) { b + c }')
expect(cop.offenses.size).to eq(1)
end

it 'registers an offense for a space in the inner nested lambda' do
inspect_source(cop, 'a = ->(b = -> (c) {}, d) { b + d }')
expect(cop.offenses.size).to eq(1)
end

it 'registers an offense for a space in the outer nested lambda' do
inspect_source(cop, 'a = -> (b = ->(c) {}, d) { b + d }')
expect(cop.offenses.size).to eq(1)
end

it 'registers two offenses for a space in both lambdas when nested' do
inspect_source(cop, 'a = -> (b = -> (c) {}, d) { b + d }')
expect(cop.offenses.size).to eq(2)
end

it 'autocorrects an offense for a space between -> and (' do
code = 'a = -> (b, c) { b + c }'
expected = 'a = ->(b, c) { b + c }'
expect(autocorrect_source(cop, code)).to eq(expected)
end

it 'autocorrects an offense for spaces between -> and (' do
code = 'a = -> (b, c) { b + c }'
expected = 'a = ->(b, c) { b + c }'
expect(autocorrect_source(cop, code)).to eq(expected)
end

it 'autocorrects an offense for a space in the inner nested lambda' do
code = 'a = ->(b = -> (c) {}, d) { b + d }'
expected = 'a = ->(b = ->(c) {}, d) { b + d }'
expect(autocorrect_source(cop, code)).to eq(expected)
end

it 'autocorrects an offense for a space in the outer nested lambda' do
code = 'a = -> (b = ->(c) {}, d) { b + d }'
expected = 'a = ->(b = ->(c) {}, d) { b + d }'
expect(autocorrect_source(cop, code)).to eq(expected)
end

it 'autocorrects two offenses for a space in both lambdas when nested' do
code = 'a = -> (b = -> (c) {}, d) { b + d }'
expected = 'a = ->(b = ->(c) {}, d) { b + d }'
expect(autocorrect_source(cop, code)).to eq(expected)
end
end
end