Skip to content

Commit

Permalink
Add version constraint for add-ons (#2638)
Browse files Browse the repository at this point in the history
  • Loading branch information
vinistock authored Oct 2, 2024
1 parent d7bcfc1 commit 3558809
Show file tree
Hide file tree
Showing 9 changed files with 102 additions and 5 deletions.
47 changes: 45 additions & 2 deletions lib/ruby_lsp/addon.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ class Addon

AddonNotFoundError = Class.new(StandardError)

class IncompatibleApiError < StandardError; end

class << self
extend T::Sig

Expand Down Expand Up @@ -80,13 +82,49 @@ def load_addons(global_state, outgoing_queue)
errors
end

sig { params(addon_name: String).returns(Addon) }
def get(addon_name)
# Get a reference to another add-on object by name and version. If an add-on exports an API that can be used by
# other add-ons, this is the way to get access to that API.
#
# Important: if the add-on is not found, AddonNotFoundError will be raised. If the add-on is found, but its
# current version does not satisfy the given version constraint, then IncompatibleApiError will be raised. It is
# the responsibility of the add-ons using this API to handle these errors appropriately.
sig { params(addon_name: String, version_constraints: String).returns(Addon) }
def get(addon_name, *version_constraints)
addon = addons.find { |addon| addon.name == addon_name }
raise AddonNotFoundError, "Could not find add-on '#{addon_name}'" unless addon

version_object = Gem::Version.new(addon.version)

unless version_constraints.all? { |constraint| Gem::Requirement.new(constraint).satisfied_by?(version_object) }
raise IncompatibleApiError,
"Constraints #{version_constraints.inspect} is incompatible with #{addon_name} version #{addon.version}"
end

addon
end

# Depend on a specific version of the Ruby LSP. This method should only be used if the add-on is distributed in a
# gem that does not have a runtime dependency on the ruby-lsp gem. This method should be invoked at the top of the
# `addon.rb` file before defining any classes or requiring any files. For example:
#
# ```ruby
# RubyLsp::Addon.depend_on_ruby_lsp!(">= 0.18.0")
#
# module MyGem
# class MyAddon < RubyLsp::Addon
# # ...
# end
# end
# ```
sig { params(version_constraints: String).void }
def depend_on_ruby_lsp!(*version_constraints)
version_object = Gem::Version.new(RubyLsp::VERSION)

unless version_constraints.all? { |constraint| Gem::Requirement.new(constraint).satisfied_by?(version_object) }
raise IncompatibleApiError,
"Add-on is not compatible with this version of the Ruby LSP. Skipping its activation"
end
end
end

sig { void }
Expand Down Expand Up @@ -132,6 +170,11 @@ def deactivate; end
sig { abstract.returns(String) }
def name; end

# Add-ons should override the `version` method to return a semantic version string representing the add-on's
# version. This is used for compatibility checks
sig { abstract.returns(String) }
def version; end

# Creates a new CodeLens listener. This method is invoked on every CodeLens request
sig do
overridable.params(
Expand Down
28 changes: 25 additions & 3 deletions test/addon_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ def activate(global_state, outgoing_queue)
def name
"My Add-on"
end

def version
"0.1.0"
end
end
@global_state = GlobalState.new

Expand Down Expand Up @@ -68,6 +72,10 @@ def activate(global_state, outgoing_queue)
def name
"My Add-on"
end

def version
"0.1.0"
end
end

queue = Thread::Queue.new
Expand Down Expand Up @@ -102,13 +110,19 @@ def workspace_did_change_watched_files(changes); end
end

def test_get_an_addon_by_name
addon = Addon.get("My Add-on")
addon = Addon.get("My Add-on", "0.1.0")
assert_equal("My Add-on", addon.name)
end

def test_raises_if_an_addon_cannot_be_found
assert_raises(Addon::AddonNotFoundError) do
Addon.get("Invalid Addon")
Addon.get("Invalid Addon", "0.1.0")
end
end

def test_raises_if_an_addon_version_does_not_match
assert_raises(Addon::IncompatibleApiError) do
Addon.get("My Add-on", "> 15.0.0")
end
end

Expand All @@ -125,11 +139,19 @@ def test_addons_receive_settings
outgoing_queue = Thread::Queue.new
Addon.load_addons(global_state, outgoing_queue)

addon = Addon.get("My Add-on")
addon = Addon.get("My Add-on", "0.1.0")

assert_equal({ something: false }, T.unsafe(addon).settings)
ensure
T.must(outgoing_queue).close
end

def test_depend_on_constraints
assert_raises(Addon::IncompatibleApiError) do
Addon.depend_on_ruby_lsp!(">= 10.0.0")
end

Addon.depend_on_ruby_lsp!(">= 0.18.0", "< 0.30.0")
end
end
end
4 changes: 4 additions & 0 deletions test/requests/code_lens_expectations_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,10 @@ def activate(global_state, outgoing_queue)
def deactivate; end

def name; end

def version
"0.1.0"
end
end
end

Expand Down
4 changes: 4 additions & 0 deletions test/requests/completion_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1520,6 +1520,10 @@ def deactivate; end
def name
"Foo"
end

def version
"0.1.0"
end
end
end
end
4 changes: 4 additions & 0 deletions test/requests/definition_expectations_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1027,6 +1027,10 @@ def activate(global_state, outgoing_queue); end
def deactivate; end

def name; end

def version
"0.1.0"
end
end
end
end
4 changes: 4 additions & 0 deletions test/requests/document_symbol_expectations_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ def name

def deactivate; end

def version
"0.1.0"
end

def create_document_symbol_listener(response_builder, dispatcher)
klass = Class.new do
include RubyLsp::Requests::Support::Common
Expand Down
4 changes: 4 additions & 0 deletions test/requests/hover_expectations_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,10 @@ def name

def deactivate; end

def version
"0.1.0"
end

def create_hover_listener(response_builder, nesting, dispatcher)
klass = Class.new do
def initialize(response_builder, dispatcher)
Expand Down
4 changes: 4 additions & 0 deletions test/requests/semantic_highlighting_expectations_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@ def activate(global_state, outgoing_queue); end
def deactivate; end

def name; end

def version
"0.1.0"
end
end
end

Expand Down
8 changes: 8 additions & 0 deletions test/server_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,10 @@ def name
end

def deactivate; end

def version
"0.1.0"
end
end

Class.new(RubyLsp::Addon) do
Expand All @@ -716,6 +720,10 @@ def name
end

def deactivate; end

def version
"0.1.0"
end
end
end

Expand Down

0 comments on commit 3558809

Please sign in to comment.