Skip to content

Commit 68c0037

Browse files
authored
Merge pull request #803 from Shopify/comments-and-types
Fix spoom srb assertions translate to preserve trailing comments in correct order
2 parents b8e15b5 + 661fce0 commit 68c0037

File tree

4 files changed

+71
-7
lines changed

4 files changed

+71
-7
lines changed

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

rbi/spoom.rbi

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2950,6 +2950,12 @@ class Spoom::Sorbet::Translate::SorbetAssertionsToRBSComments < ::Spoom::Sorbet:
29502950
sig { params(assign: ::Prism::Node, value: ::Prism::Node).returns(::String) }
29512951
def dedent_value(assign, value); end
29522952

2953+
sig { params(node: ::Prism::Node).returns([T.nilable(::String), T.nilable(::Integer)]) }
2954+
def extract_trailing_comment(node); end
2955+
2956+
sig { params(node: ::Prism::Node).returns(T::Boolean) }
2957+
def has_rbs_annotation?(node); end
2958+
29532959
sig { params(node: ::Prism::Node).returns(T::Boolean) }
29542960
def maybe_translate_assertion(node); end
29552961

test/spoom/context/sorbet_test.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ def test_context_run_srb_from_bundle
4848
a.rb:3: Method `foo` does not exist on `T.class_of(<root>)` https://srb.help/7003
4949
3 |foo(42)
5050
^^^
51-
Errors: 1
5251
ERR
52+
assert_includes(res.err, "Errors: 1")
5353
refute(res.status)
5454

5555
context.write!("b.rb", <<~RB)

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)