Skip to content
Draft
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
5 changes: 5 additions & 0 deletions lib/spoom/cli/srb/sigs.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ class Sigs < Thor
option :translate_generics, type: :boolean, desc: "Translate generics", default: false
option :translate_helpers, type: :boolean, desc: "Translate helpers", default: false
option :translate_abstract_methods, type: :boolean, desc: "Translate abstract methods", default: false
option :erase_generic_types,
type: :boolean,
desc: "Drop generic types when translating from RBS to RBI",
default: false
def translate(*paths)
from = options[:from]
to = options[:to]
Expand Down Expand Up @@ -65,6 +69,7 @@ def translate(*paths)
contents,
file: file,
max_line_length: max_line_length,
erase_generic_types: options[:erase_generic_types],
)
end
end
Expand Down
6 changes: 4 additions & 2 deletions lib/spoom/sorbet/translate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,15 @@ def sorbet_sigs_to_rbs_comments(

# Converts all the RBS comments in the given Ruby code to `sig` nodes.
# It also handles type members and class annotations.
#: (String ruby_contents, file: String, ?max_line_length: Integer?, ?overloads_strategy: Symbol) -> String
def rbs_comments_to_sorbet_sigs(ruby_contents, file:, max_line_length: nil, overloads_strategy: :translate_all)
#: (String ruby_contents, file: String, ?max_line_length: Integer?, ?overloads_strategy: Symbol, ?erase_generic_types: bool) -> String
def rbs_comments_to_sorbet_sigs(ruby_contents, file:, max_line_length: nil, overloads_strategy: :translate_all,
erase_generic_types: false)
RBSCommentsToSorbetSigs.rewrite_if_needed(
ruby_contents,
file: file,
max_line_length: max_line_length,
overloads_strategy: overloads_strategy,
erase_generic_types: erase_generic_types,
)
end

Expand Down
39 changes: 27 additions & 12 deletions lib/spoom/sorbet/translate/rbs_comments_to_sorbet_sigs.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,18 @@ def contains_rbs_syntax?(source)
Sigils.contains_valid_sigil?(source) && source.match?(RBS_REWRITE_PATTERN)
end

#: (String ruby_contents, file: String, ?max_line_length: Integer?, ?overloads_strategy: Symbol) -> String
def rewrite_if_needed(ruby_contents, file:, max_line_length: nil, overloads_strategy: :translate_all)
#: (String ruby_contents, file: String, ?max_line_length: Integer?, ?overloads_strategy: Symbol, ?erase_generic_types: bool) -> String
def rewrite_if_needed(ruby_contents, file:, max_line_length: nil, overloads_strategy: :translate_all,
erase_generic_types: false)
return ruby_contents unless contains_rbs_syntax?(ruby_contents)

new(ruby_contents, file:, max_line_length:, overloads_strategy:).rewrite
new(ruby_contents, file:, max_line_length:, overloads_strategy:, erase_generic_types:).rewrite
end
end

#: (String, file: String, ?max_line_length: Integer?, ?overloads_strategy: Symbol) -> void
def initialize(ruby_contents, file:, max_line_length: nil, overloads_strategy: :translate_all)
#: (String, file: String, ?max_line_length: Integer?, ?overloads_strategy: Symbol, ?erase_generic_types: bool) -> void
def initialize(ruby_contents, file:, max_line_length: nil, overloads_strategy: :translate_all,
erase_generic_types: false)
super(ruby_contents, file: file)

unless ALLOWED_OVERLOAD_STRATEGIES.include?(overloads_strategy)
Expand All @@ -47,6 +49,8 @@ def initialize(ruby_contents, file:, max_line_length: nil, overloads_strategy: :

@max_line_length = max_line_length
@overloads_strategy = overloads_strategy
@erase_generic_types = erase_generic_types
@type_translator = RBI::RBS::TypeTranslator.new(erase_generic_types:) #: RBI::RBS::TypeTranslator
end

# @override
Expand Down Expand Up @@ -133,11 +137,11 @@ def visit_attr(node)
name = node.arguments&.arguments&.first #: as Prism::SymbolNode
sig.params << RBI::SigParam.new(
name.slice[1..-1], #: as String
RBI::RBS::TypeTranslator.translate(attr_type),
@type_translator.translate(attr_type),
)
end

sig.return_type = RBI::RBS::TypeTranslator.translate(attr_type)
sig.return_type = @type_translator.translate(attr_type)

apply_member_annotations(comments.method_annotations, sig)

Expand Down Expand Up @@ -174,7 +178,7 @@ def rewrite_def(def_node, comments)
next
end

translator = RBI::RBS::MethodTypeTranslator.new(rbi_node)
translator = RBI::RBS::MethodTypeTranslator.new(rbi_node, erase_generic_types: @erase_generic_types)

begin
translator.visit(method_type)
Expand Down Expand Up @@ -259,7 +263,7 @@ def apply_class_annotations(node)
"final!"
when /^@requires_ancestor: /
srb_type = ::RBS::Parser.parse_type(annotation.string.delete_prefix("@requires_ancestor: "))
rbs_type = RBI::RBS::TypeTranslator.translate(srb_type)
rbs_type = @type_translator.translate(srb_type)
"requires_ancestor { #{rbs_type} }"
else
next
Expand Down Expand Up @@ -288,6 +292,17 @@ def apply_class_annotations(node)
to = adjust_to_line_end(signature.location.end_offset)
@rewriter << Source::Delete.new(from, to)

if @erase_generic_types
type_params.each do |type_param|
@rewriter << Source::Insert.new(
insert_pos,
"\n#{indent}#{type_param.name} = T.type_alias { T.anything }\n",
)
end

next
end

unless already_extends?(node, /^(::)?T::Generic$/)
@rewriter << Source::Insert.new(insert_pos, "\n#{indent}extend T::Generic\n")
end
Expand All @@ -304,12 +319,12 @@ def apply_class_annotations(node)

if type_param.upper_bound || type_param.default_type
if type_param.upper_bound
rbs_type = RBI::RBS::TypeTranslator.translate(type_param.upper_bound)
rbs_type = @type_translator.translate(type_param.upper_bound)
type_member = "#{type_member} {{ upper: #{rbs_type} }}"
end

if type_param.default_type
rbs_type = RBI::RBS::TypeTranslator.translate(type_param.default_type)
rbs_type = @type_translator.translate(type_param.default_type)
type_member = "#{type_member} {{ fixed: #{rbs_type} }}"
end
end
Expand Down Expand Up @@ -417,7 +432,7 @@ def apply_type_aliases(comments)
next unless decls.size == 1 && decls.first.is_a?(::RBS::AST::Declarations::TypeAlias)

rbs_type = decls.first
sorbet_type = RBI::RBS::TypeTranslator.translate(rbs_type.type)
sorbet_type = @type_translator.translate(rbs_type.type)

alias_name = ::RBS::TypeName.new(
namespace: rbs_type.name.namespace,
Expand Down
40 changes: 40 additions & 0 deletions test/spoom/cli/srb/sigs_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,46 @@ def foo(a, b = 42, *c, d:, e: 42, **f); end
RB
end

def test_translate_rbs_to_rbi_with_erase_generic_types
@project.write!("file.rb", <<~RB)
# typed: true

#: [E]
class Box
#: -> Array[E]
def values
[]
end

#: (E) -> void
def push(value)
end
end
RB

result = @project.spoom("srb sigs translate --from rbs --to rbi --no-color --erase-generic-types")

assert_empty(result.err)
assert(result.status)

assert_equal(<<~RB, @project.read("file.rb"))
# typed: true

class Box
E = T.type_alias { T.anything }

sig { returns(Array) }
def values
[]
end

sig { params(value: E).void }
def push(value)
end
end
RB
end

def test_translate_includes_rbi_files
@project.write!("file.rb", <<~RB)
sig { void }
Expand Down
Loading
Loading