From 9e28e99d01891cacbe6816abf2ed3bf46a77c69f Mon Sep 17 00:00:00 2001 From: Alex Rocha Date: Wed, 27 Nov 2024 14:57:42 -0800 Subject: [PATCH 01/11] Create lockfile diff parser --- lib/ruby_lsp/tapioca/lockfile_diff_parser.rb | 45 ++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 lib/ruby_lsp/tapioca/lockfile_diff_parser.rb diff --git a/lib/ruby_lsp/tapioca/lockfile_diff_parser.rb b/lib/ruby_lsp/tapioca/lockfile_diff_parser.rb new file mode 100644 index 000000000..4f1a70904 --- /dev/null +++ b/lib/ruby_lsp/tapioca/lockfile_diff_parser.rb @@ -0,0 +1,45 @@ +# typed: true +# frozen_string_literal: true + +module RubyLsp + module Tapioca + class LockfileDiffParser + GEM_NAME_PATTERN = /[\w\-]+/ + DIFF_LINE_PATTERN = /[+-](.*#{GEM_NAME_PATTERN})\s*\(/ + ADDED_LINE_PATTERN = /^\+.*#{GEM_NAME_PATTERN} \(.*\)/ + REMOVED_LINE_PATTERN = /^-.*#{GEM_NAME_PATTERN} \(.*\)/ + + attr_reader :added_or_modified_gems + attr_reader :removed_gems + + def initialize(diff_content) + @diff_content = diff_content.lines + @added_or_modified_gems = parse_added_or_modified_gems + @removed_gems = parse_removed_gems + end + + private + + def parse_added_or_modified_gems + @diff_content + .filter_map { |line| extract_gem(line) if line.match?(ADDED_LINE_PATTERN) } + .uniq + end + + def parse_removed_gems + @diff_content.filter_map do |line| + next unless line.match?(REMOVED_LINE_PATTERN) + + gem = extract_gem(line) + next if @added_or_modified_gems.include?(gem) + + gem + end.uniq + end + + def extract_gem(line) + line.match(DIFF_LINE_PATTERN)[1].strip + end + end + end +end From 0e3e113fb5321cc527c6c5db350029a4ea66a823 Mon Sep 17 00:00:00 2001 From: Andy Waite <13400+andyw8@users.noreply.github.com> Date: Mon, 25 Nov 2024 10:33:21 -0500 Subject: [PATCH 02/11] Add test for lockfile diff parser --- .../tapioca/lockfile_diff_parser_spec.rb | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 spec/tapioca/ruby_lsp/tapioca/lockfile_diff_parser_spec.rb diff --git a/spec/tapioca/ruby_lsp/tapioca/lockfile_diff_parser_spec.rb b/spec/tapioca/ruby_lsp/tapioca/lockfile_diff_parser_spec.rb new file mode 100644 index 000000000..139dc3fd2 --- /dev/null +++ b/spec/tapioca/ruby_lsp/tapioca/lockfile_diff_parser_spec.rb @@ -0,0 +1,62 @@ +# typed: strict +# frozen_string_literal: true + +require "spec_helper" +require "ruby_lsp/tapioca/lockfile_diff_parser" + +module RubyLsp + module Tapioca + class LockFileDiffParserSpec < Minitest::Spec + describe "#parse_added_or_modified_gems" do + it "parses added or modified gems from git diff" do + diff_output = <<~DIFF + + new_gem (1.0.0) + + updated_gem (2.0.0) + - removed_gem (1.0.0) + DIFF + + lockfile_parser = RubyLsp::Tapioca::LockfileDiffParser.new(diff_output) + assert_equal ["new_gem", "updated_gem"], lockfile_parser.added_or_modified_gems + end + + it "is empty when there is no diff" do + diff_output = "" + + lockfile_parser = RubyLsp::Tapioca::LockfileDiffParser.new(diff_output) + assert_empty lockfile_parser.added_or_modified_gems + end + end + + describe "#parse_removed_gems" do + it "parses removed gems from git diff" do + diff_output = <<~DIFF + + new_gem (1.0.0) + - removed_gem (1.0.0) + - outdated_gem (2.3.4) + DIFF + + lockfile_parser = RubyLsp::Tapioca::LockfileDiffParser.new(diff_output) + assert_equal ["removed_gem", "outdated_gem"], lockfile_parser.removed_gems + end + end + + it "handles gem names with hyphens and underscores" do + diff_output = <<~DIFF + - my-gem_extra2 (1.0.0.beta1) + DIFF + + lockfile_parser = RubyLsp::Tapioca::LockfileDiffParser.new(diff_output) + assert_equal ["my-gem_extra2"], lockfile_parser.removed_gems + end + + it "handles gem names with multiple hyphens" do + diff_output = <<~DIFF + - sorbet-static-and-runtime (0.5.0) + DIFF + + lockfile_parser = RubyLsp::Tapioca::LockfileDiffParser.new(diff_output) + assert_equal ["sorbet-static-and-runtime"], lockfile_parser.removed_gems + end + end + end +end From 47d4943ed8d82b0b13443cad20b0ed7d70041b59 Mon Sep 17 00:00:00 2001 From: Alex Rocha Date: Wed, 30 Oct 2024 17:42:04 -0700 Subject: [PATCH 03/11] Add Tapioca Addon gem RBI generation support To support gem RBI generation, we needed a way to detect changes in Gemfile.lock. Currently, changes to this file cause the Ruby LSP to restart, resulting in loss of access to any previous state information. By running git diff on Gemfile.lock, we can detect changes to the file, and trigger the gem RBI generation process. Then we can parse the diff output to determine which gems have been removed, added or modified, and either remove the corresponding RBI files or trigger the RBI generation process for the gems. --- lib/ruby_lsp/tapioca/addon.rb | 57 +++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/lib/ruby_lsp/tapioca/addon.rb b/lib/ruby_lsp/tapioca/addon.rb index b3d17f0c2..fdedd64de 100644 --- a/lib/ruby_lsp/tapioca/addon.rb +++ b/lib/ruby_lsp/tapioca/addon.rb @@ -13,6 +13,8 @@ end require "zlib" +require "open3" +require "ruby_lsp/tapioca/lockfile_diff_parser" module RubyLsp module Tapioca @@ -27,6 +29,7 @@ def initialize @rails_runner_client = T.let(nil, T.nilable(RubyLsp::Rails::RunnerClient)) @index = T.let(nil, T.nilable(RubyIndexer::Index)) @file_checksums = T.let({}, T::Hash[String, String]) + @lockfile_diff = T.let(nil, T.nilable(String)) @outgoing_queue = T.let(nil, T.nilable(Thread::Queue)) end @@ -50,6 +53,8 @@ def activate(global_state, outgoing_queue) request_name: "load_compilers_and_extensions", workspace_path: @global_state.workspace_path, ) + + generate_gem_rbis if git_repo? && lockfile_changed? rescue IncompatibleApiError # The requested version for the Rails add-on no longer matches. We need to upgrade and fix the breaking # changes @@ -132,6 +137,58 @@ def file_updated?(change, path) false end + + sig { returns(T::Boolean) } + def git_repo? + Dir.exist?(".git") + 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 end end end From 7541f1ddba93eac888293f599625c89edb4c4dcc Mon Sep 17 00:00:00 2001 From: Alex Rocha Date: Wed, 27 Nov 2024 11:38:04 -0800 Subject: [PATCH 04/11] Cleanup orphaned RBIs --- lib/ruby_lsp/tapioca/addon.rb | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/lib/ruby_lsp/tapioca/addon.rb b/lib/ruby_lsp/tapioca/addon.rb index fdedd64de..d29264db8 100644 --- a/lib/ruby_lsp/tapioca/addon.rb +++ b/lib/ruby_lsp/tapioca/addon.rb @@ -54,7 +54,9 @@ def activate(global_state, outgoing_queue) workspace_path: @global_state.workspace_path, ) - generate_gem_rbis if git_repo? && lockfile_changed? + if git_repo? + lockfile_changed? ? generate_gem_rbis : cleanup_orphaned_rbis + end rescue IncompatibleApiError # The requested version for the Rails add-on no longer matches. We need to upgrade and fix the breaking # changes @@ -189,6 +191,30 @@ def generate_gem_rbis ) 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 From 32306d77f2aba0d131de006f44da596ea4728a3a Mon Sep 17 00:00:00 2001 From: Alex Rocha Date: Wed, 4 Dec 2024 10:25:06 -0800 Subject: [PATCH 05/11] Refactor how we detect git repo --- lib/ruby_lsp/tapioca/addon.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/ruby_lsp/tapioca/addon.rb b/lib/ruby_lsp/tapioca/addon.rb index d29264db8..04147c649 100644 --- a/lib/ruby_lsp/tapioca/addon.rb +++ b/lib/ruby_lsp/tapioca/addon.rb @@ -13,7 +13,6 @@ end require "zlib" -require "open3" require "ruby_lsp/tapioca/lockfile_diff_parser" module RubyLsp @@ -142,7 +141,11 @@ def file_updated?(change, path) sig { returns(T::Boolean) } def git_repo? - Dir.exist?(".git") + require "open3" + + _, status = Open3.capture2e("git rev-parse --is-inside-work-tree") + + T.must(status.success?) end sig { returns(T::Boolean) } From ded190017c2162c8a091d318a56b96d040804201 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 06/11] Extract gem rbi check logic into its own class --- lib/ruby_lsp/tapioca/addon.rb | 86 +--------- lib/ruby_lsp/tapioca/run_gem_rbi_check.rb | 112 +++++++++++++ .../ruby_lsp/run_gem_rbi_check_spec.rb | 152 ++++++++++++++++++ 3 files changed, 266 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 04147c649..ed359b54e 100644 --- a/lib/ruby_lsp/tapioca/addon.rb +++ b/lib/ruby_lsp/tapioca/addon.rb @@ -13,7 +13,7 @@ end require "zlib" -require "ruby_lsp/tapioca/lockfile_diff_parser" +require "ruby_lsp/tapioca/run_gem_rbi_check" module RubyLsp module Tapioca @@ -53,9 +53,7 @@ def activate(global_state, outgoing_queue) workspace_path: @global_state.workspace_path, ) - 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 @@ -138,86 +136,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..2253a48a3 --- /dev/null +++ b/lib/ruby_lsp/tapioca/run_gem_rbi_check.rb @@ -0,0 +1,112 @@ +# typed: true +# frozen_string_literal: true + +require "ruby_lsp/tapioca/lockfile_diff_parser" + +module RubyLsp + module Tapioca + class RunGemRbiCheck + extend T::Sig + + def initialize + @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: 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..b0d89a7f6 --- /dev/null +++ b/spec/tapioca/ruby_lsp/run_gem_rbi_check_spec.rb @@ -0,0 +1,152 @@ +# 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 + + def setup_git_repo + @project.exec("git init") + @project.exec("touch Gemfile.lock") + FileUtils.mkdir_p("#{@project.absolute_path}/sorbet/rbi/gems") + @project.exec("git add . && git commit -m 'Initial commit'") + 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 "creates the RBI for a newly added gem" do + setup_git_repo + + 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 + + it "regenerates RBI when a gem version changes" do + setup_git_repo + + 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") + + # Modify the gem + foo = mock_gem("foo", "0.0.2") do + write!("lib/foo.rb", FOO_RB) + end + @project.require_mock_gem(foo) + @project.bundle_install! + + check.run(@project.absolute_path) + + assert_project_file_exist("sorbet/rbi/gems/foo@0.0.2.rbi") + end + + it "removes RBI file when a gem is removed" do + setup_git_repo + + 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") + + @project.exec("git add Gemfile.lock") + @project.exec("git commit -m 'Add foo gem'") + + @project.write_gemfile!(@project.tapioca_gemfile) + @project.bundle_install! + + check = ::RubyLsp::Tapioca::RunGemRbiCheck.new + check.run(@project.absolute_path) + + refute_project_file_exist("sorbet/rbi/gems/foo@0.0.1.rbi") + end + + it "deletes untracked RBI files" do + setup_git_repo + + # Create an untracked RBI file + FileUtils.touch("#{@project.absolute_path}/sorbet/rbi/gems/bar@0.0.1.rbi") + + assert_project_file_exist("/sorbet/rbi/gems/bar@0.0.1.rbi") + + check = ::RubyLsp::Tapioca::RunGemRbiCheck.new + check.run(@project.absolute_path) + + refute_project_file_exist("sorbet/rbi/gems/bar@0.0.1.rbi") + end + + it "restores deleted RBI files" do + setup_git_repo + + # Create and delete a tracked RBI file + FileUtils.touch("#{@project.absolute_path}/sorbet/rbi/gems/foo@0.0.1.rbi") + @project.exec("git add sorbet/rbi/gems/foo@0.0.1.rbi") + @project.exec("git commit -m 'Add foo RBI'") + FileUtils.rm("#{@project.absolute_path}/sorbet/rbi/gems/foo@0.0.1.rbi") + + refute_project_file_exist("sorbet/rbi/gems/foo@0.0.1.rbi") + + 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 From f0065681fe8caa9ff8d07a207131e84aa0e89c9f Mon Sep 17 00:00:00 2001 From: Alex Rocha Date: Mon, 6 Jan 2025 21:52:17 -0800 Subject: [PATCH 07/11] Fix logging to work with addon notifications --- lib/ruby_lsp/tapioca/addon.rb | 9 ++- lib/ruby_lsp/tapioca/run_gem_rbi_check.rb | 59 +++++++++---------- .../ruby_lsp/run_gem_rbi_check_spec.rb | 2 +- 3 files changed, 37 insertions(+), 33 deletions(-) diff --git a/lib/ruby_lsp/tapioca/addon.rb b/lib/ruby_lsp/tapioca/addon.rb index ed359b54e..d15835dc2 100644 --- a/lib/ruby_lsp/tapioca/addon.rb +++ b/lib/ruby_lsp/tapioca/addon.rb @@ -53,7 +53,14 @@ def activate(global_state, outgoing_queue) workspace_path: @global_state.workspace_path, ) - RunGemRbiCheck.new.run + gem_rbi_check_result = RunGemRbiCheck.new.run + @outgoing_queue << Notification.window_log_message( + gem_rbi_check_result.stdout, + ) unless gem_rbi_check_result.stdout.empty? + @outgoing_queue << Notification.window_log_message( + gem_rbi_check_result.stderr, + type: Constant::MessageType::WARNING, + ) unless gem_rbi_check_result.stderr.empty? rescue IncompatibleApiError # The requested version for the Rails add-on no longer matches. We need to upgrade and fix the breaking # changes diff --git a/lib/ruby_lsp/tapioca/run_gem_rbi_check.rb b/lib/ruby_lsp/tapioca/run_gem_rbi_check.rb index 2253a48a3..0d47a212a 100644 --- a/lib/ruby_lsp/tapioca/run_gem_rbi_check.rb +++ b/lib/ruby_lsp/tapioca/run_gem_rbi_check.rb @@ -8,20 +8,33 @@ module Tapioca class RunGemRbiCheck extend T::Sig + class GemRbiCheckResult < T::Struct + prop :stdout, String + prop :stderr, String + prop :status, T.nilable(Process::Status) + end + + sig { void } def initialize - @logs = [] + @result = T.let( + GemRbiCheckResult.new(stdout: "", stderr: "", status: nil), + GemRbiCheckResult, + ) end - attr_reader :logs + attr_reader :result + sig { params(project_path: String).returns(GemRbiCheckResult) } 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" + @result.stdout = "Not a git repository" end end + + @result end private @@ -55,32 +68,17 @@ def generate_gem_rbis 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( + @result.stdout, @result.stderr, @result.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: Constant::MessageType::INFO, - ) + + @result.stdout = "Removed RBIs for: #{removed_gems.join(", ")}" end end @@ -89,23 +87,22 @@ 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) + deleted_rbis = [] + restored_rbis = [] + untracked_files.each do |file| File.delete(file) - - T.must(@outgoing_queue) << Notification.window_log_message( - "Deleted untracked RBI: #{file}", - type: Constant::MessageType::INFO, - ) + deleted_rbis << file 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, - ) + restored_rbis << file end + + @result.stdout = "Deleted untracked RBIs: #{deleted_rbis.join(", ")}" unless deleted_rbis.empty? + @result.stdout += "\n" unless deleted_rbis.empty? || restored_rbis.empty? + @result.stdout += "Restored deleted RBIs: #{restored_rbis.join(", ")}" unless restored_rbis.empty? 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 index b0d89a7f6..8348f1947 100644 --- a/spec/tapioca/ruby_lsp/run_gem_rbi_check_spec.rb +++ b/spec/tapioca/ruby_lsp/run_gem_rbi_check_spec.rb @@ -47,7 +47,7 @@ def setup_git_repo check = ::RubyLsp::Tapioca::RunGemRbiCheck.new check.run(@project.absolute_path) - assert check.logs.include?("Not a git repository") + assert check.result.stdout.include?("Not a git repository") end it "creates the RBI for a newly added gem" do From 68fb331ecf3c7333ead78bcaa1c4f0b75602b8ea Mon Sep 17 00:00:00 2001 From: Alex Rocha Date: Mon, 6 Jan 2025 22:18:25 -0800 Subject: [PATCH 08/11] Refactor RunGemRbiCheck for readability --- lib/ruby_lsp/tapioca/run_gem_rbi_check.rb | 81 ++++++++++++++--------- 1 file changed, 48 insertions(+), 33 deletions(-) diff --git a/lib/ruby_lsp/tapioca/run_gem_rbi_check.rb b/lib/ruby_lsp/tapioca/run_gem_rbi_check.rb index 0d47a212a..a658d1e24 100644 --- a/lib/ruby_lsp/tapioca/run_gem_rbi_check.rb +++ b/lib/ruby_lsp/tapioca/run_gem_rbi_check.rb @@ -5,15 +5,15 @@ module RubyLsp module Tapioca + class GemRbiCheckResult < T::Struct + prop :stdout, String + prop :stderr, String + prop :status, T.nilable(Process::Status) + end + class RunGemRbiCheck extend T::Sig - class GemRbiCheckResult < T::Struct - prop :stdout, String - prop :stderr, String - prop :status, T.nilable(Process::Status) - end - sig { void } def initialize @result = T.let( @@ -30,7 +30,7 @@ def run(project_path = ".") if git_repo? lockfile_changed? ? generate_gem_rbis : cleanup_orphaned_rbis else - @result.stdout = "Not a git repository" + log_message("Not a git repository") end end @@ -44,7 +44,6 @@ def git_repo? require "open3" _, status = Open3.capture2e("git rev-parse --is-inside-work-tree") - T.must(status.success?) end @@ -62,47 +61,63 @@ def fetch_lockfile_diff 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 - @result.stdout, @result.stderr, @result.status = T.unsafe(Open3).capture3( - { "BUNDLE_GEMFILE" => "Gemfile" }, - "bin/tapioca", - "gem", - "--lsp_addon", - *added_or_modified_gems, - ) + log_message("Identified lockfile changes, attempting to generate gem RBIs...") + execute_tapioca_gem_command(added_or_modified_gems) elsif removed_gems.any? - FileUtils.rm_f(Dir.glob("sorbet/rbi/gems/{#{removed_gems.join(",")}}@*.rbi")) - - @result.stdout = "Removed RBIs for: #{removed_gems.join(", ")}" + remove_rbis(removed_gems) end end + sig { params(gems: T::Array[String]).void } + def execute_tapioca_gem_command(gems) + # 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", + *gems, + ) + + log_message(stdout) unless stdout.empty? + log_message(stderr) unless stderr.empty? + @result.status = status + end + + sig { params(gems: T::Array[String]).void } + def remove_rbis(gems) + FileUtils.rm_f(Dir.glob("sorbet/rbi/gems/{#{gems.join(",")}}@*.rbi")) + log_message("Removed RBIs for: #{gems.join(", ")}") + 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) - deleted_rbis = [] - restored_rbis = [] + delete_files(untracked_files, "Deleted untracked RBIs") + restore_files(deleted_files, "Restored deleted RBIs") + end - untracked_files.each do |file| - File.delete(file) - deleted_rbis << file - end + sig { params(files: T::Array[String], message: String).void } + def delete_files(files, message) + files.each { |file| File.delete(file) } + log_message("#{message}: #{files.join(", ")}") unless files.empty? + end - deleted_files.each do |file| - %x(git checkout -- #{file}) - restored_rbis << file - end + sig { params(files: T::Array[String], message: String).void } + def restore_files(files, message) + files.each { |file| %x(git checkout -- #{file}) } + log_message("#{message}: #{files.join(", ")}") unless files.empty? + end - @result.stdout = "Deleted untracked RBIs: #{deleted_rbis.join(", ")}" unless deleted_rbis.empty? - @result.stdout += "\n" unless deleted_rbis.empty? || restored_rbis.empty? - @result.stdout += "Restored deleted RBIs: #{restored_rbis.join(", ")}" unless restored_rbis.empty? + sig { params(message: String).void } + def log_message(message) + @result.stdout += "#{message}\n" end end end From 3dd5d4b254d7bfd91605161e01d0da4ea56f308c Mon Sep 17 00:00:00 2001 From: Alex Rocha Date: Mon, 6 Jan 2025 23:15:28 -0800 Subject: [PATCH 09/11] Prevent RBI deletion for gems that are still direct dependencies --- lib/ruby_lsp/tapioca/lockfile_diff_parser.rb | 8 ++++++-- .../ruby_lsp/tapioca/lockfile_diff_parser_spec.rb | 14 ++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/lib/ruby_lsp/tapioca/lockfile_diff_parser.rb b/lib/ruby_lsp/tapioca/lockfile_diff_parser.rb index 4f1a70904..7935d0e09 100644 --- a/lib/ruby_lsp/tapioca/lockfile_diff_parser.rb +++ b/lib/ruby_lsp/tapioca/lockfile_diff_parser.rb @@ -1,6 +1,8 @@ # typed: true # frozen_string_literal: true +require "bundler" + module RubyLsp module Tapioca class LockfileDiffParser @@ -12,8 +14,10 @@ class LockfileDiffParser attr_reader :added_or_modified_gems attr_reader :removed_gems - def initialize(diff_content) + def initialize(diff_content, direct_dependencies: nil) @diff_content = diff_content.lines + @current_dependencies = direct_dependencies || + Bundler::LockfileParser.new(Bundler.default_lockfile.read).dependencies.keys @added_or_modified_gems = parse_added_or_modified_gems @removed_gems = parse_removed_gems end @@ -31,7 +35,7 @@ def parse_removed_gems next unless line.match?(REMOVED_LINE_PATTERN) gem = extract_gem(line) - next if @added_or_modified_gems.include?(gem) + next if @current_dependencies.include?(gem) gem end.uniq diff --git a/spec/tapioca/ruby_lsp/tapioca/lockfile_diff_parser_spec.rb b/spec/tapioca/ruby_lsp/tapioca/lockfile_diff_parser_spec.rb index 139dc3fd2..4d3b32130 100644 --- a/spec/tapioca/ruby_lsp/tapioca/lockfile_diff_parser_spec.rb +++ b/spec/tapioca/ruby_lsp/tapioca/lockfile_diff_parser_spec.rb @@ -38,6 +38,20 @@ class LockFileDiffParserSpec < Minitest::Spec lockfile_parser = RubyLsp::Tapioca::LockfileDiffParser.new(diff_output) assert_equal ["removed_gem", "outdated_gem"], lockfile_parser.removed_gems end + + it "ignores direct dependencies" do + diff_output = <<~DIFF + foo (1.1.1) + bar (1.2.3) + - foo (> 0) + DIFF + + lockfile_parser = RubyLsp::Tapioca::LockfileDiffParser.new( + diff_output, + direct_dependencies: ["foo"], + ) + assert_empty lockfile_parser.removed_gems + end end it "handles gem names with hyphens and underscores" do From 6b9f24f8a3b5543071001d8e93e351492ab3647c Mon Sep 17 00:00:00 2001 From: Alex Rocha Date: Tue, 7 Jan 2025 14:11:23 -0800 Subject: [PATCH 10/11] wip test fix against CI --- .github/workflows/ci.yml | 2 +- lib/ruby_lsp/tapioca/run_gem_rbi_check.rb | 8 +- spec/helpers/mock_gem.rb | 2 +- spec/spec_with_project.rb | 7 + .../ruby_lsp/run_gem_rbi_check_spec.rb | 183 +++++++++--------- 5 files changed, 101 insertions(+), 101 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b8328f7d4..a55935403 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -71,7 +71,7 @@ jobs: bundler-cache: true rubygems: ${{ matrix.rubygems }} - name: Run tests - run: bin/test + run: bin/test spec/tapioca/ruby_lsp/run_gem_rbi_check_spec.rb continue-on-error: ${{ !!matrix.experimental }} buildall: diff --git a/lib/ruby_lsp/tapioca/run_gem_rbi_check.rb b/lib/ruby_lsp/tapioca/run_gem_rbi_check.rb index a658d1e24..f3add9188 100644 --- a/lib/ruby_lsp/tapioca/run_gem_rbi_check.rb +++ b/lib/ruby_lsp/tapioca/run_gem_rbi_check.rb @@ -50,12 +50,12 @@ def git_repo? sig { returns(T::Boolean) } def lockfile_changed? fetch_lockfile_diff - !T.must(@lockfile_diff).empty? + !@lockfile_diff.empty? end sig { returns(String) } def fetch_lockfile_diff - @lockfile_diff = %x(git diff HEAD Gemfile.lock).strip + @lockfile_diff = File.exist?("Gemfile.lock") ? %x(git diff Gemfile.lock).strip : "" end sig { void } @@ -77,7 +77,9 @@ def execute_tapioca_gem_command(gems) # 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", + "bundle", + "exec", + "tapioca", "gem", "--lsp_addon", *gems, diff --git a/spec/helpers/mock_gem.rb b/spec/helpers/mock_gem.rb index 1aa278fdd..d26ad94ed 100644 --- a/spec/helpers/mock_gem.rb +++ b/spec/helpers/mock_gem.rb @@ -12,7 +12,7 @@ class MockGem < Spoom::Context # The gem's version string such as "1.0.0" or ">= 2.0.5" sig { returns(String) } - attr_reader :version + attr_accessor :version # The dependencies to be added to the gem's gemspec sig { returns(T::Array[String]) } diff --git a/spec/spec_with_project.rb b/spec/spec_with_project.rb index 3413914a5..be06a01b6 100644 --- a/spec/spec_with_project.rb +++ b/spec/spec_with_project.rb @@ -84,6 +84,13 @@ def mock_gem(name, version, dependencies: [], path: default_gem_path(name), &blo gem end + sig { params(gem: MockGem, version: String).returns(MockGem) } + def update_gem(gem, version) + gem.version = version + gem.gemspec(gem.default_gemspec_contents) + gem + end + # Spec assertions # Assert that the contents of `path` inside `@project` is equals to `expected` diff --git a/spec/tapioca/ruby_lsp/run_gem_rbi_check_spec.rb b/spec/tapioca/ruby_lsp/run_gem_rbi_check_spec.rb index 8348f1947..1778d59d1 100644 --- a/spec/tapioca/ruby_lsp/run_gem_rbi_check_spec.rb +++ b/spec/tapioca/ruby_lsp/run_gem_rbi_check_spec.rb @@ -12,140 +12,131 @@ module Foo end RUBY - # TODO: understand why this fails with `before(:all)` - # before(:all) do before do - @project.tapioca("configure") + @project = mock_project 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") + @project.destroy! end - def setup_git_repo - @project.exec("git init") - @project.exec("touch Gemfile.lock") - FileUtils.mkdir_p("#{@project.absolute_path}/sorbet/rbi/gems") - @project.exec("git add . && git commit -m 'Initial commit'") - 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) + describe "without git" do + before do + @project.bundle_install! + @project.tapioca("configure") end - @project.require_mock_gem(foo) - @project.bundle_install! - check = ::RubyLsp::Tapioca::RunGemRbiCheck.new - check.run(@project.absolute_path) - - assert check.result.stdout.include?("Not a git repository") - 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) - it "creates the RBI for a newly added gem" do - setup_git_repo + @project.bundle_install! + check = ::RubyLsp::Tapioca::RunGemRbiCheck.new + check.run(@project.absolute_path) - foo = mock_gem("foo", "0.0.1") do - write!("lib/foo.rb", FOO_RB) + assert check.result.stdout.include?("Not a git repository") 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 - it "regenerates RBI when a gem version changes" do - setup_git_repo - - foo = mock_gem("foo", "0.0.1") do - write!("lib/foo.rb", FOO_RB) + describe "with git" do + before do + @project.bundle_install! + @project.tapioca("configure") + @project.exec("git init") + @project.exec("git config commit.gpgsign false") + @project.exec("git add .") + @project.exec("git commit -m 'Initial commit'") end - @project.require_mock_gem(foo) - @project.bundle_install! - check = ::RubyLsp::Tapioca::RunGemRbiCheck.new - check.run(@project.absolute_path) + it "creates the RBI for a newly added gem" do + foo = mock_gem("foo", "0.0.1") do + write!("lib/foo.rb", FOO_RB) + end + @project.require_mock_gem(foo) + @project.bundle_install! - assert_project_file_exist("sorbet/rbi/gems/foo@0.0.1.rbi") + check = ::RubyLsp::Tapioca::RunGemRbiCheck.new + check.run(@project.absolute_path) - # Modify the gem - foo = mock_gem("foo", "0.0.2") do - write!("lib/foo.rb", FOO_RB) + assert_project_file_exist("sorbet/rbi/gems/foo@0.0.1.rbi") end - @project.require_mock_gem(foo) - @project.bundle_install! - check.run(@project.absolute_path) + it "regenerates RBI when a gem version changes" do + foo = mock_gem("foo", "0.0.1") do + write!("lib/foo.rb", FOO_RB) + end + @project.require_mock_gem(foo) + @project.bundle_install! - assert_project_file_exist("sorbet/rbi/gems/foo@0.0.2.rbi") - end + check = ::RubyLsp::Tapioca::RunGemRbiCheck.new + check.run(@project.absolute_path) - it "removes RBI file when a gem is removed" do - setup_git_repo + assert_project_file_exist("sorbet/rbi/gems/foo@0.0.1.rbi") - foo = mock_gem("foo", "0.0.1") do - write!("lib/foo.rb", FOO_RB) - end - @project.require_mock_gem(foo) - @project.bundle_install! + # Modify the gem + update_gem foo, "0.0.2" + @project.bundle_install! + + check.run(@project.absolute_path) - check = ::RubyLsp::Tapioca::RunGemRbiCheck.new - check.run(@project.absolute_path) + assert_project_file_exist("sorbet/rbi/gems/foo@0.0.2.rbi") + end - assert_project_file_exist("sorbet/rbi/gems/foo@0.0.1.rbi") + it "removes RBI file when a gem is removed" do + foo = mock_gem("foo", "0.0.1") do + write!("lib/foo.rb", FOO_RB) + end + @project.require_mock_gem(foo) + @project.bundle_install! - @project.exec("git add Gemfile.lock") - @project.exec("git commit -m 'Add foo gem'") + check1 = ::RubyLsp::Tapioca::RunGemRbiCheck.new + check1.run(@project.absolute_path) - @project.write_gemfile!(@project.tapioca_gemfile) - @project.bundle_install! + assert_project_file_exist("sorbet/rbi/gems/foo@0.0.1.rbi") - check = ::RubyLsp::Tapioca::RunGemRbiCheck.new - check.run(@project.absolute_path) + @project.exec("git restore Gemfile Gemfile.lock") - refute_project_file_exist("sorbet/rbi/gems/foo@0.0.1.rbi") - end + check2 = ::RubyLsp::Tapioca::RunGemRbiCheck.new + check2.run(@project.absolute_path) - it "deletes untracked RBI files" do - setup_git_repo + refute_project_file_exist("sorbet/rbi/gems/foo@0.0.1.rbi") + end - # Create an untracked RBI file - FileUtils.touch("#{@project.absolute_path}/sorbet/rbi/gems/bar@0.0.1.rbi") + it "deletes untracked RBI files" do + @project.bundle_install! + FileUtils.mkdir_p("#{@project.absolute_path}/sorbet/rbi/gems") + # Create an untracked RBI file + FileUtils.touch("#{@project.absolute_path}/sorbet/rbi/gems/bar@0.0.1.rbi") - assert_project_file_exist("/sorbet/rbi/gems/bar@0.0.1.rbi") + assert_project_file_exist("/sorbet/rbi/gems/bar@0.0.1.rbi") - check = ::RubyLsp::Tapioca::RunGemRbiCheck.new - check.run(@project.absolute_path) + check = ::RubyLsp::Tapioca::RunGemRbiCheck.new + check.run(@project.absolute_path) - refute_project_file_exist("sorbet/rbi/gems/bar@0.0.1.rbi") - end + refute_project_file_exist("sorbet/rbi/gems/bar@0.0.1.rbi") + end - it "restores deleted RBI files" do - setup_git_repo + it "restores deleted RBI files" do + @project.bundle_install! + FileUtils.mkdir_p("#{@project.absolute_path}/sorbet/rbi/gems") + # Create and delete a tracked RBI file + FileUtils.touch("#{@project.absolute_path}/sorbet/rbi/gems/foo@0.0.1.rbi") + @project.exec("git add sorbet/rbi/gems/foo@0.0.1.rbi") + @project.exec("git commit -m 'Add foo RBI'") + FileUtils.rm("#{@project.absolute_path}/sorbet/rbi/gems/foo@0.0.1.rbi") - # Create and delete a tracked RBI file - FileUtils.touch("#{@project.absolute_path}/sorbet/rbi/gems/foo@0.0.1.rbi") - @project.exec("git add sorbet/rbi/gems/foo@0.0.1.rbi") - @project.exec("git commit -m 'Add foo RBI'") - FileUtils.rm("#{@project.absolute_path}/sorbet/rbi/gems/foo@0.0.1.rbi") + refute_project_file_exist("sorbet/rbi/gems/foo@0.0.1.rbi") - refute_project_file_exist("sorbet/rbi/gems/foo@0.0.1.rbi") + check = ::RubyLsp::Tapioca::RunGemRbiCheck.new + check.run(@project.absolute_path) - check = ::RubyLsp::Tapioca::RunGemRbiCheck.new - check.run(@project.absolute_path) + assert_project_file_exist("sorbet/rbi/gems/foo@0.0.1.rbi") - assert_project_file_exist("sorbet/rbi/gems/foo@0.0.1.rbi") + # Clean-up commit + @project.exec("git reset --hard HEAD^") + end end end end From 068808aa720cab6be801ef1588043e62bc2a1c1b Mon Sep 17 00:00:00 2001 From: Alex Rocha Date: Mon, 20 Jan 2025 13:55:13 -0800 Subject: [PATCH 11/11] Ensure tapioca gem command executes in a clean env --- lib/ruby_lsp/tapioca/run_gem_rbi_check.rb | 30 ++++++++++++----------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/lib/ruby_lsp/tapioca/run_gem_rbi_check.rb b/lib/ruby_lsp/tapioca/run_gem_rbi_check.rb index f3add9188..7b995594f 100644 --- a/lib/ruby_lsp/tapioca/run_gem_rbi_check.rb +++ b/lib/ruby_lsp/tapioca/run_gem_rbi_check.rb @@ -74,20 +74,22 @@ def generate_gem_rbis sig { params(gems: T::Array[String]).void } def execute_tapioca_gem_command(gems) - # 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" }, - "bundle", - "exec", - "tapioca", - "gem", - "--lsp_addon", - *gems, - ) - - log_message(stdout) unless stdout.empty? - log_message(stderr) unless stderr.empty? - @result.status = status + Bundler.with_unbundled_env do + # 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" }, + "bundle", + "exec", + "tapioca", + "gem", + "--lsp_addon", + *gems, + ) + + log_message(stdout) unless stdout.empty? + log_message(stderr) unless stderr.empty? + @result.status = status + end end sig { params(gems: T::Array[String]).void }