From b7cf80bac5df2b657551cedc664d3abc874cba8a Mon Sep 17 00:00:00 2001 From: Vinicius Stock Date: Fri, 22 Sep 2023 16:35:18 -0400 Subject: [PATCH] Rename extensions to addons --- SERVER_EXTENSIONS.md => ADDONS.md | 69 ++++++++++--------- README.md | 8 +-- exe/ruby-lsp-check | 2 +- lib/ruby_lsp/{extension.rb => addon.rb} | 48 ++++++------- lib/ruby_lsp/check_docs.rb | 15 ++-- lib/ruby_lsp/executor.rb | 10 +-- lib/ruby_lsp/internal.rb | 2 +- lib/ruby_lsp/listener.rb | 15 ++-- lib/ruby_lsp/requests/code_lens.rb | 6 +- lib/ruby_lsp/requests/definition.rb | 6 +- lib/ruby_lsp/requests/document_symbol.rb | 6 +- lib/ruby_lsp/requests/hover.rb | 6 +- lib/ruby_lsp/requests/support/common.rb | 5 +- lib/ruby_lsp/server.rb | 2 +- test/addon_test.rb | 64 +++++++++++++++++ test/expectations/expectations_test_runner.rb | 6 +- test/extension_test.rb | 64 ----------------- test/requests/code_lens_expectations_test.rb | 10 +-- test/requests/definition_expectations_test.rb | 14 ++-- .../document_symbol_expectations_test.rb | 10 +-- test/requests/hover_expectations_test.rb | 10 +-- 21 files changed, 190 insertions(+), 188 deletions(-) rename SERVER_EXTENSIONS.md => ADDONS.md (77%) rename lib/ruby_lsp/{extension.rb => addon.rb} (68%) create mode 100644 test/addon_test.rb delete mode 100644 test/extension_test.rb diff --git a/SERVER_EXTENSIONS.md b/ADDONS.md similarity index 77% rename from SERVER_EXTENSIONS.md rename to ADDONS.md index 8f3ff181c..fc3f09666 100644 --- a/SERVER_EXTENSIONS.md +++ b/ADDONS.md @@ -1,9 +1,9 @@ -# Server extensions +# Ruby LSP addons > **WARNING** -> The Ruby LSP server extensions system is currently experimental and subject to changes in the API +> The Ruby LSP addon system is currently experimental and subject to changes in the API -Need help writing extensions? Consider joining the #ruby-lsp-extensions channel in the [Ruby DX Slack +Need help writing addons? Consider joining the #ruby-lsp-addons channel in the [Ruby DX Slack workspace](https://join.slack.com/t/ruby-dx/shared_invite/zt-1zjp7lmgk-zL7bGvze8gj5hFaYS~r5vg). ## Motivation and goals @@ -17,49 +17,49 @@ ecosystem. It would also create a bottleneck for authors to push new features. B hand, increases fragmentation which tends to increase the effort required by users to configure their development environments. -For these reasons, the Ruby LSP ships with a server extension system that authors can use to enhance the behavior of the -base LSP with tool specific functionality, aimed at +For these reasons, the Ruby LSP ships with an addon system that authors can use to enhance the behavior of the base LSP +with tool specific functionality, aimed at -- Allowing gem authors to export Ruby LSP extensions from their own gems -- Allowing LSP features to be enhanced by extensions present in the application the developer is currently working on +- Allowing gem authors to export Ruby LSP addons from their own gems +- Allowing LSP features to be enhanced by addons present in the application the developer is currently working on - Not requiring extra configuration from the user - Seamlessly integrating with the base features of the Ruby LSP ## Guidelines -When building a Ruby LSP extension, refer to these guidelines to ensure a good developer experience. +When building a Ruby LSP addon, refer to these guidelines to ensure a good developer experience. - Performance over features. A single slow request may result in lack of responsiveness in the editor - There are two types of LSP requests: automatic (e.g.: semantic highlighting) and user initiated (go to definition). The performance of automatic requests is critical for responsiveness as they are executed every time the user types - Avoid duplicate work where possible. If something can be computed once and memoized, like configurations, do it -- Do not mutate LSP state directly. Extensions sometimes have access to important state such as document objects, which +- Do not mutate LSP state directly. Addons sometimes have access to important state such as document objects, which should never be mutated directly, but instead through the mechanisms provided by the LSP specification - like text edits - Do not overnotify users. It's generally annoying and diverts attention from the current task -## Building a Ruby LSP extension +## Building a Ruby LSP addon -**Note**: the Ruby LSP uses [Sorbet](https://sorbet.org/). We recommend using Sorbet in extensions as well, which allows +**Note**: the Ruby LSP uses [Sorbet](https://sorbet.org/). We recommend using Sorbet in addons as well, which allows authors to benefit from types declared by the Ruby LSP. -As an example, check out [Ruby LSP Rails](https://github.com/Shopify/ruby-lsp-rails), which is a Ruby LSP extension to +As an example, check out [Ruby LSP Rails](https://github.com/Shopify/ruby-lsp-rails), which is a Ruby LSP addon to provide Rails related features. -### Activating the extension +### Activating the addon -The Ruby LSP discovers extensions based on the existence of an `extension.rb` file placed inside a `ruby_lsp` folder. -For example, `my_gem/lib/ruby_lsp/my_gem/extension.rb`. This file must declare the extension class, which can be used to -perform any necessary activation when the server starts. +The Ruby LSP discovers addons based on the existence of an `addon.rb` file placed inside a `ruby_lsp` folder. For +example, `my_gem/lib/ruby_lsp/my_gem/addon.rb`. This file must declare the addon class, which can be used to perform any +necessary activation when the server starts. ```ruby # frozen_string_literal: true -require "ruby_lsp/extension" +require "ruby_lsp/addon" module RubyLsp module MyGem - class Extension < ::RubyLsp::Extension + class Addon < ::RubyLsp::Addon extend T::Sig # Performs any activation that needs to happen once when the language server is booted @@ -72,7 +72,7 @@ module RubyLsp def deactivate end - # Returns the name of the extension + # Returns the name of the addon sig { override.returns(String) } def name "Ruby LSP My Gem" @@ -84,9 +84,9 @@ end ### Enhancing features -All Ruby LSP requests are listeners that handle specific node types. To enhance a request, the extension must create a +All Ruby LSP requests are listeners that handle specific node types. To enhance a request, the addon must create a listener that will collect extra results that will be automatically appended to the base language server response. -Additionally, `Extension` has to implement a factory method that instantiates the listener. +Additionally, `Addon` has to implement a factory method that instantiates the listener. For example: to add a message on hover saying "Hello!" on top of the base hover behavior of the Ruby LSP, we can use the following listener implementation. @@ -96,7 +96,7 @@ following listener implementation. module RubyLsp module MyGem - class Extension < ::RubyLsp::Extension + class Addon < ::RubyLsp::Addon extend T::Sig sig { override.void } @@ -123,7 +123,7 @@ module RubyLsp end def create_hover_listener(nesting, index emitter, message_queue) # Use the listener factory methods to instantiate listeners with parameters sent by the LSP combined with any - # pre-computed information in the extension. These factory methods are invoked on every request + # pre-computed information in the addon. These factory methods are invoked on every request Hover.new(@config, emitter, message_queue) end end @@ -150,7 +150,8 @@ module RubyLsp @_response = T.let(nil, ResponseType) @config = config - # Register that this listener will handle `on_constant_read` events (i.e.: whenever a constant read is found in the code) + # Register that this listener will handle `on_constant_read` events (i.e.: whenever a constant read is found in + # the code) emitter.register(self, :on_constant_read) end @@ -170,12 +171,12 @@ end ### Registering formatters -Gems may also provide a formatter to be used by the Ruby LSP. To do that, the extension must create a formatter runner -and register it. The formatter is used if the `rubyLsp.formatter` option configured by the user matches the identifier +Gems may also provide a formatter to be used by the Ruby LSP. To do that, the addon must create a formatter runner and +register it. The formatter is used if the `rubyLsp.formatter` option configured by the user matches the identifier registered. ```ruby -class MyFormatterRubyLspExtension < RubyLsp::Extension +class MyFormatterRubyLspAddon < RubyLsp::Addon def name "My Formatter" end @@ -192,7 +193,7 @@ end class MyFormatterRunner # Make it a singleton class include Singleton - # If using Sorbet to develop the extension, then include this interface to make sure the class is properly implemented + # If using Sorbet to develop the addon, then include this interface to make sure the class is properly implemented include RubyLsp::Requests::Support::FormatterRunner # Use the initialize method to perform any sort of ahead of time work. For example, reading configurations for your @@ -241,7 +242,7 @@ ensure documentation is always up to date and consistent. ```ruby require "ruby_lsp/check_docs" -# The first argument is the file list including all of the listeners declared by the extension +# The first argument is the file list including all of the listeners declared by the addon # The second argument is the file list of GIF files with the demos of all listeners RubyLsp::CheckDocs.new( FileList["#{__dir__}/lib/ruby_lsp/ruby_lsp_rails/**/*.rb"], @@ -251,17 +252,17 @@ RubyLsp::CheckDocs.new( ### Dependency constraints -While we figure out a good design for the extensions API, breaking changes are bound to happen. To avoid having your -extension accidentally break editor functionality, always restrict the dependency on the `ruby-lsp` gem based on minor -versions (breaking changes may land on minor versions until we reach v1.0.0). +While we figure out a good design for the addons API, breaking changes are bound to happen. To avoid having your addon +accidentally break editor functionality, always restrict the dependency on the `ruby-lsp` gem based on minor versions +(breaking changes may land on minor versions until we reach v1.0.0). ```ruby spec.add_dependency("ruby-lsp", "~> 0.6.0") ``` -### Testing extensions +### Testing addons -When writing unit tests for extensions, it's essential to keep in mind that code is rarely in its final state while the +When writing unit tests for addons, it's essential to keep in mind that code is rarely in its final state while the developer is coding. Therefore, be sure to test valid scenarios where the code is still incomplete. For example, if you are writing a feature related to `require`, do not test `require "library"` exclusively. Consider diff --git a/README.md b/README.md index 30e6152cf..5e2280c64 100644 --- a/README.md +++ b/README.md @@ -46,14 +46,14 @@ See the [documentation](https://shopify.github.io/ruby-lsp) for more in-depth de For creating rich themes for Ruby using the semantic highlighting information, see the [semantic highlighting documentation](SEMANTIC_HIGHLIGHTING.md). -### Extensions +### Addons -The Ruby LSP provides a server extension system that allows other gems to enhance the base functionality with more -editor features. This is the mechanism that powers extensions like +The Ruby LSP provides an addon system that allows other gems to enhance the base functionality with more editor +features. This is the mechanism that powers addons like - [Ruby LSP Rails](https://github.com/Shopify/ruby-lsp-rails) -For instructions on how to create extensions, see the [server extensions documentation](SERVER_EXTENSIONS.md). +For instructions on how to create addons, see the [addons documentation](ADDONS.md). ## Learn More diff --git a/exe/ruby-lsp-check b/exe/ruby-lsp-check index 8a034df30..afa02936c 100755 --- a/exe/ruby-lsp-check +++ b/exe/ruby-lsp-check @@ -16,7 +16,7 @@ end require_relative "../lib/ruby_lsp/internal" -RubyLsp::Extension.load_extensions +RubyLsp::Addon.load_addons T::Utils.run_all_sig_blocks diff --git a/lib/ruby_lsp/extension.rb b/lib/ruby_lsp/addon.rb similarity index 68% rename from lib/ruby_lsp/extension.rb rename to lib/ruby_lsp/addon.rb index 4d82ecb78..97ae96675 100644 --- a/lib/ruby_lsp/extension.rb +++ b/lib/ruby_lsp/addon.rb @@ -2,24 +2,24 @@ # frozen_string_literal: true module RubyLsp - # To register an extension, inherit from this class and implement both `name` and `activate` + # To register an addon, inherit from this class and implement both `name` and `activate` # # # Example # # ```ruby # module MyGem - # class MyExtension < Extension + # class MyAddon < Addon # def activate # # Perform any relevant initialization # end # # def name - # "My extension name" + # "My addon name" # end # end # end # ``` - class Extension + class Addon extend T::Sig extend T::Helpers @@ -28,37 +28,37 @@ class Extension class << self extend T::Sig - # Automatically track and instantiate extension classes - sig { params(child_class: T.class_of(Extension)).void } + # Automatically track and instantiate addon classes + sig { params(child_class: T.class_of(Addon)).void } def inherited(child_class) - extensions << child_class.new + addons << child_class.new super end - sig { returns(T::Array[Extension]) } - def extensions - @extensions ||= T.let([], T.nilable(T::Array[Extension])) + sig { returns(T::Array[Addon]) } + def addons + @addons ||= T.let([], T.nilable(T::Array[Addon])) end - # Discovers and loads all extensions. Returns the list of activated extensions - sig { returns(T::Array[Extension]) } - def load_extensions - # Require all extensions entry points, which should be placed under - # `some_gem/lib/ruby_lsp/your_gem_name/extension.rb` - Gem.find_files("ruby_lsp/**/extension.rb").each do |extension| - require File.expand_path(extension) + # Discovers and loads all addons. Returns the list of activated addons + sig { returns(T::Array[Addon]) } + def load_addons + # Require all addons entry points, which should be placed under + # `some_gem/lib/ruby_lsp/your_gem_name/addon.rb` + Gem.find_files("ruby_lsp/**/addon.rb").each do |addon| + require File.expand_path(addon) rescue => e warn(e.message) warn(e.backtrace.to_s) # rubocop:disable Lint/RedundantStringCoercion end - # Activate each one of the discovered extensions. If any problems occur in the extensions, we don't want to + # Activate each one of the discovered addons. If any problems occur in the addons, we don't want to # fail to boot the server - extensions.each do |extension| - extension.activate + addons.each do |addon| + addon.activate nil rescue => e - extension.add_error(e) + addon.add_error(e) end end end @@ -92,17 +92,17 @@ def backtraces @errors.filter_map(&:backtrace).join("\n\n") end - # Each extension should implement `MyExtension#activate` and use to perform any sort of initialization, such as + # Each addon should implement `MyAddon#activate` and use to perform any sort of initialization, such as # reading information into memory or even spawning a separate process sig { abstract.void } def activate; end - # Each extension should implement `MyExtension#deactivate` and use to perform any clean up, like shutting down a + # Each addon should implement `MyAddon#deactivate` and use to perform any clean up, like shutting down a # child process sig { abstract.void } def deactivate; end - # Extensions should override the `name` method to return the extension name + # Addons should override the `name` method to return the addon name sig { abstract.returns(String) } def name; end diff --git a/lib/ruby_lsp/check_docs.rb b/lib/ruby_lsp/check_docs.rb index d18e62aec..07c1f9955 100644 --- a/lib/ruby_lsp/check_docs.rb +++ b/lib/ruby_lsp/check_docs.rb @@ -5,9 +5,9 @@ require "objspace" module RubyLsp - # This rake task checks that all requests or extensions are fully documented. Add the rake task to your Rakefile and - # specify the absolute path for all files that must be required in order to discover all listeners and their - # related GIFs + # This rake task checks that all requests or addons are fully documented. Add the rake task to your Rakefile and + # specify the absolute path for all files that must be required in order to discover all listeners and their related + # GIFs # # # Rakefile # request_files = FileList.new("#{__dir__}/lib/ruby_lsp/requests/*.rb") do |fl| @@ -53,8 +53,7 @@ def run_task # documented features = ObjectSpace.each_object(Class).filter_map do |k| klass = T.unsafe(k) - klass if klass < RubyLsp::Requests::BaseRequest || - (klass < RubyLsp::Listener && klass != RubyLsp::ExtensibleListener) + klass if klass < Requests::BaseRequest || (klass < Listener && klass != ExtensibleListener) end missing_docs = T.let(Hash.new { |h, k| h[k] = [] }, T::Hash[String, T::Array[String]]) @@ -82,14 +81,14 @@ def run_task T.must(missing_docs[class_name]) << "No documentation found" elsif !%r{\(https://microsoft.github.io/language-server-protocol/specification#.*\)}.match?(documentation) T.must(missing_docs[class_name]) << <<~DOCS - Missing specification link. Requests and extensions should include a link to the LSP specification for the + Missing specification link. Requests and addons should include a link to the LSP specification for the related feature. For example: [Inlay hint](https://microsoft.github.io/language-server-protocol/specification#textDocument_inlayHint) DOCS elsif !documentation.include?("# Example") T.must(missing_docs[class_name]) << <<~DOCS - Missing example. Requests and extensions should include a code example that explains what the feature does. + Missing example. Requests and addons should include a code example that explains what the feature does. # # Example # ```ruby @@ -99,7 +98,7 @@ def run_task DOCS elsif !/\[.* demo\]\(.*\.gif\)/.match?(documentation) T.must(missing_docs[class_name]) << <<~DOCS - Missing demonstration GIF. Each request and extension must be documented with a GIF that shows the feature + Missing demonstration GIF. Each request and addon must be documented with a GIF that shows the feature working. For example: # [Inlay hint demo](../../inlay_hint.gif) diff --git a/lib/ruby_lsp/executor.rb b/lib/ruby_lsp/executor.rb index 69961094e..336a26a55 100644 --- a/lib/ruby_lsp/executor.rb +++ b/lib/ruby_lsp/executor.rb @@ -42,20 +42,20 @@ def run(request) when "initialize" initialize_request(request.dig(:params)) when "initialized" - Extension.load_extensions + Addon.load_addons - errored_extensions = Extension.extensions.select(&:error?) + errored_addons = Addon.addons.select(&:error?) - if errored_extensions.any? + if errored_addons.any? @message_queue << Notification.new( message: "window/showMessage", params: Interface::ShowMessageParams.new( type: Constant::MessageType::WARNING, - message: "Error loading extensions:\n\n#{errored_extensions.map(&:formatted_errors).join("\n\n")}", + message: "Error loading addons:\n\n#{errored_addons.map(&:formatted_errors).join("\n\n")}", ), ) - warn(errored_extensions.map(&:backtraces).join("\n\n")) + warn(errored_addons.map(&:backtraces).join("\n\n")) end perform_initial_indexing diff --git a/lib/ruby_lsp/internal.rb b/lib/ruby_lsp/internal.rb index b54b96f8d..fd9fd4ea8 100644 --- a/lib/ruby_lsp/internal.rb +++ b/lib/ruby_lsp/internal.rb @@ -21,5 +21,5 @@ require "ruby_lsp/requests" require "ruby_lsp/listener" require "ruby_lsp/store" -require "ruby_lsp/extension" +require "ruby_lsp/addon" require "ruby_lsp/requests/support/rubocop_runner" diff --git a/lib/ruby_lsp/listener.rb b/lib/ruby_lsp/listener.rb index 0f73a4327..8a3ef21bf 100644 --- a/lib/ruby_lsp/listener.rb +++ b/lib/ruby_lsp/listener.rb @@ -31,7 +31,7 @@ def response def _response; end end - # ExtensibleListener is an abstract class to be used by requests that accept extensions. + # ExtensibleListener is an abstract class to be used by requests that accept addons. class ExtensibleListener < Listener extend T::Sig extend T::Generic @@ -48,7 +48,7 @@ def initialize(emitter, message_queue) super @response_merged = T.let(false, T::Boolean) @external_listeners = T.let( - Extension.extensions.filter_map do |ext| + Addon.addons.filter_map do |ext| initialize_external_listener(ext) end, T::Array[RubyLsp::Listener[ResponseType]], @@ -56,7 +56,7 @@ def initialize(emitter, message_queue) end # Merge responses from all external listeners into the base listener's response. We do this to return a single - # response to the editor including the results of all extensions + # response to the editor including the results of all addons sig { void } def merge_external_listeners_responses! @external_listeners.each { |l| merge_response!(l) } @@ -69,14 +69,15 @@ def response end sig do - abstract.params(extension: RubyLsp::Extension).returns(T.nilable(RubyLsp::Listener[ResponseType])) + abstract.params(addon: RubyLsp::Addon).returns(T.nilable(RubyLsp::Listener[ResponseType])) end - def initialize_external_listener(extension); end + def initialize_external_listener(addon); end - # Does nothing by default. Requests that accept extensions should override this method to define how to merge - # responses coming from external listeners + # Does nothing by default. Requests that accept addons should override this method to define how to merge responses + # coming from external listeners sig { abstract.params(other: Listener[T.untyped]).returns(T.self_type) } def merge_response!(other) end end + private_constant(:ExtensibleListener) end diff --git a/lib/ruby_lsp/requests/code_lens.rb b/lib/ruby_lsp/requests/code_lens.rb index b60c7b2db..cb898edcd 100644 --- a/lib/ruby_lsp/requests/code_lens.rb +++ b/lib/ruby_lsp/requests/code_lens.rb @@ -129,9 +129,9 @@ def after_call(node) @visibility_stack.push([prev_visibility, prev_visibility]) end - sig { override.params(extension: RubyLsp::Extension).returns(T.nilable(Listener[ResponseType])) } - def initialize_external_listener(extension) - extension.create_code_lens_listener(@uri, @emitter, @message_queue) + sig { override.params(addon: Addon).returns(T.nilable(Listener[ResponseType])) } + def initialize_external_listener(addon) + addon.create_code_lens_listener(@uri, @emitter, @message_queue) end sig { override.params(other: Listener[ResponseType]).returns(T.self_type) } diff --git a/lib/ruby_lsp/requests/definition.rb b/lib/ruby_lsp/requests/definition.rb index 753f050d3..1e7d8037e 100644 --- a/lib/ruby_lsp/requests/definition.rb +++ b/lib/ruby_lsp/requests/definition.rb @@ -46,9 +46,9 @@ def initialize(uri, nesting, index, emitter, message_queue) emitter.register(self, :on_call, :on_constant_read, :on_constant_path) end - sig { override.params(ext: Extension).returns(T.nilable(RubyLsp::Listener[ResponseType])) } - def initialize_external_listener(ext) - ext.create_definition_listener(@uri, @nesting, @index, @emitter, @message_queue) + sig { override.params(addon: Addon).returns(T.nilable(RubyLsp::Listener[ResponseType])) } + def initialize_external_listener(addon) + addon.create_definition_listener(@uri, @nesting, @index, @emitter, @message_queue) end sig { override.params(other: Listener[ResponseType]).returns(T.self_type) } diff --git a/lib/ruby_lsp/requests/document_symbol.rb b/lib/ruby_lsp/requests/document_symbol.rb index ec3f74253..1b1307a34 100644 --- a/lib/ruby_lsp/requests/document_symbol.rb +++ b/lib/ruby_lsp/requests/document_symbol.rb @@ -76,9 +76,9 @@ def initialize(emitter, message_queue) ) end - sig { override.params(extension: RubyLsp::Extension).returns(T.nilable(Listener[ResponseType])) } - def initialize_external_listener(extension) - extension.create_document_symbol_listener(@emitter, @message_queue) + sig { override.params(addon: Addon).returns(T.nilable(Listener[ResponseType])) } + def initialize_external_listener(addon) + addon.create_document_symbol_listener(@emitter, @message_queue) end # Merges responses from other listeners diff --git a/lib/ruby_lsp/requests/hover.rb b/lib/ruby_lsp/requests/hover.rb index bb792d644..4cca261b7 100644 --- a/lib/ruby_lsp/requests/hover.rb +++ b/lib/ruby_lsp/requests/hover.rb @@ -49,9 +49,9 @@ def initialize(index, nesting, emitter, message_queue) emitter.register(self, :on_constant_read, :on_constant_write, :on_constant_path) end - sig { override.params(extension: RubyLsp::Extension).returns(T.nilable(Listener[ResponseType])) } - def initialize_external_listener(extension) - extension.create_hover_listener(@nesting, @index, @emitter, @message_queue) + sig { override.params(addon: Addon).returns(T.nilable(Listener[ResponseType])) } + def initialize_external_listener(addon) + addon.create_hover_listener(@nesting, @index, @emitter, @message_queue) end # Merges responses from other hover listeners diff --git a/lib/ruby_lsp/requests/support/common.rb b/lib/ruby_lsp/requests/support/common.rb index 5d98bcf2b..e9b72277d 100644 --- a/lib/ruby_lsp/requests/support/common.rb +++ b/lib/ruby_lsp/requests/support/common.rb @@ -5,8 +5,9 @@ module RubyLsp module Requests module Support module Common - # WARNING: Methods in this class may be used by Ruby LSP extensions such as https://github.com/Shopify/ruby-lsp-rails, - # or extensions by created by developers outside of Shopify, so be cautious of changing anything. + # WARNING: Methods in this class may be used by Ruby LSP addons such as + # https://github.com/Shopify/ruby-lsp-rails, or addons by created by developers outside of Shopify, so be + # cautious of changing anything. extend T::Sig sig { params(node: YARP::Node).returns(Interface::Range) } diff --git a/lib/ruby_lsp/server.rb b/lib/ruby_lsp/server.rb index 1f232c98c..fdb9fcc03 100644 --- a/lib/ruby_lsp/server.rb +++ b/lib/ruby_lsp/server.rb @@ -86,7 +86,7 @@ def start @message_dispatcher.join @store.clear - Extension.extensions.each(&:deactivate) + Addon.addons.each(&:deactivate) finalize_request(Result.new(response: nil), request) when "exit" # We return zero if shutdown has already been received or one otherwise as per the recommendation in the spec diff --git a/test/addon_test.rb b/test/addon_test.rb new file mode 100644 index 000000000..0f0b67366 --- /dev/null +++ b/test/addon_test.rb @@ -0,0 +1,64 @@ +# typed: true +# frozen_string_literal: true + +require "test_helper" + +module RubyLsp + class AddonTest < Minitest::Test + def setup + @addon = Class.new(Addon) do + attr_reader :activated + + def activate + @activated = true + end + + def name + "My Addon" + end + end + end + + def teardown + Addon.addons.clear + end + + def test_registering_an_addon_invokes_activate_on_initialized + message_queue = Thread::Queue.new + Executor.new(RubyLsp::Store.new, message_queue).execute({ method: "initialized" }) + + addon_instance = T.must(Addon.addons.find { |addon| addon.is_a?(@addon) }) + assert_predicate(addon_instance, :activated) + ensure + T.must(message_queue).close + end + + def test_addons_are_automatically_tracked + assert( + Addon.addons.any? { |addon| addon.is_a?(@addon) }, + "Expected addon to be automatically tracked", + ) + end + + def test_load_addons_returns_errors + Class.new(Addon) do + def activate + raise StandardError, "Failed to activate" + end + + def name + "My addon" + end + end + + Addon.load_addons + error_addon = T.must(Addon.addons.find(&:error?)) + + assert_predicate(error_addon, :error?) + assert_equal(<<~MESSAGE, error_addon.formatted_errors) + My addon: + Failed to activate + MESSAGE + end + end +end diff --git a/test/expectations/expectations_test_runner.rb b/test/expectations/expectations_test_runner.rb index 06baf54bc..7439bc677 100644 --- a/test/expectations/expectations_test_runner.rb +++ b/test/expectations/expectations_test_runner.rb @@ -125,11 +125,11 @@ def ruby_requirement_magic_comment_version(fixture_path) private - def test_extension(extension_creation_method, source:) + def test_addon(addon_creation_method, source:) RubyLsp::DependencyDetector.const_set(:HAS_TYPECHECKER, false) message_queue = Thread::Queue.new - send(extension_creation_method) + send(addon_creation_method) store = RubyLsp::Store.new uri = URI::Generic.from_path(path: "/fake.rb") @@ -143,7 +143,7 @@ def test_extension(extension_creation_method, source:) yield(executor) ensure - RubyLsp::Extension.extensions.clear + RubyLsp::Addon.addons.clear RubyLsp::DependencyDetector.const_set(:HAS_TYPECHECKER, true) T.must(message_queue).close end diff --git a/test/extension_test.rb b/test/extension_test.rb deleted file mode 100644 index 78d294e64..000000000 --- a/test/extension_test.rb +++ /dev/null @@ -1,64 +0,0 @@ -# typed: true -# frozen_string_literal: true - -require "test_helper" - -module RubyLsp - class ExtensionTest < Minitest::Test - def setup - @extension = Class.new(Extension) do - attr_reader :activated - - def activate - @activated = true - end - - def name - "My extension" - end - end - end - - def teardown - Extension.extensions.clear - end - - def test_registering_an_extension_invokes_activate_on_initialized - message_queue = Thread::Queue.new - Executor.new(RubyLsp::Store.new, message_queue).execute({ method: "initialized" }) - - extension_instance = T.must(Extension.extensions.find { |ext| ext.is_a?(@extension) }) - assert_predicate(extension_instance, :activated) - ensure - T.must(message_queue).close - end - - def test_extensions_are_automatically_tracked - assert( - Extension.extensions.any? { |ext| ext.is_a?(@extension) }, - "Expected extension to be automatically tracked", - ) - end - - def test_load_extensions_returns_errors - Class.new(Extension) do - def activate - raise StandardError, "Failed to activate" - end - - def name - "My extension" - end - end - - Extension.load_extensions - error_extension = T.must(Extension.extensions.find(&:error?)) - - assert_predicate(error_extension, :error?) - assert_equal(<<~MESSAGE, error_extension.formatted_errors) - My extension: - Failed to activate - MESSAGE - end - end -end diff --git a/test/requests/code_lens_expectations_test.rb b/test/requests/code_lens_expectations_test.rb index 04094515a..860ae52b2 100644 --- a/test/requests/code_lens_expectations_test.rb +++ b/test/requests/code_lens_expectations_test.rb @@ -97,12 +97,12 @@ def test_bar; end assert_empty(response) end - def test_code_lens_extensions + def test_code_lens_addons source = <<~RUBY class Test < Minitest::Test; end RUBY - test_extension(:create_code_lens_extension, source: source) do |executor| + test_addon(:create_code_lens_addon, source: source) do |executor| response = executor.execute({ method: "textDocument/codeLens", params: { textDocument: { uri: "file:///fake.rb" }, position: { line: 1, character: 2 } }, @@ -118,12 +118,12 @@ class Test < Minitest::Test; end private - def create_code_lens_extension - Class.new(RubyLsp::Extension) do + def create_code_lens_addon + Class.new(RubyLsp::Addon) do def activate; end def name - "CodeLensExtension" + "CodeLensAddon" end def create_code_lens_listener(uri, emitter, message_queue) diff --git a/test/requests/definition_expectations_test.rb b/test/requests/definition_expectations_test.rb index 8c7cdff21..804eaca32 100644 --- a/test/requests/definition_expectations_test.rb +++ b/test/requests/definition_expectations_test.rb @@ -208,12 +208,12 @@ class A T.must(message_queue).close end - def test_definition_extensions + def test_definition_addons source = <<~RUBY RubyLsp RUBY - test_extension(:create_definition_extension, source: source) do |executor| + test_addon(:create_definition_addon, source: source) do |executor| index = executor.instance_variable_get(:@index) index.index_single( RubyIndexer::IndexablePath.new( @@ -231,18 +231,18 @@ def test_definition_extensions assert_equal(2, response.size) assert_match("class_reference_target.rb", response[0].uri) - assert_match("generated_by_extension.rb", response[1].uri) + assert_match("generated_by_addon.rb", response[1].uri) end end private - def create_definition_extension - Class.new(RubyLsp::Extension) do + def create_definition_addon + Class.new(RubyLsp::Addon) do def activate; end def name - "Definition Extension" + "Definition Addon" end def create_definition_listener(uri, nesting, index, emitter, message_queue) @@ -258,7 +258,7 @@ def initialize(uri, _, _, emitter, message_queue) def on_constant_read(node) location = node.location @_response = RubyLsp::Interface::Location.new( - uri: "file:///generated_by_extension.rb", + uri: "file:///generated_by_addon.rb", range: RubyLsp::Interface::Range.new( start: RubyLsp::Interface::Position.new( line: location.start_line - 1, diff --git a/test/requests/document_symbol_expectations_test.rb b/test/requests/document_symbol_expectations_test.rb index e5cafcd26..162bbeb5b 100644 --- a/test/requests/document_symbol_expectations_test.rb +++ b/test/requests/document_symbol_expectations_test.rb @@ -7,13 +7,13 @@ class DocumentSymbolExpectationsTest < ExpectationsTestRunner expectations_tests RubyLsp::Requests::DocumentSymbol, "document_symbol" - def test_document_symbol_extensions + def test_document_symbol_addons source = <<~RUBY test "foo" do end RUBY - test_extension(:create_document_symbol_extension, source: source) do |executor| + test_addon(:create_document_symbol_addon, source: source) do |executor| response = executor.execute({ method: "textDocument/documentSymbol", params: { textDocument: { uri: "file:///fake.rb" } }, @@ -26,12 +26,12 @@ def test_document_symbol_extensions private - def create_document_symbol_extension - Class.new(RubyLsp::Extension) do + def create_document_symbol_addon + Class.new(RubyLsp::Addon) do def activate; end def name - "Document SymbolsExtension" + "Document SymbolsAddon" end def create_document_symbol_listener(emitter, message_queue) diff --git a/test/requests/hover_expectations_test.rb b/test/requests/hover_expectations_test.rb index bfa6a0624..6b6011a66 100644 --- a/test/requests/hover_expectations_test.rb +++ b/test/requests/hover_expectations_test.rb @@ -94,7 +94,7 @@ class A T.must(message_queue).close end - def test_hover_extensions + def test_hover_addons source = <<~RUBY # Hello class Post @@ -103,7 +103,7 @@ class Post Post RUBY - test_extension(:create_hover_extension, source: source) do |executor| + test_addon(:create_hover_addon, source: source) do |executor| response = executor.execute({ method: "textDocument/hover", params: { textDocument: { uri: "file:///fake.rb" }, position: { line: 4, character: 0 } }, @@ -115,12 +115,12 @@ class Post private - def create_hover_extension - Class.new(RubyLsp::Extension) do + def create_hover_addon + Class.new(RubyLsp::Addon) do def activate; end def name - "HoverExtension" + "HoverAddon" end def create_hover_listener(nesting, index, emitter, message_queue)