Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 34 additions & 10 deletions lib/rfmt/prism_bridge.rb
Original file line number Diff line number Diff line change
Expand Up @@ -125,16 +125,14 @@ def self.extract_location(node)
end
end

# Check child nodes for heredoc (e.g., LocalVariableWriteNode containing StringNode)
node.child_nodes.compact.each do |child|
next unless child.respond_to?(:closing_loc) && child.closing_loc

closing = child.closing_loc
next unless closing.end_offset > end_offset

end_offset = closing.end_offset
end_line = closing.end_line
end_column = closing.end_column
# Recursively check all descendant nodes for heredoc closing_loc
# Issue #74: handled direct children (e.g., LocalVariableWriteNode -> StringNode)
# Issue #86: handles deeper nesting (e.g., CallNode -> ArgumentsNode -> StringNode)
max_closing = find_max_closing_loc_recursive(node)
if max_closing && max_closing[:end_offset] > end_offset
end_offset = max_closing[:end_offset]
end_line = max_closing[:end_line]
end_column = max_closing[:end_column]
end

{
Expand All @@ -147,6 +145,32 @@ def self.extract_location(node)
}
end

# Recursively find the maximum closing_loc among all descendant nodes
# Returns nil if no closing_loc found, otherwise { end_offset:, end_line:, end_column: }
def self.find_max_closing_loc_recursive(node, depth: 0)
return nil if depth > 10

max_closing = nil

node.child_nodes.compact.each do |child|
if child.respond_to?(:closing_loc) && child.closing_loc
closing = child.closing_loc
if max_closing.nil? || closing.end_offset > max_closing[:end_offset]
max_closing = {
end_offset: closing.end_offset,
end_line: closing.end_line,
end_column: closing.end_column
}
end
end

child_max = find_max_closing_loc_recursive(child, depth: depth + 1)
max_closing = child_max if child_max && (max_closing.nil? || child_max[:end_offset] > max_closing[:end_offset])
end

max_closing
end

# Extract child nodes
def self.extract_children(node)
children = []
Expand Down
45 changes: 45 additions & 0 deletions spec/rfmt_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,51 @@ def bar = 42
end
end

describe 'heredoc in method call arguments (Issue #86)' do
it 'preserves multiple heredocs as method arguments' do
source = <<~RUBY
puts(<<~HEREDOC, <<~HEREDOC2)
This is a heredoc.
HEREDOC
This is another heredoc.
HEREDOC2
RUBY
result = Rfmt.format(source)

expect(result).to include('This is a heredoc.')
expect(result).to include('This is another heredoc.')
expect(result).to match(/^HEREDOC$/m)
expect(result).to match(/^HEREDOC2$/m)
expect(Prism.parse(result).errors).to be_empty
end

it 'preserves single heredoc as method argument' do
source = <<~RUBY
puts(<<~HEREDOC)
Single heredoc content.
HEREDOC
RUBY
result = Rfmt.format(source)

expect(result).to include('Single heredoc content.')
expect(result).to match(/^HEREDOC$/m)
expect(Prism.parse(result).errors).to be_empty
end

it 'preserves heredoc with method chain' do
source = <<~RUBY
foo.bar(<<~SQL)
SELECT * FROM users
SQL
RUBY
result = Rfmt.format(source)

expect(result).to include('SELECT * FROM users')
expect(result).to match(/^SQL$/m)
expect(Prism.parse(result).errors).to be_empty
end
end

describe 'inline then style preservation (Issue #75)' do
describe 'case...in with then' do
it 'preserves inline then style in pattern matching' do
Expand Down
Loading