@@ -100,6 +100,8 @@ def visit_attr(node)
100100 location : "#{ @file } :#{ node . location . start_line } " ,
101101 )
102102
103+ known_annotations = nil #: Array[Spoom::RBS::Annotation]?
104+
103105 signatures . each do |signature |
104106 attr_type = ::RBS ::Parser . parse_type ( signature . string )
105107 sig = RBI ::Sig . new
@@ -118,17 +120,21 @@ def visit_attr(node)
118120
119121 sig . return_type = @type_translator . translate ( attr_type )
120122
121- apply_member_annotations ( comments . method_annotations , sig )
123+ known_annotations = apply_member_annotations ( comments . method_annotations , sig )
122124
123125 @rewriter << Source ::Replace . new (
124126 signature . location . start_offset ,
125127 signature . location . end_offset ,
126- sig . string ( max_line_length : @max_line_length ) ,
128+ pad_out_line_count ( of : sig . string ( max_line_length : @max_line_length ) , to_height_of : signature ) ,
127129 )
128130 rescue ::RBS ::ParsingError , ::RBI ::Error
129131 # Ignore signatures with errors
130132 next
131133 end
134+
135+ if known_annotations
136+ rewrite_member_annotations ( comments . method_annotations , known : known_annotations )
137+ end
132138 end
133139
134140 #: (Prism::DefNode, Spoom::RBS::Comments) -> void
@@ -146,6 +152,8 @@ def rewrite_def(def_node, comments)
146152 builder . visit ( def_node )
147153 rbi_node = builder . tree . nodes . first #: as RBI::Method
148154
155+ known_annotations = nil #: Array[Spoom::RBS::Annotation]?
156+
149157 signatures . each do |signature |
150158 begin
151159 method_type = ::RBS ::Parser . parse_method_type ( signature . string )
@@ -163,7 +171,7 @@ def rewrite_def(def_node, comments)
163171
164172 sig = translator . result
165173
166- apply_member_annotations ( comments . method_annotations , sig )
174+ known_annotations = apply_member_annotations ( comments . method_annotations , sig )
167175
168176 # Sorbet runtime doesn't support `sig` on `method_added` or
169177 # `singleton_method_added`, so we always use `without_runtime` for them.
@@ -174,9 +182,13 @@ def rewrite_def(def_node, comments)
174182 @rewriter << Source ::Replace . new (
175183 signature . location . start_offset ,
176184 signature . location . end_offset ,
177- sig . string ( max_line_length : @max_line_length ) ,
185+ pad_out_line_count ( of : sig . string ( max_line_length : @max_line_length ) , to_height_of : signature ) ,
178186 )
179187 end
188+
189+ if known_annotations
190+ rewrite_member_annotations ( comments . method_annotations , known : known_annotations )
191+ end
180192 end
181193
182194 #: (Array[Spoom::RBS::Signature], method_name: String, location: String) -> Array[Spoom::RBS::Signature]
@@ -187,27 +199,26 @@ def apply_overloads_strategy(signatures, method_name:, location:)
187199 when :translate_all
188200 signatures
189201 when :translate_last
190- kept = signatures . last #: as Spoom::RBS::Signature
191202 others = signatures [ 0 ...-1 ] #: as !nil
203+ others . each { |signature | rewrite_discarded_overload ( signature ) }
192204
193- # Delete all the signatures we didn't keep
194- others . each do |signature |
195- from = adjust_to_line_start ( signature . location . start_offset )
196- to = adjust_to_line_end ( signature . location . end_offset )
197- @rewriter << Source ::Delete . new ( from , to )
198- end
205+ kept = signatures . last #: as Spoom::RBS::Signature
199206 [ kept ]
200207 else # :raise
201208 raise Error , "Method `#{ method_name } ` at #{ location } has multiple overloaded signatures"
202209 end
203210 end
204211
212+ # Called for every overloaded method sig that we discard because it wasn't the last one.
213+ # @abstract
214+ #: (Spoom::RBS::Signature) -> void
215+ def rewrite_discarded_overload ( signature ) = raise
216+
205217 #: (PrismTypes::anyScopeNode) -> void
206218 def apply_class_annotations ( node )
207219 comments = node_rbs_comments ( node )
208220 return if comments . empty?
209221
210- indent = " " * ( node . location . start_column + 2 )
211222 insert_pos = case node
212223 when Prism ::ClassNode
213224 ( node . superclass || node . constant_path ) . location . end_offset
@@ -217,16 +228,14 @@ def apply_class_annotations(node)
217228 node . expression . location . end_offset
218229 end
219230
220- class_annotations = comments . class_annotations
221- if class_annotations . any?
231+ # Only translate (and `extend T::Helpers`) when there's at least one *known* class
232+ # annotation. A node with only unknown annotations (e.g. `@private`) is left untouched.
233+ if comments . class_annotations . any?
222234 unless already_extends? ( node , /^(::)?T::Helpers$/ )
223- @rewriter << Source :: Insert . new ( insert_pos , " \n #{ indent } extend T::Helpers\n " )
235+ extend_with ( " T::Helpers" , into : node , at : insert_pos )
224236 end
225237
226- class_annotations . reverse_each do |annotation |
227- from = adjust_to_line_start ( annotation . location . start_offset )
228- to = adjust_to_line_end ( annotation . location . end_offset )
229-
238+ comments . annotations . reverse_each do |annotation |
230239 content = case annotation . string
231240 when "@abstract"
232241 "abstract!"
@@ -241,15 +250,13 @@ def apply_class_annotations(node)
241250 rbs_type = @type_translator . translate ( srb_type )
242251 "requires_ancestor { #{ rbs_type } }"
243252 else
253+ apply_class_annotation ( annotation , parent_node : node , insert_pos :, sorbet_replacement : nil )
244254 next
245255 end
246256
247- @rewriter << Source ::Delete . new ( from , to )
248-
249- newline = node . body . nil? ? "" : "\n "
250- @rewriter << Source ::Insert . new ( insert_pos , "\n #{ indent } #{ content } #{ newline } " )
257+ apply_class_annotation ( annotation , parent_node : node , insert_pos :, sorbet_replacement : content )
251258 rescue ::RBS ::ParsingError , ::RBI ::Error
252- # Ignore annotations with errors
259+ apply_class_annotation ( annotation , parent_node : node , insert_pos : , sorbet_replacement : nil )
253260 next
254261 end
255262 end
@@ -261,14 +268,11 @@ def apply_class_annotations(node)
261268 next unless signature . string . start_with? ( "[" )
262269
263270 type_params = ::RBS ::Parser . parse_type_params ( signature . string )
271+ rewrite_type_params_signature ( signature , type_params :)
264272 next if type_params . empty?
265273
266- from = adjust_to_line_start ( signature . location . start_offset )
267- to = adjust_to_line_end ( signature . location . end_offset )
268- @rewriter << Source ::Delete . new ( from , to )
269-
270274 unless already_extends? ( node , /^(::)?T::Generic$/ )
271- @rewriter << Source :: Insert . new ( insert_pos , " \n #{ indent } extend T::Generic\n " )
275+ extend_with ( " T::Generic" , into : node , at : insert_pos )
272276 end
273277
274278 type_params . each do |type_param |
@@ -293,8 +297,7 @@ def apply_class_annotations(node)
293297 end
294298 end
295299
296- newline = node . body . nil? ? "" : "\n "
297- @rewriter << Source ::Insert . new ( insert_pos , "\n #{ indent } #{ type_member } #{ newline } " )
300+ insert_type_member ( type_member , parent_node : node , insert_pos :)
298301 rescue ::RBS ::ParsingError , ::RBI ::Error
299302 # Ignore signatures with errors
300303 next
@@ -303,8 +306,31 @@ def apply_class_annotations(node)
303306 end
304307 end
305308
306- #: (Array[Spoom::RBS::Annotation], RBI::Sig) -> void
309+ # @param is_known: true if this is an RBS annotation that we recognize
310+ # false for some other `@`-prefixed thing, like a documentation `@param` tag.
311+ # @abstract
312+ #: (
313+ #| Spoom::RBS::Annotation,
314+ #| parent_node: PrismTypes::anyScopeNode,
315+ #| insert_pos: Integer,
316+ #| sorbet_replacement: String?
317+ #| ) -> void
318+ def apply_class_annotation ( annotation , parent_node :, insert_pos :, sorbet_replacement :) = raise
319+
320+ # Rewrites the `#: [...]` type params comment (e.g. delete it, or mark it as translated).
321+ # @abstract
322+ #: (Spoom::RBS::Signature, type_params: Array[::RBS::AST::TypeParam]) -> void
323+ def rewrite_type_params_signature ( signature , type_params :) = raise
324+
325+ # Inserts a single `type_member` declaration into the class/module body.
326+ # @abstract
327+ #: (String type_member, parent_node: PrismTypes::anyScopeNode, insert_pos: Integer) -> void
328+ def insert_type_member ( type_member , parent_node :, insert_pos :) = raise
329+
330+ #: (Array[Spoom::RBS::Annotation], RBI::Sig) -> Array[Spoom::RBS::Annotation]
307331 def apply_member_annotations ( annotations , sig )
332+ known = [ ] #: Array[Spoom::RBS::Annotation]
333+
308334 annotations . each do |annotation |
309335 case annotation . string
310336 when "@abstract"
@@ -323,10 +349,37 @@ def apply_member_annotations(annotations, sig)
323349 sig . is_overridable = true
324350 when "@without_runtime"
325351 sig . without_runtime = true
352+ else
353+ next
326354 end
355+
356+ known << annotation
327357 end
358+
359+ known
328360 end
329361
362+ # Rewrites the member annotation comments in the source. Called once per method,
363+ # regardless of how many overloaded signatures share the annotations, to avoid
364+ # emitting duplicate markers.
365+ #
366+ #: (Array[Spoom::RBS::Annotation], known: Array[Spoom::RBS::Annotation]) -> void
367+ def rewrite_member_annotations ( annotations , known :)
368+ annotations . each do |annotation |
369+ rewrite_annotation ( annotation , is_known : known . include? ( annotation ) )
370+ end
371+ end
372+
373+ # @param is_known: true if this is an RBS annotation that we recognize
374+ # false for some other `@`-prefixed thing, like a documentation `@param` tag.
375+ # @overridable
376+ #: (Spoom::RBS::Annotation, is_known: bool) -> void
377+ def rewrite_annotation ( annotation , is_known :) = nil # no-op
378+
379+ # @abstract
380+ #: (String mixin_name, into: PrismTypes::anyScopeNode, at: Integer) -> void
381+ def extend_with ( mixin_name , into :, at :) = raise
382+
330383 #: (PrismTypes::anyScopeNode, Regexp) -> bool
331384 def already_extends? ( node , constant_regex )
332385 node . child_nodes . any? do |c |
@@ -408,12 +461,22 @@ def apply_type_aliases(comments)
408461
409462 @rewriter << Source ::Delete . new ( from , to )
410463 content = "#{ indent } #{ alias_name } = T.type_alias { #{ sorbet_type . to_rbi } }\n "
464+ content = pad_out_line_count ( of : content , to_height_of : type_alias )
411465 @rewriter << Source ::Insert . new ( insert_pos , content )
412466 rescue ::RBS ::ParsingError , ::RBI ::Error
413467 # Ignore type aliases with errors
414468 next
415469 end
416470 end
471+
472+ # @overridable
473+ #: (of: String, to_height_of: Spoom::RBS::Comment) -> String
474+ def pad_out_line_count ( of :, to_height_of :)
475+ replacement = of
476+
477+ # no-op implementation
478+ replacement
479+ end
417480 end
418481 end
419482 end
0 commit comments