Skip to content

Commit f40a4e5

Browse files
authored
Avoid requiring add-ons that are copied into the workspace (#3669)
1 parent 577b452 commit f40a4e5

File tree

2 files changed

+53
-4
lines changed

2 files changed

+53
-4
lines changed

lib/ruby_lsp/addon.rb

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,26 @@ def load_addons(global_state, outgoing_queue, include_project_addons: true)
5757

5858
if include_project_addons
5959
project_addons = Dir.glob("#{global_state.workspace_path}/**/ruby_lsp/**/addon.rb")
60-
61-
# Ignore add-ons from dependencies if the bundle is stored inside the project. We already found those with
62-
# `Gem.find_files`
6360
bundle_path = Bundler.bundle_path.to_s
64-
project_addons.reject! { |path| path.start_with?(bundle_path) }
61+
gems_dir = Bundler.bundle_path.join("gems")
62+
63+
# Create an array of rejection glob patterns to ignore add-ons already discovered through Gem.find_files if
64+
# they are also copied inside the workspace for whatever reason. We received reports of projects having gems
65+
# installed in vendor/bundle despite BUNDLE_PATH pointing elsewhere. Without this mechanism, we will
66+
# double-require the same add-on, potentially for different versions of the same gem, which leads to incorrect
67+
# behavior
68+
reject_glob_patterns = addon_files.map do |path|
69+
relative_gem_path = Pathname.new(path).relative_path_from(gems_dir)
70+
first_part, *parts = relative_gem_path.to_s.split(File::SEPARATOR)
71+
first_part&.gsub!(/-([0-9.]+)$/, "*")
72+
"**/#{first_part}/#{parts.join("/")}"
73+
end
74+
75+
project_addons.reject! do |path|
76+
path.start_with?(bundle_path) ||
77+
reject_glob_patterns.any? { |pattern| File.fnmatch?(pattern, path, File::Constants::FNM_PATHNAME) }
78+
end
79+
6580
addon_files.concat(project_addons)
6681
end
6782

test/addon_test.rb

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,5 +225,39 @@ def version
225225
end
226226
end
227227
end
228+
229+
def test_loading_project_addons_ignores_vendor_bundle
230+
# Some users have gems installed under `vendor/bundle` despite not having their BUNDLE_PATH configured to be so.
231+
# That leads to loading the same add-on multiple times if they have the same gem installed both in their
232+
# BUNDLE_PATH and in `vendor/bundle`
233+
Dir.mktmpdir do |dir|
234+
addon_dir = File.join(dir, "vendor", "bundle", "rubocop-1.73.0", "lib", "ruby_lsp", "rubocop")
235+
FileUtils.mkdir_p(addon_dir)
236+
File.write(File.join(addon_dir, "addon.rb"), <<~RUBY)
237+
class OldRuboCopAddon < RubyLsp::Addon
238+
def activate(global_state, outgoing_queue)
239+
end
240+
241+
def name
242+
"Old RuboCop Addon"
243+
end
244+
245+
def version
246+
"0.1.0"
247+
end
248+
end
249+
RUBY
250+
251+
@global_state.apply_options({
252+
workspaceFolders: [{ uri: URI::Generic.from_path(path: dir).to_s }],
253+
})
254+
255+
Addon.load_addons(@global_state, @outgoing_queue)
256+
257+
assert_raises(Addon::AddonNotFoundError) do
258+
Addon.get("Project Addon", "0.1.0")
259+
end
260+
end
261+
end
228262
end
229263
end

0 commit comments

Comments
 (0)