Skip to content

Commit

Permalink
[Fix #3631] Add new Style/SpaceInLambdaLiteral cop
Browse files Browse the repository at this point in the history
  • Loading branch information
swcraig authored and bbatsov committed Oct 24, 2016
1 parent c7c9e30 commit a94bef0
Show file tree
Hide file tree
Showing 7 changed files with 287 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
* [#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][])
* [#3570](https://github.com/bbatsov/rubocop/issues/3570): Add new `MultilineIfModifier` cop to avoid usage of if/unless-modifiers on multiline statements. ([@tessi][])
* [#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 @@ -348,6 +348,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
87 changes: 87 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,87 @@
# frozen_string_literal: true

module RuboCop
module Cop
module Style
# This cop checks for spaces between -> and opening parameter
# brace 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 brace ' \
'in lambda literals'.freeze
MSG_REQUIRE_NO_SPACE = 'Do not use spaces between `->` and opening ' \
'brace 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

0 comments on commit a94bef0

Please sign in to comment.