From cb3342c156220bbbdd16b2c5f1809d782fab2183 Mon Sep 17 00:00:00 2001 From: Andy Waite <13400+andyw8@users.noreply.github.com> Date: Thu, 19 Dec 2024 15:53:20 -0500 Subject: [PATCH] wip --- lib/ruby_lsp/tapioca/addon.rb | 85 +------------ lib/ruby_lsp/tapioca/run_gem_rbi_check.rb | 115 ++++++++++++++++++ .../ruby_lsp/run_gem_rbi_check_spec.rb | 64 ++++++++++ 3 files changed, 180 insertions(+), 84 deletions(-) create mode 100644 lib/ruby_lsp/tapioca/run_gem_rbi_check.rb create mode 100644 spec/tapioca/ruby_lsp/run_gem_rbi_check_spec.rb diff --git a/lib/ruby_lsp/tapioca/addon.rb b/lib/ruby_lsp/tapioca/addon.rb index 99a4f5f6f..32fe83e11 100644 --- a/lib/ruby_lsp/tapioca/addon.rb +++ b/lib/ruby_lsp/tapioca/addon.rb @@ -13,7 +13,6 @@ end require "zlib" -require "ruby_lsp/tapioca/lockfile_diff_parser" module RubyLsp module Tapioca @@ -48,9 +47,7 @@ def activate(global_state, outgoing_queue) @outgoing_queue << Notification.window_log_message("Activating Tapioca add-on v#{version}") @rails_runner_client.register_server_addon(File.expand_path("server_addon.rb", __dir__)) - if git_repo? - lockfile_changed? ? generate_gem_rbis : cleanup_orphaned_rbis - end + RunGemRbiCheck.new.run rescue IncompatibleApiError # The requested version for the Rails add-on no longer matches. We need to upgrade and fix the breaking # changes @@ -133,86 +130,6 @@ def file_updated?(change, path) false end - - sig { returns(T::Boolean) } - def git_repo? - require "open3" - - _, status = Open3.capture2e("git rev-parse --is-inside-work-tree") - - T.must(status.success?) - end - - sig { returns(T::Boolean) } - def lockfile_changed? - fetch_lockfile_diff - !T.must(@lockfile_diff).empty? - end - - sig { returns(String) } - def fetch_lockfile_diff - @lockfile_diff = %x(git diff HEAD Gemfile.lock).strip - end - - sig { void } - def generate_gem_rbis - parser = LockfileDiffParser.new(@lockfile_diff) - - removed_gems = parser.removed_gems - added_or_modified_gems = parser.added_or_modified_gems - - if added_or_modified_gems.any? - # Resetting BUNDLE_GEMFILE to root folder to use the project's Gemfile instead of Ruby LSP's composed Gemfile - stdout, stderr, status = T.unsafe(Open3).capture3( - { "BUNDLE_GEMFILE" => "Gemfile" }, - "bin/tapioca", - "gem", - "--lsp_addon", - *added_or_modified_gems, - ) - T.must(@outgoing_queue) << if status.success? - Notification.window_log_message( - stdout, - type: Constant::MessageType::INFO, - ) - else - Notification.window_log_message( - stderr, - type: Constant::MessageType::ERROR, - ) - end - elsif removed_gems.any? - FileUtils.rm_f(Dir.glob("sorbet/rbi/gems/{#{removed_gems.join(",")}}@*.rbi")) - T.must(@outgoing_queue) << Notification.window_log_message( - "Removed RBIs for: #{removed_gems.join(", ")}", - type: Constant::MessageType::INFO, - ) - end - end - - sig { void } - def cleanup_orphaned_rbis - untracked_files = %x(git ls-files --others --exclude-standard sorbet/rbi/gems/).lines.map(&:strip) - deleted_files = %x(git ls-files --deleted sorbet/rbi/gems/).lines.map(&:strip) - - untracked_files.each do |file| - File.delete(file) - - T.must(@outgoing_queue) << Notification.window_log_message( - "Deleted untracked RBI: #{file}", - type: Constant::MessageType::INFO, - ) - end - - deleted_files.each do |file| - %x(git checkout -- #{file}) - - T.must(@outgoing_queue) << Notification.window_log_message( - "Restored deleted RBI: #{file}", - type: Constant::MessageType::INFO, - ) - end - end end end end diff --git a/lib/ruby_lsp/tapioca/run_gem_rbi_check.rb b/lib/ruby_lsp/tapioca/run_gem_rbi_check.rb new file mode 100644 index 000000000..bf63725bc --- /dev/null +++ b/lib/ruby_lsp/tapioca/run_gem_rbi_check.rb @@ -0,0 +1,115 @@ +# typed: true + +require "ruby_lsp/tapioca/lockfile_diff_parser" +require "ruby_lsp/tapioca/run_gem_rbi_check" + +module RubyLsp + module Tapioca + # TODO: this could probably have a better name + class RunGemRbiCheck + extend T::Sig + + def initialize + # We considered using logs to assert against things that other wise would be tricky to test directly, + # but it may not be needed. + @logs = [] + end + + attr_reader :logs + + def run(project_path = ".") + FileUtils.chdir(project_path) do + if git_repo? + lockfile_changed? ? generate_gem_rbis : cleanup_orphaned_rbis + else + @logs << "Not a git repository" + end + end + end + + private + + sig { returns(T::Boolean) } + def git_repo? + require "open3" + + _, status = Open3.capture2e("git rev-parse --is-inside-work-tree") + + T.must(status.success?) + end + + sig { returns(T::Boolean) } + def lockfile_changed? + fetch_lockfile_diff + !T.must(@lockfile_diff).empty? + end + + sig { returns(String) } + def fetch_lockfile_diff + @lockfile_diff = %x(git diff HEAD Gemfile.lock).strip + end + + sig { void } + def generate_gem_rbis + parser = Tapioca::LockfileDiffParser.new(@lockfile_diff) + + removed_gems = parser.removed_gems + added_or_modified_gems = parser.added_or_modified_gems + + if added_or_modified_gems.any? + # Resetting BUNDLE_GEMFILE to root folder to use the project's Gemfile instead of Ruby LSP's composed Gemfile + stdout, stderr, status = T.unsafe(Open3).capture3( + { "BUNDLE_GEMFILE" => "Gemfile" }, + "bin/tapioca", + "gem", + "--lsp_addon", + *added_or_modified_gems, + ) + @logs << stdout + @logs << stderr + # T.must(@outgoing_queue) << if status.success? + # Notification.window_log_message( + # stdout, + # type: Constant::MessageType::INFO, + # ) + # else + # Notification.window_log_message( + # stderr, + # type: Constant::MessageType::ERROR, + # ) + # end + elsif removed_gems.any? + FileUtils.rm_f(Dir.glob("sorbet/rbi/gems/{#{removed_gems.join(",")}}@*.rbi")) + # T.must(@outgoing_queue) << RubyLsp::Notification.window_log_message( + # "Removed RBIs for: #{removed_gems.join(", ")}", + # type: LanguageServer::Protocol::Constant::MessageType::INFO, + # ) + end + end + + sig { void } + def cleanup_orphaned_rbis + untracked_files = %x(git ls-files --others --exclude-standard sorbet/rbi/gems/).lines.map(&:strip) + deleted_files = %x(git ls-files --deleted sorbet/rbi/gems/).lines.map(&:strip) + + untracked_files.each do |file| + File.delete(file) + + T.must(@outgoing_queue) << Notification.window_log_message( + "Deleted untracked RBI: #{file}", + type: Constant::MessageType::INFO, + ) + end + + deleted_files.each do |file| + %x(git checkout -- #{file}) + + T.must(@outgoing_queue) << RubyLsp::Notification.window_log_message( + "Restored deleted RBI: #{file}", + type: Constant::MessageType::INFO, + ) + end + end + end + end +end diff --git a/spec/tapioca/ruby_lsp/run_gem_rbi_check_spec.rb b/spec/tapioca/ruby_lsp/run_gem_rbi_check_spec.rb new file mode 100644 index 000000000..50638e3d8 --- /dev/null +++ b/spec/tapioca/ruby_lsp/run_gem_rbi_check_spec.rb @@ -0,0 +1,64 @@ +# typed: true +# frozen_string_literal: true + +require "spec_helper" +require "ruby_lsp/tapioca/run_gem_rbi_check" + +module Tapioca + module RubyLsp + class RunGemRbiCheckSpec < SpecWithProject + FOO_RB = <<~RUBY + module Foo + end + RUBY + + # TODO: understand why this fails with `before(:all)` + # before(:all) do + before do + @project.tapioca("configure") + end + + after do + project.write_gemfile!(project.tapioca_gemfile) + @project.require_default_gems + project.remove!("sorbet/rbi") + project.remove!("../gems") + project.remove!(".git") + project.remove!("sorbet/tapioca/require.rb") + project.remove!("config/application.rb") + ensure + @project.remove!("output") + end + + it "does nothing if there is no git repo" do + foo = mock_gem("foo", "0.0.1") do + write!("lib/foo.rb", FOO_RB) + end + @project.require_mock_gem(foo) + + @project.bundle_install! + check = ::RubyLsp::Tapioca::RunGemRbiCheck.new + check.run(@project.absolute_path) + + assert check.logs.include?("Not a git repository") + end + + it "create the RBI for a newly added gem" do + @project.exec("git init") + @project.exec("touch Gemfile.lock") + @project.exec("git add . && git commit -m 'Initial commit'") + + foo = mock_gem("foo", "0.0.1") do + write!("lib/foo.rb", FOO_RB) + end + @project.require_mock_gem(foo) + @project.bundle_install! + + check = ::RubyLsp::Tapioca::RunGemRbiCheck.new + check.run(@project.absolute_path) + + assert_project_file_exist("sorbet/rbi/gems/foo@0.0.1.rbi") + end + end + end +end