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 #10110] Update Layout/DotPosition to be able to handle heredocs #10113

Merged
merged 1 commit into from Sep 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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