From 1ab57d162a7b842fc8a671d88e8a9817d8a65da7 Mon Sep 17 00:00:00 2001 From: Alexandre Terrasa Date: Thu, 3 Oct 2024 14:59:42 -0400 Subject: [PATCH] Introduce CLI command to translate RBI sigs to RBS Signed-off-by: Alexandre Terrasa --- lib/spoom/cli/srb.rb | 4 ++ lib/spoom/cli/srb/sigs.rb | 59 +++++++++++++++++ test/spoom/cli/srb/sigs_test.rb | 111 ++++++++++++++++++++++++++++++++ 3 files changed, 174 insertions(+) create mode 100644 lib/spoom/cli/srb/sigs.rb create mode 100644 test/spoom/cli/srb/sigs_test.rb diff --git a/lib/spoom/cli/srb.rb b/lib/spoom/cli/srb.rb index 9c1f85a5..4767f390 100644 --- a/lib/spoom/cli/srb.rb +++ b/lib/spoom/cli/srb.rb @@ -4,6 +4,7 @@ require_relative "srb/bump" require_relative "srb/coverage" require_relative "srb/lsp" +require_relative "srb/sigs" require_relative "srb/tc" module Spoom @@ -19,6 +20,9 @@ class Main < Thor desc "bump", "Change Sorbet sigils from one strictness to another when no errors" subcommand "bump", Spoom::Cli::Srb::Bump + desc "sigs", "Translate signatures from/to RBI and RBS" + subcommand "sigs", Spoom::Cli::Srb::Sigs + desc "tc", "Run typechecking with advanced options" subcommand "tc", Spoom::Cli::Srb::Tc end diff --git a/lib/spoom/cli/srb/sigs.rb b/lib/spoom/cli/srb/sigs.rb new file mode 100644 index 00000000..8aa5d864 --- /dev/null +++ b/lib/spoom/cli/srb/sigs.rb @@ -0,0 +1,59 @@ +# typed: true +# frozen_string_literal: true + +require "spoom/sorbet/translate_sigs" + +module Spoom + module Cli + module Srb + class Sigs < Thor + include Helper + + desc "translate", "Translate signatures from/to RBI and RBS" + option :from, type: :string, aliases: :f, desc: "From format", enum: ["rbi"], default: "rbi" + option :to, type: :string, aliases: :t, desc: "To format", enum: ["rbs"], default: "rbs" + def translate(*paths) + from = options[:from] + to = options[:to] + paths << "." if paths.empty? + + files = paths.flat_map do |path| + if File.file?(path) + [path] + else + Dir.glob("#{path}/**/*.rb") + end + end + + if files.empty? + say_error("No files to translate") + exit(1) + end + + say("Translating signatures from `#{from}` to `#{to}` " \ + "in `#{files.size}` file#{files.size == 1 ? "" : "s"}...\n\n") + + translated_files = T.let(0, Integer) + + files.each do |file| + contents = File.read(file) + contents = Spoom::Sorbet::TranslateSigs.rbi_to_rbs(contents) + File.write(file, contents) + translated_files += 1 + rescue RBI::Error => error + say_warning("Can't parse #{file}: #{error.message}") + next + end + + say("Translated signatures in `#{translated_files}` file#{translated_files == 1 ? "" : "s"}.") + end + + no_commands do + def pluralize(word, count) + count == 1 ? word : "#{word}s" + end + end + end + end + end +end diff --git a/test/spoom/cli/srb/sigs_test.rb b/test/spoom/cli/srb/sigs_test.rb new file mode 100644 index 00000000..6c923bc5 --- /dev/null +++ b/test/spoom/cli/srb/sigs_test.rb @@ -0,0 +1,111 @@ +# typed: true +# frozen_string_literal: true + +require "test_with_project" + +module Spoom + module Cli + module Srb + class SigsTest < TestWithProject + def setup + @project.bundle_install! + end + + def test_only_supports_translation_from_rbi + result = @project.spoom("srb sigs translate --from rbs") + + assert_equal(<<~ERR, result.err) + Expected '--from' to be one of rbi; got rbs + ERR + refute(result.status) + end + + def test_only_supports_translation_to_rbs + result = @project.spoom("srb sigs translate --to rbi") + + assert_equal(<<~ERR, result.err) + Expected '--to' to be one of rbs; got rbi + ERR + refute(result.status) + end + + def test_no_files + result = @project.spoom("srb sigs translate --no-color") + + assert_equal(<<~OUT, result.err) + Error: No files to translate + OUT + refute(result.status) + end + + def test_only_selected_files + @project.write!("a/file1.rb", <<~RB) + sig { void } + def foo; end + RB + + @project.write!("a/file2.rb", <<~RB) + sig { void } + def foo; end + RB + + @project.write!("b/file1.rb", <<~RB) + sig { void } + def foo; end + RB + + result = @project.spoom("srb sigs translate --no-color a/file1.rb b/") + + assert_empty(result.err) + assert_equal(<<~OUT, result.out) + Translating signatures from `rbi` to `rbs` in `2` files... + + Translated signatures in `2` files. + OUT + assert(result.status) + + assert_equal(<<~RB, @project.read("a/file1.rb")) + #: -> void + def foo; end + RB + + assert_equal(<<~RB, @project.read("a/file2.rb")) + sig { void } + def foo; end + RB + + assert_equal(<<~RB, @project.read("b/file1.rb")) + #: -> void + def foo; end + RB + end + + def test_encoding_support + path = @project.absolute_path_to("file.rb") + File.write(path, <<~RB, encoding: Encoding::ISO8859_1) + # Some content with accentuated characters: éàèù + sig { void } + def foo; end + RB + + result = @project.spoom("srb sigs translate --no-color --encoding ISO-8859-1") + + assert_empty(result.err) + assert_equal(<<~OUT, result.out) + Translating signatures from `rbi` to `rbs` in `1` file... + + Translated signatures in `1` file. + OUT + assert(result.status) + + contents = File.read(path, encoding: Encoding::ISO8859_1) + assert_equal(<<~RB.encode(Encoding::ISO8859_1), contents) + # Some content with accentuated characters: éàèù + #: -> void + def foo; end + RB + end + end + end + end +end