Skip to content

Commit

Permalink
[Fix rubocop#1646] MultilineMethodCallInd. indented_relative_to_recei…
Browse files Browse the repository at this point in the history
…ver style

Add the style indented_relative_to_receiver in
Style/MultilineMethodCallIndentation for indentation of
method calls so that each new line of a call chain is
indented one step more than the ultimate receiver.
  • Loading branch information
jonas054 committed Jun 19, 2016
1 parent 30be9f1 commit a8f75aa
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 56 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
* [#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][])
* [#1646](https://github.com/bbatsov/rubocop/issues/1646): Add configuration style `indented_relative_to_receiver` for `Style/MultilineMethodCallIndentation`. ([@jonas054][])

### Bug fixes

Expand Down
1 change: 1 addition & 0 deletions config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -712,6 +712,7 @@ Style/MultilineMethodCallIndentation:
SupportedStyles:
- aligned
- indented
- indented_relative_to_receiver
# By default, the indentation width from Style/IndentationWidth is used
# But it can be overridden by setting this parameter
IndentationWidth: ~
Expand Down
3 changes: 2 additions & 1 deletion lib/rubocop/cop/mixin/multiline_expression_indentation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ def check(range, node, lhs, rhs)

def incorrect_style_detected(range, node, lhs, rhs)
add_offense(range, range, message(node, lhs, rhs)) do
if offending_range(node, lhs, rhs, alternative_style)
if supported_styles.size > 2 ||
offending_range(node, lhs, rhs, alternative_style)
unrecognized_style_detected
else
opposite_style_detected
Expand Down
48 changes: 42 additions & 6 deletions lib/rubocop/cop/style/multiline_method_call_indentation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,28 +57,47 @@ def offending_range(node, lhs, rhs, given_style)

@base = alignment_base(node, rhs, given_style)
correct_column = if @base
@base.column
@base.column + extra_indentation(given_style)
else
indentation(lhs) + correct_indentation(node)
end
@column_delta = correct_column - rhs.column
rhs if @column_delta != 0
end

def extra_indentation(given_style)
if given_style == :indented_relative_to_receiver
configured_indentation_width
else
0
end
end

def message(node, lhs, rhs)
what = operation_description(node, rhs)
if @base
"Align `#{rhs.source}` with `#{@base.source[/[^\n]*/]}` on " \
base_source = @base.source[/[^\n]*/]
if style == :indented_relative_to_receiver
"Indent `#{rhs.source}` #{configured_indentation_width} spaces " \
"more than `#{base_source}` on line #{@base.line}."
else
"Align `#{rhs.source}` with `#{base_source}` on " \
"line #{@base.line}."
end
else
used_indentation = rhs.column - indentation(lhs)
what = operation_description(node, rhs)
"Use #{correct_indentation(node)} (not #{used_indentation}) " \
"spaces for indenting #{what} spanning multiple lines."
end
end

def alignment_base(node, rhs, given_style)
return nil unless given_style == :aligned
return nil if given_style == :indented

if given_style == :indented_relative_to_receiver
receiver_base = receiver_alignment_base(node)
return receiver_base if receiver_base
end

semantic_alignment_base(node, rhs) ||
syntactic_alignment_base(node, rhs)
Expand Down Expand Up @@ -112,6 +131,24 @@ def syntactic_alignment_base(lhs, rhs)
# .c
def semantic_alignment_base(node, rhs)
return unless rhs.source.start_with?('.')

node = semantic_alignment_node(node)
return unless node

node.loc.dot.join(node.loc.selector)
end

# a
# .b
# .c
def receiver_alignment_base(node)
node = semantic_alignment_node(node)
return unless node

node.receiver.source_range
end

def semantic_alignment_node(node)
return if argument_in_method_call(node)

# descend to root of method chain
Expand All @@ -121,8 +158,7 @@ def semantic_alignment_base(node, rhs)
node = node.parent until node.loc.dot

return if node.loc.dot.line != node.loc.line

node.loc.dot.join(node.loc.selector)
node
end

def operation_rhs(node)
Expand Down
180 changes: 131 additions & 49 deletions spec/rubocop/cop/style/multiline_method_call_indentation_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,6 @@
expect(cop.messages).to be_empty
end

it 'registers an offense for no indentation of second line' do
inspect_source(cop,
['a.',
'b'])
expect(cop.messages).to eq(['Use 2 (not 0) spaces for indenting an ' \
'expression spanning multiple lines.'])
expect(cop.highlights).to eq(['b'])
end

it 'registers an offense for one space indentation of second line' do
inspect_source(cop,
['a',
Expand All @@ -80,17 +71,6 @@
expect(cop.highlights).to eq(['.('])
end

it 'registers an offense for 3 spaces indentation of second line' do
inspect_source(cop,
['a.',
' b',
'c.',
' d'])
expect(cop.messages).to eq(['Use 2 (not 3) spaces for indenting an ' \
'expression spanning multiple lines.'] * 2)
expect(cop.highlights).to eq(%w(b d))
end

it 'accepts no extra indentation of third line' do
inspect_source(cop,
[' a.',
Expand All @@ -99,28 +79,6 @@
expect(cop.offenses).to be_empty
end

it 'registers an offense for extra indentation of third line' do
inspect_source(cop,
[' a.',
' b.',
' c'])
expect(cop.messages).to eq(['Use 2 (not 4) spaces for indenting an ' \
'expression spanning multiple lines.'])
expect(cop.highlights).to eq(['c'])
end

it 'registers an offense for the emacs ruby-mode 1.1 indentation of an ' \
'expression in an array' do
inspect_source(cop,
[' [',
' a.',
' b',
' ]'])
expect(cop.messages).to eq(['Use 2 (not 0) spaces for indenting an ' \
'expression spanning multiple lines.'])
expect(cop.highlights).to eq(['b'])
end

it 'accepts indented methods in for body' do
inspect_source(cop,
['for x in a',
Expand Down Expand Up @@ -182,10 +140,59 @@
end
end

shared_examples 'common for aligned and indented' do
it 'registers an offense for no indentation of second line' do
inspect_source(cop,
['a.',
'b'])
expect(cop.messages)
.to eq(['Use 2 (not 0) spaces for indenting an expression spanning ' \
'multiple lines.'])
expect(cop.highlights).to eq(['b'])
end

it 'registers an offense for 3 spaces indentation of second line' do
inspect_source(cop,
['a.',
' b',
'c.',
' d'])
expect(cop.messages)
.to eq(['Use 2 (not 3) spaces for indenting an expression spanning ' \
'multiple lines.'] * 2)
expect(cop.highlights).to eq(%w(b d))
end

it 'registers an offense for extra indentation of third line' do
inspect_source(cop,
[' a.',
' b.',
' c'])
expect(cop.messages)
.to eq(['Use 2 (not 4) spaces for indenting an expression spanning ' \
'multiple lines.'])
expect(cop.highlights).to eq(['c'])
end

it 'registers an offense for the emacs ruby-mode 1.1 indentation of an ' \
'expression in an array' do
inspect_source(cop,
[' [',
' a.',
' b',
' ]'])
expect(cop.messages)
.to eq(['Use 2 (not 0) spaces for indenting an expression spanning ' \
'multiple lines.'])
expect(cop.highlights).to eq(['b'])
end
end

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

include_examples 'common'
include_examples 'common for aligned and indented'

# We call it semantic alignment when a dot is aligned with the first dot in
# a chain of calls, and that first dot does not begin its line.
Expand Down Expand Up @@ -344,7 +351,7 @@
'end'])
expect(cop.messages).to eq(['Align `b` with `a.` on line 1.'])
expect(cop.highlights).to eq(['b'])
expect(cop.config_to_allow_offenses).to eq('EnforcedStyle' => 'indented')
expect(cop.config_to_allow_offenses).to eq('Enabled' => false)
end

it 'falls back to indentation in complicated cases' do
Expand Down Expand Up @@ -486,11 +493,7 @@
end
end

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

include_examples 'common'

shared_examples 'both indented* styles' do
# We call it semantic alignment when a dot is aligned with the first dot in
# a chain of calls, and that first dot does not begin its line. But for the
# indented style, it doesn't come into play.
Expand All @@ -500,9 +503,88 @@
['User.a',
' .c',
' .b'])
expect(cop.messages).to be_empty
expect(cop.highlights).to be_empty
expect(cop.offenses).to be_empty
end
end
end

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

include_examples 'common'
include_examples 'both indented* styles'

it 'accepts correctly indented methods in operation' do
inspect_source(cop, [' 1 + a',
' .b',
' .c'])
expect(cop.highlights).to be_empty
expect(cop.offenses).to be_empty
end

it 'registers an offense for no indentation of second line' do
inspect_source(cop,
['a.',
'b'])
expect(cop.messages)
.to eq(['Indent `b` 2 spaces more than `a` on line 1.'])
expect(cop.highlights).to eq(['b'])
end

it 'registers an offense for 3 spaces indentation of second line' do
inspect_source(cop,
['a.',
' b',
'c.',
' d'])
expect(cop.messages)
.to eq(['Indent `b` 2 spaces more than `a` on line 1.',
'Indent `d` 2 spaces more than `c` on line 3.'])
expect(cop.highlights).to eq(%w(b d))
end

it 'registers an offense for extra indentation of third line' do
inspect_source(cop,
[' a.',
' b.',
' c'])
expect(cop.messages)
.to eq(['Indent `c` 2 spaces more than `a` on line 1.'])
expect(cop.highlights).to eq(['c'])
end

it 'registers an offense for the emacs ruby-mode 1.1 indentation of an ' \
'expression in an array' do
inspect_source(cop,
[' [',
' a.',
' b',
' ]'])
expect(cop.messages)
.to eq(['Indent `b` 2 spaces more than `a` on line 2.'])
expect(cop.highlights).to eq(['b'])
end

it 'auto-corrects' do
new_source = autocorrect_source(cop, ['until a.',
' b',
' something',
'end'])
expect(new_source).to eq(['until a.',
' b',
' something',
'end'].join("\n"))
end
end

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

include_examples 'common'
include_examples 'common for aligned and indented'
include_examples 'both indented* styles'

it 'accepts correctly indented methods in operation' do
inspect_source(cop, [' 1 + a',
Expand Down Expand Up @@ -541,7 +623,7 @@
'condition in an `if` statement spanning ' \
'multiple lines.'])
expect(cop.highlights).to eq(['b'])
expect(cop.config_to_allow_offenses).to eq('EnforcedStyle' => 'aligned')
expect(cop.config_to_allow_offenses).to eq('Enabled' => false)
end

it 'accepts normal indentation of method parameters' do
Expand Down

0 comments on commit a8f75aa

Please sign in to comment.