Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,12 @@ def maybe_translate_assertion(node)
# Otherwise, replace up to the end of the node
end_offset = comment_end_offset || node.location.end_offset

heredoc_body = heredoc_body_within_range(value, end_offset)

replacement = if node.name == :bind
"#{rbs_annotation}#{trailing_comment}"
else
"#{dedent_value(node, value)} #{rbs_annotation}#{trailing_comment}"
"#{dedent_value(node, value)} #{rbs_annotation}#{trailing_comment}#{heredoc_body}"
end

@rewriter << Source::Replace.new(start_offset, end_offset - 1, replacement)
Expand Down Expand Up @@ -212,6 +214,47 @@ def extract_trailing_comment(node)
[" #{range.pack("C*")}", end_offset]
end

#: (Prism::Node, Integer) -> String?
def heredoc_body_within_range(node, replace_end_offset)
heredoc_end = find_heredoc_end_offset(node)
return unless heredoc_end
return if heredoc_end > replace_end_offset

value_end = node.location.end_offset
opener_line_end = value_end
opener_line_end += 1 while opener_line_end < @ruby_bytes.size && @ruby_bytes[opener_line_end] != LINE_BREAK

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use

def adjust_to_line_end(offset)

return if opener_line_end >= @ruby_bytes.size

body_bytes = @ruby_bytes[(opener_line_end + 1)...heredoc_end] #: as !nil
body = body_bytes.pack("C*")
body.chomp! if @ruby_bytes[replace_end_offset] == LINE_BREAK
"\n#{body}"
end

#: (Prism::Node) -> Integer?
def find_heredoc_end_offset(node)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we have something like

both = T.let(
  foo(<<~A, <<~B),
    first
  A
    second
  B
  String,
)

stopping at the first heredoc would end up dropping subsequent ones. I think we can support this case too.

Something like

#: (Prism::Node) -> Array[Integer]
def heredoc_end_offsets(node)
  offsets = [] #: Array[Integer]

  case node
  when Prism::StringNode, Prism::InterpolatedStringNode
    opening = node.opening_loc
    closing = node.closing_loc

    if opening && closing && opening.start_line != closing.start_line
      offsets << closing.end_offset
    end
  end

  node.each_child_node do |child|
    offsets.concat(heredoc_end_offsets(child))
  end

  offsets
end

Using each_child_node should take care of it for us. And then inside heredoc_body_within_range we can do

heredoc_end = heredoc_end_offsets(node)
  .select { |offset| offset <= replace_end_offset }
  .max
return unless heredoc_end

case node
when Prism::StringNode, Prism::InterpolatedStringNode
closing = node.closing_loc
opening = node.opening_loc
if closing && opening && opening.start_line != closing.start_line
return closing.end_offset
end
when Prism::CallNode
receiver = node.receiver
if receiver
result = find_heredoc_end_offset(receiver)
return result if result
end
node.arguments&.arguments&.each do |arg|
found = find_heredoc_end_offset(arg)
return found if found
end
end

nil
end

#: (Prism::Node, Prism::Node) -> String
def dedent_value(assign, value)
if value.location.start_line == assign.location.start_line
Expand Down
6 changes: 6 additions & 0 deletions rbi/spoom.rbi
Original file line number Diff line number Diff line change
Expand Up @@ -3040,9 +3040,15 @@ class Spoom::Sorbet::Translate::SorbetAssertionsToRBSComments < ::Spoom::Sorbet:
sig { params(node: ::Prism::Node).returns([T.nilable(::String), T.nilable(::Integer)]) }
def extract_trailing_comment(node); end

sig { params(node: ::Prism::Node).returns(T.nilable(::Integer)) }
def find_heredoc_end_offset(node); end

sig { params(node: ::Prism::Node).returns(T::Boolean) }
def has_rbs_annotation?(node); end

sig { params(node: ::Prism::Node, replace_end_offset: ::Integer).returns(T.nilable(::String)) }
def heredoc_body_within_range(node, replace_end_offset); end

sig { params(node: ::Prism::Node).returns(T::Boolean) }
def maybe_translate_assertion(node); end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,40 @@ def test_translate_assigns_ignore_heredoc_values
RB
end

def test_translate_assigns_multiline_tlet_with_heredoc_values
rb = <<~RB
MSG = T.let(
<<~MSG.gsub(/[[:space:]]+/, " ").strip,
Do not use foo directly. Use bar instead.
See this guide: https://example.com/docs
MSG
String,
)

QUERY = T.let(
<<~SQL.squish.freeze,
SELECT id, name
FROM users
WHERE active = true
SQL
String,
)
RB

assert_equal(<<~RB, rbi_to_rbs(rb))
MSG = <<~MSG.gsub(/[[:space:]]+/, " ").strip #: String
Do not use foo directly. Use bar instead.
See this guide: https://example.com/docs
MSG

QUERY = <<~SQL.squish.freeze #: String
SELECT id, name
FROM users
WHERE active = true
SQL
RB
end

def test_translate_assigns_does_not_match_bare_strings_has_heredoc
rb = <<~RB
a = T.let("<<~STR", String)
Expand Down
Loading