Skip to content

Commit e43ac52

Browse files
authored
Automatically install ruby-lsp-rails as part of custom bundle (#1381)
Automatically install ruby-lsp-rails as part of custom bundle
1 parent c9d9a85 commit e43ac52

File tree

2 files changed

+114
-19
lines changed

2 files changed

+114
-19
lines changed

lib/ruby_lsp/setup_bundler.rb

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -57,15 +57,18 @@ def initialize(project_path, **options)
5757
def setup!
5858
raise BundleNotLocked if @gemfile&.exist? && !@lockfile&.exist?
5959

60-
# Do not setup a custom bundle if both `ruby-lsp` and `debug` are already in the Gemfile
61-
if @dependencies["ruby-lsp"] && @dependencies["debug"]
60+
# Do not set up a custom bundle if LSP dependencies are already in the Gemfile
61+
if @dependencies["ruby-lsp"] &&
62+
@dependencies["debug"] &&
63+
(@dependencies["rails"] ? @dependencies["ruby-lsp-rails"] : true)
6264
$stderr.puts(
63-
"Ruby LSP> Skipping custom bundle setup since both `ruby-lsp` and `debug` are already in #{@gemfile}",
65+
"Ruby LSP> Skipping custom bundle setup since LSP dependencies are already in #{@gemfile}",
6466
)
6567

66-
# If the user decided to add the `ruby-lsp` and `debug` to their Gemfile after having already run the Ruby LSP,
67-
# then we need to remove the `.ruby-lsp` folder, otherwise we will run `bundle install` for the top level and
68-
# try to execute the Ruby LSP using the custom bundle, which will fail since the gems are not installed there
68+
# If the user decided to add `ruby-lsp` and `debug` (and potentially `ruby-lsp-rails`) to their Gemfile after
69+
# having already run the Ruby LSP, then we need to remove the `.ruby-lsp` folder, otherwise we will run `bundle
70+
# install` for the top level and try to execute the Ruby LSP using the custom bundle, which will fail since the
71+
# gems are not installed there
6972
@custom_dir.rmtree if @custom_dir.exist?
7073
return run_bundle_install
7174
end
@@ -143,6 +146,10 @@ def write_custom_gemfile
143146
parts << 'gem "debug", require: false, group: :development, platforms: :mri'
144147
end
145148

149+
if @dependencies["rails"] && !@dependencies["ruby-lsp-rails"]
150+
parts << 'gem "ruby-lsp-rails", require: false, group: :development'
151+
end
152+
146153
content = parts.join("\n")
147154
@custom_gemfile.write(content) unless @custom_gemfile.exist? && @custom_gemfile.read == content
148155
end
@@ -183,22 +190,24 @@ def run_bundle_install(bundle_gemfile = @gemfile)
183190
local_config_path = File.join(Dir.pwd, ".bundle")
184191
env["BUNDLE_APP_CONFIG"] = local_config_path if Dir.exist?(local_config_path)
185192

186-
# If both `ruby-lsp` and `debug` are already in the Gemfile, then we shouldn't try to upgrade them or else we'll
187-
# produce undesired source control changes. If the custom bundle was just created and either `ruby-lsp` or `debug`
188-
# weren't a part of the Gemfile, then we need to run `bundle install` for the first time to generate the
189-
# Gemfile.lock with them included or else Bundler will complain that they're missing. We can only update if the
190-
# custom `.ruby-lsp/Gemfile.lock` already exists and includes both gems
193+
# If `ruby-lsp` and `debug` (and potentially `ruby-lsp-rails`) are already in the Gemfile, then we shouldn't try
194+
# to upgrade them or else we'll produce undesired source control changes. If the custom bundle was just created
195+
# and any of `ruby-lsp`, `ruby-lsp-rails` or `debug` weren't a part of the Gemfile, then we need to run `bundle
196+
# install` for the first time to generate the Gemfile.lock with them included or else Bundler will complain that
197+
# they're missing. We can only update if the custom `.ruby-lsp/Gemfile.lock` already exists and includes all gems
191198

192199
# When not updating, we run `(bundle check || bundle install)`
193200
# When updating, we run `((bundle check && bundle update ruby-lsp debug) || bundle install)`
194201
command = +"(bundle check"
195202

196203
if should_bundle_update?
197-
# If ruby-lsp or debug are not in the Gemfile, try to update them to the latest version
204+
# If any of `ruby-lsp`, `ruby-lsp-rails` or `debug` are not in the Gemfile, try to update them to the latest
205+
# version
198206
command.prepend("(")
199207
command << " && bundle update "
200208
command << "ruby-lsp " unless @dependencies["ruby-lsp"]
201209
command << "debug " unless @dependencies["debug"]
210+
command << "ruby-lsp-rails " if @dependencies["rails"] && !@dependencies["ruby-lsp-rails"]
202211
command << "--pre" if @experimental
203212
command.delete_suffix!(" ")
204213
command << ")"
@@ -221,13 +230,20 @@ def run_bundle_install(bundle_gemfile = @gemfile)
221230

222231
sig { returns(T::Boolean) }
223232
def should_bundle_update?
224-
# If both `ruby-lsp` and `debug` are in the Gemfile, then we shouldn't try to upgrade them or else it will produce
225-
# version control changes
226-
return false if @dependencies["ruby-lsp"] && @dependencies["debug"]
233+
# If `ruby-lsp`, `ruby-lsp-rails` and `debug` are in the Gemfile, then we shouldn't try to upgrade them or else it
234+
# will produce version control changes
235+
if @dependencies["rails"]
236+
return false if @dependencies.values_at("ruby-lsp", "ruby-lsp-rails", "debug").all?
237+
238+
# If the custom lockfile doesn't include `ruby-lsp`, `ruby-lsp-rails` or `debug`, we need to run bundle install
239+
# before updating
240+
return false if custom_bundle_dependencies.values_at("ruby-lsp", "debug", "ruby-lsp-rails").any?(&:nil?)
241+
else
242+
return false if @dependencies.values_at("ruby-lsp", "debug").all?
227243

228-
# If the custom lockfile doesn't include either the `ruby-lsp` or `debug`, we need to run bundle install before
229-
# updating
230-
return false if custom_bundle_dependencies["ruby-lsp"].nil? || custom_bundle_dependencies["debug"].nil?
244+
# If the custom lockfile doesn't include `ruby-lsp` or `debug`, we need to run bundle install before updating
245+
return false if custom_bundle_dependencies.values_at("ruby-lsp", "debug").any?(&:nil?)
246+
end
231247

232248
# If the last updated file doesn't exist or was updated more than 4 hours ago, we should update
233249
!@last_updated_path.exist? || Time.parse(@last_updated_path.read) < (Time.now - FOUR_HOURS)

test/setup_bundler_test.rb

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,18 @@ def test_does_nothing_if_both_ruby_lsp_and_debug_are_in_the_bundle
1212
refute_path_exists(".ruby-lsp")
1313
end
1414

15+
def test_does_nothing_if_both_ruby_lsp_and_debug_are_in_the_bundle2
16+
Object.any_instance.expects(:system).with(bundle_env, "(bundle check || bundle install) 1>&2")
17+
Bundler::LockfileParser.any_instance.expects(:dependencies).returns({
18+
"ruby-lsp" => true,
19+
"rails" => true,
20+
"ruby-lsp-rails" => true,
21+
"debug" => true,
22+
})
23+
run_script
24+
refute_path_exists(".ruby-lsp")
25+
end
26+
1527
def test_removes_ruby_lsp_folder_if_both_gems_were_added_to_the_bundle
1628
Object.any_instance.expects(:system).with(bundle_env, "(bundle check || bundle install) 1>&2")
1729
Bundler::LockfileParser.any_instance.expects(:dependencies).returns({ "ruby-lsp" => true, "debug" => true })
@@ -22,6 +34,27 @@ def test_removes_ruby_lsp_folder_if_both_gems_were_added_to_the_bundle
2234
FileUtils.rm_r(".ruby-lsp") if Dir.exist?(".ruby-lsp")
2335
end
2436

37+
def test_in_a_rails_app_does_nothing_if_ruby_lsp_and_ruby_lsp_rails_and_debug_are_in_the_bundle
38+
Object.any_instance.expects(:system).with(bundle_env, "(bundle check || bundle install) 1>&2")
39+
Bundler::LockfileParser.any_instance.expects(:dependencies)
40+
.returns({ "ruby-lsp" => true, "ruby-lsp-rails" => true, "debug" => true })
41+
run_script
42+
refute_path_exists(".ruby-lsp")
43+
ensure
44+
FileUtils.rm_r(".ruby-lsp") if Dir.exist?(".ruby-lsp")
45+
end
46+
47+
def test_in_a_rails_app_removes_ruby_lsp_folder_if_all_gems_were_added_to_the_bundle
48+
Object.any_instance.expects(:system).with(bundle_env, "(bundle check || bundle install) 1>&2")
49+
Bundler::LockfileParser.any_instance.expects(:dependencies)
50+
.returns({ "ruby-lsp" => true, "ruby-lsp-rails" => true, "debug" => true })
51+
FileUtils.mkdir(".ruby-lsp")
52+
run_script
53+
refute_path_exists(".ruby-lsp")
54+
ensure
55+
FileUtils.rm_r(".ruby-lsp") if Dir.exist?(".ruby-lsp")
56+
end
57+
2558
def test_creates_custom_bundle
2659
Object.any_instance.expects(:system).with(bundle_env(".ruby-lsp/Gemfile"), "(bundle check || bundle install) 1>&2")
2760
Bundler::LockfileParser.any_instance.expects(:dependencies).returns({}).at_least_once
@@ -32,9 +65,26 @@ def test_creates_custom_bundle
3265
assert_path_exists(".ruby-lsp/Gemfile.lock")
3366
assert_path_exists(".ruby-lsp/main_lockfile_hash")
3467
assert_match("ruby-lsp", File.read(".ruby-lsp/Gemfile"))
68+
refute_match("ruby-lsp-rails", File.read(".ruby-lsp/Gemfile"))
3569
assert_match("debug", File.read(".ruby-lsp/Gemfile"))
3670
ensure
37-
FileUtils.rm_r(".ruby-lsp")
71+
FileUtils.rm_r(".ruby-lsp") if Dir.exist?(".ruby-lsp")
72+
end
73+
74+
def test_creates_custom_bundle_for_a_rails_app
75+
Object.any_instance.expects(:system).with(bundle_env(".ruby-lsp/Gemfile"), "(bundle check || bundle install) 1>&2")
76+
Bundler::LockfileParser.any_instance.expects(:dependencies).returns({ "rails" => true }).at_least_once
77+
run_script
78+
79+
assert_path_exists(".ruby-lsp")
80+
assert_path_exists(".ruby-lsp/Gemfile")
81+
assert_path_exists(".ruby-lsp/Gemfile.lock")
82+
assert_path_exists(".ruby-lsp/main_lockfile_hash")
83+
assert_match("ruby-lsp", File.read(".ruby-lsp/Gemfile"))
84+
assert_match("debug", File.read(".ruby-lsp/Gemfile"))
85+
assert_match("ruby-lsp-rails", File.read(".ruby-lsp/Gemfile"))
86+
ensure
87+
FileUtils.rm_r(".ruby-lsp") if Dir.exist?(".ruby-lsp")
3888
end
3989

4090
def test_changing_lockfile_causes_custom_bundle_to_be_rebuilt
@@ -422,6 +472,35 @@ def test_ensures_lockfile_remotes_are_relative_to_default_gemfile
422472
end
423473
end
424474

475+
def test_ruby_lsp_rails_is_automatically_included_in_rails_apps
476+
Dir.mktmpdir do |dir|
477+
Dir.chdir(dir) do
478+
File.write(File.join(dir, "Gemfile"), <<~GEMFILE)
479+
source "https://rubygems.org"
480+
gem "rails"
481+
GEMFILE
482+
483+
capture_subprocess_io do
484+
Bundler.with_unbundled_env do
485+
# Run bundle install to generate the lockfile
486+
system("bundle install")
487+
end
488+
end
489+
490+
Object.any_instance.expects(:system).with(
491+
bundle_env(".ruby-lsp/Gemfile"),
492+
"(bundle check || bundle install) 1>&2",
493+
)
494+
Bundler.with_unbundled_env do
495+
run_script
496+
end
497+
498+
assert_path_exists(".ruby-lsp/Gemfile")
499+
assert_match('gem "ruby-lsp-rails"', File.read(".ruby-lsp/Gemfile"))
500+
end
501+
end
502+
end
503+
425504
private
426505

427506
# This method runs the script and then immediately unloads it. This allows us to make assertions against the effects

0 commit comments

Comments
 (0)