Skip to content

Commit 79fbc5b

Browse files
burkeclaude
andcommitted
Fix spoom srb assertions translate to preserve trailing comments in correct order
When translating T.let with trailing comments, the RBS annotation now appears before the comment so Sorbet can properly recognize the type: a = T.let(3, Integer) # comment now translates to: a = 3 #: Integer # comment instead of: a = 3 # comment #: Integer 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent b8e15b5 commit 79fbc5b

2 files changed

Lines changed: 64 additions & 6 deletions

File tree

lib/spoom/sorbet/translate/sorbet_assertions_to_rbs_comments.rb

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,18 +89,26 @@ def maybe_translate_assertion(node)
8989
return false unless translatable_annotation?(node)
9090
return false unless at_end_of_line?(node)
9191

92+
trailing_comment, comment_end_offset = extract_trailing_comment(node)
93+
# If extract_trailing_comment returns nil when there's an RBS annotation, don't translate
94+
return false if trailing_comment.nil? && has_rbs_annotation?(node)
95+
9296
value = T.must(node.arguments&.arguments&.first)
9397
rbs_annotation = build_rbs_annotation(node)
9498

9599
start_offset = node.location.start_offset
96-
end_offset = node.location.end_offset
100+
# If there's a trailing comment, replace up to the end of the comment
101+
# Otherwise, replace up to the end of the node
102+
end_offset = comment_end_offset || node.location.end_offset
97103

98-
@rewriter << if node.name == :bind
99-
Source::Replace.new(start_offset, end_offset - 1, rbs_annotation)
104+
replacement = if node.name == :bind
105+
"#{rbs_annotation}#{trailing_comment}"
100106
else
101-
Source::Replace.new(start_offset, end_offset - 1, "#{dedent_value(node, value)} #{rbs_annotation}")
107+
"#{dedent_value(node, value)} #{rbs_annotation}#{trailing_comment}"
102108
end
103109

110+
@rewriter << Source::Replace.new(start_offset, end_offset - 1, replacement)
111+
104112
true
105113
end
106114

@@ -166,7 +174,40 @@ def translatable_annotation?(node)
166174
def at_end_of_line?(node)
167175
end_offset = node.location.end_offset
168176
end_offset += 1 while (@ruby_bytes[end_offset] == " ".ord) && (end_offset < @ruby_bytes.size)
169-
@ruby_bytes[end_offset] == LINE_BREAK
177+
# Check if we're at a newline OR at the start of a comment
178+
@ruby_bytes[end_offset] == LINE_BREAK || @ruby_bytes[end_offset] == "#".ord
179+
end
180+
181+
# Check if the node has an RBS annotation comment (#:) after it
182+
#: (Prism::Node) -> bool
183+
def has_rbs_annotation?(node)
184+
end_offset = node.location.end_offset
185+
# Skip spaces
186+
end_offset += 1 while (@ruby_bytes[end_offset] == " ".ord) && (end_offset < @ruby_bytes.size)
187+
# Check if there's a comment starting with #:
188+
@ruby_bytes[end_offset] == "#".ord && @ruby_bytes[end_offset + 1] == ":".ord
189+
end
190+
191+
# Extract any trailing comment after the node
192+
# Returns [comment_text, comment_end_offset] or [nil, nil] if no comment or RBS annotation
193+
#: (Prism::Node) -> [String?, Integer?]
194+
def extract_trailing_comment(node)
195+
end_offset = node.location.end_offset
196+
# Skip spaces
197+
end_offset += 1 while (@ruby_bytes[end_offset] == " ".ord) && (end_offset < @ruby_bytes.size)
198+
# Check if there's a comment
199+
return [nil, nil] unless @ruby_bytes[end_offset] == "#".ord
200+
201+
# If it's an RBS annotation comment (#:), return nil to prevent translation
202+
return [nil, nil] if @ruby_bytes[end_offset + 1] == ":".ord
203+
204+
# Find the end of the comment (end of line)
205+
comment_start = end_offset
206+
end_offset += 1 while @ruby_bytes[end_offset] != LINE_BREAK && end_offset < @ruby_bytes.size
207+
208+
# Extract the comment including the leading space and return the end offset
209+
range = @ruby_bytes[comment_start...end_offset] #: as !nil
210+
[" #{range.pack('C*')}", end_offset]
170211
end
171212

172213
#: (Prism::Node, Prism::Node) -> String

test/spoom/sorbet/translate/sorbet_assertions_to_rbs_comments_test.rb

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -376,7 +376,10 @@ def test_does_not_translate_assertions_already_with_comments
376376
a = T.let(42, Integer) # as Integer
377377
RB
378378

379-
assert_equal(rb, rbi_to_rbs(rb))
379+
# Now translates and preserves the comment
380+
assert_equal(<<~RB, rbi_to_rbs(rb))
381+
a = 42 #: Integer # as Integer
382+
RB
380383
end
381384

382385
def test_translate_cast_in_oneliners
@@ -539,6 +542,20 @@ def test_translate_options
539542
)
540543
end
541544

545+
def test_translate_with_trailing_comments
546+
rb = <<~RB
547+
a = T.let(3, Integer) # comment
548+
b = T.cast(x, String) # another comment
549+
T.must(y) # must comment
550+
RB
551+
552+
assert_equal(<<~RB, rbi_to_rbs(rb))
553+
a = 3 #: Integer # comment
554+
b = x #: as String # another comment
555+
y #: as !nil # must comment
556+
RB
557+
end
558+
542559
private
543560

544561
#: (

0 commit comments

Comments
 (0)