Skip to content

Commit

Permalink
Merge pull request #10113 from dvandersluis/issue/10110
Browse files Browse the repository at this point in the history
[Fix #10110] Update `Layout/DotPosition` to be able to handle heredocs
  • Loading branch information
koic committed Sep 23, 2021
2 parents 78943de + f183c0f commit 5cde198
Show file tree
Hide file tree
Showing 3 changed files with 273 additions and 2 deletions.
1 change: 1 addition & 0 deletions changelog/fix_update_layoutdotposition_to_be_able_to.md
@@ -0,0 +1 @@
* [#10110](https://github.com/rubocop/rubocop/issues/10110): Update `Layout/DotPosition` to be able to handle heredocs. ([@dvandersluis][])
29 changes: 27 additions & 2 deletions lib/rubocop/cop/layout/dot_position.rb
Expand Up @@ -68,7 +68,7 @@ def message(dot)
end

def proper_dot_position?(node)
receiver_line = node.receiver.source_range.end.line
receiver_line = receiver_end_line(node.receiver)
selector_line = selector_range(node).line

# receiver and selector are on the same line
Expand All @@ -80,7 +80,10 @@ def proper_dot_position?(node)
# dot and the selector otherwise, we might break the code while
# "correcting" it (even if there is just an extra blank line, treat
# it the same)
return true if line_between?(selector_line, dot_line)
# Also, in the case of a heredoc, the receiver will end after the dot,
# because the heredoc body is on subsequent lines, so use the highest
# line to compare to.
return true if line_between?(selector_line, [receiver_line, dot_line].max)

correct_dot_position_style?(dot_line, selector_line)
end
Expand All @@ -96,6 +99,28 @@ def correct_dot_position_style?(dot_line, selector_line)
end
end

def receiver_end_line(node)
if (line = last_heredoc_line(node))
line
else
node.source_range.end.line
end
end

def last_heredoc_line(node)
if node.send_type?
node.arguments.select { |arg| heredoc?(arg) }
.map { |arg| arg.loc.heredoc_end.line }
.max
elsif heredoc?(node)
node.loc.heredoc_end.line
end
end

def heredoc?(node)
(node.str_type? || node.dstr_type?) && node.heredoc?
end

def selector_range(node)
# l.(1) has no selector, so we use the opening parenthesis instead
node.loc.selector || node.loc.begin
Expand Down
245 changes: 245 additions & 0 deletions spec/rubocop/cop/layout/dot_position_spec.rb
Expand Up @@ -98,6 +98,25 @@
end
end

context 'when a method spans multiple lines' do
it 'registers an offense' do
expect_offense(<<~RUBY)
something(
foo, bar
).
^ Place the . on the next line, together with the method name.
method_name
RUBY

expect_correction(<<~RUBY)
something(
foo, bar
)
.method_name
RUBY
end
end

context 'when using safe navigation operator' do
it 'registers an offense for correct + opposite' do
expect_offense(<<~RUBY)
Expand Down Expand Up @@ -145,6 +164,119 @@
RUBY
end
end

context 'when the receiver has a heredoc argument' do
context 'as the last argument' do
it 'registers an offense' do
expect_offense(<<~RUBY)
my_method.
^ Place the . on the next line, together with the method name.
something(<<~HERE).
^ Place the . on the next line, together with the method name.
something
HERE
somethingelse
RUBY

expect_correction(<<~RUBY)
my_method
.something(<<~HERE)
something
HERE
.somethingelse
RUBY
end
end

context 'with a dynamic heredoc' do
it 'registers an offense' do
expect_offense(<<~'RUBY')
my_method.
^ Place the . on the next line, together with the method name.
something(<<~HERE).
^ Place the . on the next line, together with the method name.
#{something}
HERE
somethingelse
RUBY

expect_correction(<<~'RUBY')
my_method
.something(<<~HERE)
#{something}
HERE
.somethingelse
RUBY
end
end

context 'as the first argument' do
it 'registers an offense' do
expect_offense(<<~'RUBY')
my_method.
^ Place the . on the next line, together with the method name.
something(<<~HERE, true).
^ Place the . on the next line, together with the method name.
#{something}
HERE
somethingelse
RUBY

expect_correction(<<~'RUBY')
my_method
.something(<<~HERE, true)
#{something}
HERE
.somethingelse
RUBY
end
end

context 'with multiple heredocs' do
it 'registers an offense' do
expect_offense(<<~'RUBY')
my_method.
^ Place the . on the next line, together with the method name.
something(<<~HERE, <<~THERE).
^ Place the . on the next line, together with the method name.
something
HERE
another thing
THERE
somethingelse
RUBY

expect_correction(<<~'RUBY')
my_method
.something(<<~HERE, <<~THERE)
something
HERE
another thing
THERE
.somethingelse
RUBY
end
end
end

context 'when the receiver is a heredoc' do
it 'registers an offense' do
expect_offense(<<~RUBY)
<<~HEREDOC.
^ Place the . on the next line, together with the method name.
something
HEREDOC
method_name
RUBY

expect_correction(<<~RUBY)
<<~HEREDOC
something
HEREDOC
.method_name
RUBY
end
end
end

context 'Trailing dots style' do
Expand Down Expand Up @@ -220,5 +352,118 @@
RUBY
end
end

context 'when the receiver has a heredoc argument' do
context 'as the last argument' do
it 'registers an offense' do
expect_offense(<<~RUBY)
my_method
.something(<<~HERE)
^ Place the . on the previous line, together with the method call receiver.
something
HERE
.somethingelse
^ Place the . on the previous line, together with the method call receiver.
RUBY

expect_correction(<<~RUBY)
my_method.
something(<<~HERE).
something
HERE
somethingelse
RUBY
end
end

context 'with a dynamic heredoc' do
it 'registers an offense' do
expect_offense(<<~'RUBY')
my_method
.something(<<~HERE)
^ Place the . on the previous line, together with the method call receiver.
#{something}
HERE
.somethingelse
^ Place the . on the previous line, together with the method call receiver.
RUBY

expect_correction(<<~'RUBY')
my_method.
something(<<~HERE).
#{something}
HERE
somethingelse
RUBY
end
end

context 'as the first argument' do
it 'registers an offense' do
expect_offense(<<~'RUBY')
my_method
.something(<<~HERE, true)
^ Place the . on the previous line, together with the method call receiver.
#{something}
HERE
.somethingelse
^ Place the . on the previous line, together with the method call receiver.
RUBY

expect_correction(<<~'RUBY')
my_method.
something(<<~HERE, true).
#{something}
HERE
somethingelse
RUBY
end
end

context 'with multiple heredocs' do
it 'registers an offense' do
expect_offense(<<~'RUBY')
my_method
.something(<<~HERE, <<~THERE)
^ Place the . on the previous line, together with the method call receiver.
something
HERE
another thing
THERE
.somethingelse
^ Place the . on the previous line, together with the method call receiver.
RUBY

expect_correction(<<~'RUBY')
my_method.
something(<<~HERE, <<~THERE).
something
HERE
another thing
THERE
somethingelse
RUBY
end
end
end

context 'when the receiver is a heredoc' do
it 'registers an offense' do
expect_offense(<<~RUBY)
<<~HEREDOC
something
HEREDOC
.method_name
^ Place the . on the previous line, together with the method call receiver.
RUBY

expect_correction(<<~RUBY)
<<~HEREDOC.
something
HEREDOC
method_name
RUBY
end
end
end
end

0 comments on commit 5cde198

Please sign in to comment.