ruby-lsp 0.17.11 → 0.17.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/exe/ruby-lsp +4 -4
- data/exe/ruby-lsp-check +1 -1
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +20 -3
- data/lib/ruby_indexer/lib/ruby_indexer/enhancement.rb +26 -0
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +23 -0
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +76 -2
- data/lib/ruby_indexer/ruby_indexer.rb +1 -8
- data/lib/ruby_indexer/test/configuration_test.rb +1 -1
- data/lib/ruby_indexer/test/enhancements_test.rb +197 -0
- data/lib/ruby_lsp/addon.rb +9 -4
- data/lib/ruby_lsp/base_server.rb +7 -2
- data/lib/ruby_lsp/document.rb +0 -30
- data/lib/ruby_lsp/global_state.rb +33 -13
- data/lib/ruby_lsp/listeners/completion.rb +5 -5
- data/lib/ruby_lsp/listeners/definition.rb +3 -3
- data/lib/ruby_lsp/listeners/hover.rb +9 -6
- data/lib/ruby_lsp/listeners/signature_help.rb +1 -1
- data/lib/ruby_lsp/requests/code_action_resolve.rb +105 -7
- data/lib/ruby_lsp/requests/code_actions.rb +6 -0
- data/lib/ruby_lsp/requests/completion.rb +1 -1
- data/lib/ruby_lsp/requests/completion_resolve.rb +1 -0
- data/lib/ruby_lsp/requests/definition.rb +1 -1
- data/lib/ruby_lsp/requests/hover.rb +1 -1
- data/lib/ruby_lsp/requests/signature_help.rb +1 -1
- data/lib/ruby_lsp/requests/support/common.rb +2 -2
- data/lib/ruby_lsp/ruby_document.rb +64 -0
- data/lib/ruby_lsp/server.rb +33 -11
- data/lib/ruby_lsp/utils.rb +12 -0
- metadata +5 -3
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 042725d7afce428b5c024933a7101151f2f69c57cbb40b4f938384c13d6c974b
         | 
| 4 | 
            +
              data.tar.gz: bec8636d402451e1009e87ddd98e6fdedc062643e614b596a0698ffcfcc9b271
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 6a6adb40a9ccaaf916f46c041f3eb2cc7a81be73c5d3bf7c05a45472cd9d08e5a9dd04d804b8e15f091645d420037e324368ccbcc619311a1c60c81f241c1b89
         | 
| 7 | 
            +
              data.tar.gz: d6b19c8d02cfab8dca9e4d675e8ee549e8b392631a6080757c0413da29a07d1db411d9360b7553a798805d9e6faac7318863e35ba664488c5eb41c869a7e734a
         | 
    
        data/VERSION
    CHANGED
    
    | @@ -1 +1 @@ | |
| 1 | 
            -
            0.17. | 
| 1 | 
            +
            0.17.13
         | 
    
        data/exe/ruby-lsp
    CHANGED
    
    | @@ -112,20 +112,20 @@ if options[:time_index] | |
| 112 112 | 
             
            end
         | 
| 113 113 |  | 
| 114 114 | 
             
            if options[:doctor]
         | 
| 115 | 
            +
              index = RubyIndexer::Index.new
         | 
| 116 | 
            +
             | 
| 115 117 | 
             
              if File.exist?(".index.yml")
         | 
| 116 118 | 
             
                begin
         | 
| 117 119 | 
             
                  config = YAML.parse_file(".index.yml").to_ruby
         | 
| 118 120 | 
             
                rescue => e
         | 
| 119 121 | 
             
                  abort("Error parsing config: #{e.message}")
         | 
| 120 122 | 
             
                end
         | 
| 121 | 
            -
                 | 
| 123 | 
            +
                index.configuration.apply_config(config)
         | 
| 122 124 | 
             
              end
         | 
| 123 125 |  | 
| 124 | 
            -
              index = RubyIndexer::Index.new
         | 
| 125 | 
            -
             | 
| 126 126 | 
             
              puts "Globbing for indexable files"
         | 
| 127 127 |  | 
| 128 | 
            -
               | 
| 128 | 
            +
              index.configuration.indexables.each do |indexable|
         | 
| 129 129 | 
             
                puts "indexing: #{indexable.full_path}"
         | 
| 130 130 | 
             
                index.index_single(indexable)
         | 
| 131 131 | 
             
              end
         | 
    
        data/exe/ruby-lsp-check
    CHANGED
    
    | @@ -44,7 +44,7 @@ puts "\n" | |
| 44 44 | 
             
            puts "Verifying that indexing executes successfully. This may take a while..."
         | 
| 45 45 |  | 
| 46 46 | 
             
            index = RubyIndexer::Index.new
         | 
| 47 | 
            -
            indexables =  | 
| 47 | 
            +
            indexables = index.configuration.indexables
         | 
| 48 48 |  | 
| 49 49 | 
             
            indexables.each_with_index do |indexable, i|
         | 
| 50 50 | 
             
              index.index_single(indexable)
         | 
| @@ -8,12 +8,22 @@ module RubyIndexer | |
| 8 8 | 
             
                OBJECT_NESTING = T.let(["Object"].freeze, T::Array[String])
         | 
| 9 9 | 
             
                BASIC_OBJECT_NESTING = T.let(["BasicObject"].freeze, T::Array[String])
         | 
| 10 10 |  | 
| 11 | 
            +
                sig { returns(T::Array[String]) }
         | 
| 12 | 
            +
                attr_reader :indexing_errors
         | 
| 13 | 
            +
             | 
| 11 14 | 
             
                sig do
         | 
| 12 | 
            -
                  params( | 
| 15 | 
            +
                  params(
         | 
| 16 | 
            +
                    index: Index,
         | 
| 17 | 
            +
                    dispatcher: Prism::Dispatcher,
         | 
| 18 | 
            +
                    parse_result: Prism::ParseResult,
         | 
| 19 | 
            +
                    file_path: String,
         | 
| 20 | 
            +
                    enhancements: T::Array[Enhancement],
         | 
| 21 | 
            +
                  ).void
         | 
| 13 22 | 
             
                end
         | 
| 14 | 
            -
                def initialize(index, dispatcher, parse_result, file_path)
         | 
| 23 | 
            +
                def initialize(index, dispatcher, parse_result, file_path, enhancements: [])
         | 
| 15 24 | 
             
                  @index = index
         | 
| 16 25 | 
             
                  @file_path = file_path
         | 
| 26 | 
            +
                  @enhancements = enhancements
         | 
| 17 27 | 
             
                  @visibility_stack = T.let([Entry::Visibility::PUBLIC], T::Array[Entry::Visibility])
         | 
| 18 28 | 
             
                  @comments_by_line = T.let(
         | 
| 19 29 | 
             
                    parse_result.comments.to_h do |c|
         | 
| @@ -29,6 +39,7 @@ module RubyIndexer | |
| 29 39 |  | 
| 30 40 | 
             
                  # A stack of namespace entries that represent where we currently are. Used to properly assign methods to an owner
         | 
| 31 41 | 
             
                  @owner_stack = T.let([], T::Array[Entry::Namespace])
         | 
| 42 | 
            +
                  @indexing_errors = T.let([], T::Array[String])
         | 
| 32 43 |  | 
| 33 44 | 
             
                  dispatcher.register(
         | 
| 34 45 | 
             
                    self,
         | 
| @@ -279,6 +290,12 @@ module RubyIndexer | |
| 279 290 | 
             
                  when :private
         | 
| 280 291 | 
             
                    @visibility_stack.push(Entry::Visibility::PRIVATE)
         | 
| 281 292 | 
             
                  end
         | 
| 293 | 
            +
             | 
| 294 | 
            +
                  @enhancements.each do |enhancement|
         | 
| 295 | 
            +
                    enhancement.on_call_node(@index, @owner_stack.last, node, @file_path)
         | 
| 296 | 
            +
                  rescue StandardError => e
         | 
| 297 | 
            +
                    @indexing_errors << "Indexing error in #{@file_path} with '#{enhancement.class.name}' enhancement: #{e.message}"
         | 
| 298 | 
            +
                  end
         | 
| 282 299 | 
             
                end
         | 
| 283 300 |  | 
| 284 301 | 
             
                sig { params(node: Prism::CallNode).void }
         | 
| @@ -535,7 +552,7 @@ module RubyIndexer | |
| 535 552 | 
             
                    comment_content = comment.location.slice.chomp
         | 
| 536 553 |  | 
| 537 554 | 
             
                    # invalid encodings would raise an "invalid byte sequence" exception
         | 
| 538 | 
            -
                    if !comment_content.valid_encoding? || comment_content.match?( | 
| 555 | 
            +
                    if !comment_content.valid_encoding? || comment_content.match?(@index.configuration.magic_comment_regex)
         | 
| 539 556 | 
             
                      next
         | 
| 540 557 | 
             
                    end
         | 
| 541 558 |  | 
| @@ -0,0 +1,26 @@ | |
| 1 | 
            +
            # typed: strict
         | 
| 2 | 
            +
            # frozen_string_literal: true
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module RubyIndexer
         | 
| 5 | 
            +
              module Enhancement
         | 
| 6 | 
            +
                extend T::Sig
         | 
| 7 | 
            +
                extend T::Helpers
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                interface!
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                requires_ancestor { Object }
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                # The `on_extend` indexing enhancement is invoked whenever an extend is encountered in the code. It can be used to
         | 
| 14 | 
            +
                # register for an included callback, similar to what `ActiveSupport::Concern` does in order to auto-extend the
         | 
| 15 | 
            +
                # `ClassMethods` modules
         | 
| 16 | 
            +
                sig do
         | 
| 17 | 
            +
                  abstract.params(
         | 
| 18 | 
            +
                    index: Index,
         | 
| 19 | 
            +
                    owner: T.nilable(Entry::Namespace),
         | 
| 20 | 
            +
                    node: Prism::CallNode,
         | 
| 21 | 
            +
                    file_path: String,
         | 
| 22 | 
            +
                  ).void
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
                def on_call_node(index, owner, node, file_path); end
         | 
| 25 | 
            +
              end
         | 
| 26 | 
            +
            end
         | 
| @@ -342,6 +342,19 @@ module RubyIndexer | |
| 342 342 |  | 
| 343 343 | 
             
                    "(#{first_signature.format})"
         | 
| 344 344 | 
             
                  end
         | 
| 345 | 
            +
             | 
| 346 | 
            +
                  sig { returns(String) }
         | 
| 347 | 
            +
                  def formatted_signatures
         | 
| 348 | 
            +
                    overloads_count = signatures.size
         | 
| 349 | 
            +
                    case overloads_count
         | 
| 350 | 
            +
                    when 1
         | 
| 351 | 
            +
                      ""
         | 
| 352 | 
            +
                    when 2
         | 
| 353 | 
            +
                      "\n(+1 overload)"
         | 
| 354 | 
            +
                    else
         | 
| 355 | 
            +
                      "\n(+#{overloads_count - 1} overloads)"
         | 
| 356 | 
            +
                    end
         | 
| 357 | 
            +
                  end
         | 
| 345 358 | 
             
                end
         | 
| 346 359 |  | 
| 347 360 | 
             
                class Accessor < Member
         | 
| @@ -542,6 +555,16 @@ module RubyIndexer | |
| 542 555 | 
             
                  def decorated_parameters
         | 
| 543 556 | 
             
                    @target.decorated_parameters
         | 
| 544 557 | 
             
                  end
         | 
| 558 | 
            +
             | 
| 559 | 
            +
                  sig { returns(String) }
         | 
| 560 | 
            +
                  def formatted_signatures
         | 
| 561 | 
            +
                    @target.formatted_signatures
         | 
| 562 | 
            +
                  end
         | 
| 563 | 
            +
             | 
| 564 | 
            +
                  sig { returns(T::Array[Signature]) }
         | 
| 565 | 
            +
                  def signatures
         | 
| 566 | 
            +
                    @target.signatures
         | 
| 567 | 
            +
                  end
         | 
| 545 568 | 
             
                end
         | 
| 546 569 |  | 
| 547 570 | 
             
                # Ruby doesn't support method overloading, so a method will have only one signature.
         | 
| @@ -11,6 +11,9 @@ module RubyIndexer | |
| 11 11 | 
             
                # The minimum Jaro-Winkler similarity score for an entry to be considered a match for a given fuzzy search query
         | 
| 12 12 | 
             
                ENTRY_SIMILARITY_THRESHOLD = 0.7
         | 
| 13 13 |  | 
| 14 | 
            +
                sig { returns(Configuration) }
         | 
| 15 | 
            +
                attr_reader :configuration
         | 
| 16 | 
            +
             | 
| 14 17 | 
             
                sig { void }
         | 
| 15 18 | 
             
                def initialize
         | 
| 16 19 | 
             
                  # Holds all entries in the index using the following format:
         | 
| @@ -35,6 +38,29 @@ module RubyIndexer | |
| 35 38 |  | 
| 36 39 | 
             
                  # Holds the linearized ancestors list for every namespace
         | 
| 37 40 | 
             
                  @ancestors = T.let({}, T::Hash[String, T::Array[String]])
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                  # List of classes that are enhancing the index
         | 
| 43 | 
            +
                  @enhancements = T.let([], T::Array[Enhancement])
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                  # Map of module name to included hooks that have to be executed when we include the given module
         | 
| 46 | 
            +
                  @included_hooks = T.let(
         | 
| 47 | 
            +
                    {},
         | 
| 48 | 
            +
                    T::Hash[String, T::Array[T.proc.params(index: Index, base: Entry::Namespace).void]],
         | 
| 49 | 
            +
                  )
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                  @configuration = T.let(RubyIndexer::Configuration.new, Configuration)
         | 
| 52 | 
            +
                end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                # Register an enhancement to the index. Enhancements must conform to the `Enhancement` interface
         | 
| 55 | 
            +
                sig { params(enhancement: Enhancement).void }
         | 
| 56 | 
            +
                def register_enhancement(enhancement)
         | 
| 57 | 
            +
                  @enhancements << enhancement
         | 
| 58 | 
            +
                end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                # Register an included `hook` that will be executed when `module_name` is included into any namespace
         | 
| 61 | 
            +
                sig { params(module_name: String, hook: T.proc.params(index: Index, base: Entry::Namespace).void).void }
         | 
| 62 | 
            +
                def register_included_hook(module_name, &hook)
         | 
| 63 | 
            +
                  (@included_hooks[module_name] ||= []) << hook
         | 
| 38 64 | 
             
                end
         | 
| 39 65 |  | 
| 40 66 | 
             
                sig { params(indexable: IndexablePath).void }
         | 
| @@ -275,7 +301,7 @@ module RubyIndexer | |
| 275 301 | 
             
                    block: T.nilable(T.proc.params(progress: Integer).returns(T::Boolean)),
         | 
| 276 302 | 
             
                  ).void
         | 
| 277 303 | 
             
                end
         | 
| 278 | 
            -
                def index_all(indexable_paths:  | 
| 304 | 
            +
                def index_all(indexable_paths: @configuration.indexables, &block)
         | 
| 279 305 | 
             
                  RBSIndexer.new(self).index_ruby_core
         | 
| 280 306 | 
             
                  # Calculate how many paths are worth 1% of progress
         | 
| 281 307 | 
             
                  progress_step = (indexable_paths.length / 100.0).ceil
         | 
| @@ -296,11 +322,25 @@ module RubyIndexer | |
| 296 322 | 
             
                  dispatcher = Prism::Dispatcher.new
         | 
| 297 323 |  | 
| 298 324 | 
             
                  result = Prism.parse(content)
         | 
| 299 | 
            -
                  DeclarationListener.new( | 
| 325 | 
            +
                  listener = DeclarationListener.new(
         | 
| 326 | 
            +
                    self,
         | 
| 327 | 
            +
                    dispatcher,
         | 
| 328 | 
            +
                    result,
         | 
| 329 | 
            +
                    indexable_path.full_path,
         | 
| 330 | 
            +
                    enhancements: @enhancements,
         | 
| 331 | 
            +
                  )
         | 
| 300 332 | 
             
                  dispatcher.dispatch(result.value)
         | 
| 301 333 |  | 
| 334 | 
            +
                  indexing_errors = listener.indexing_errors.uniq
         | 
| 335 | 
            +
             | 
| 302 336 | 
             
                  require_path = indexable_path.require_path
         | 
| 303 337 | 
             
                  @require_paths_tree.insert(require_path, indexable_path) if require_path
         | 
| 338 | 
            +
             | 
| 339 | 
            +
                  if indexing_errors.any?
         | 
| 340 | 
            +
                    indexing_errors.each do |error|
         | 
| 341 | 
            +
                      $stderr.puts error
         | 
| 342 | 
            +
                    end
         | 
| 343 | 
            +
                  end
         | 
| 304 344 | 
             
                rescue Errno::EISDIR, Errno::ENOENT
         | 
| 305 345 | 
             
                  # If `path` is a directory, just ignore it and continue indexing. If the file doesn't exist, then we also ignore
         | 
| 306 346 | 
             
                  # it
         | 
| @@ -457,6 +497,12 @@ module RubyIndexer | |
| 457 497 | 
             
                    end
         | 
| 458 498 | 
             
                  end
         | 
| 459 499 |  | 
| 500 | 
            +
                  # We only need to run included hooks when linearizing singleton classes. Included hooks are typically used to add
         | 
| 501 | 
            +
                  # new singleton methods or to extend a module through an include. There's no need to support instance methods, the
         | 
| 502 | 
            +
                  # inclusion of another module or the prepending of another module, because those features are already a part of
         | 
| 503 | 
            +
                  # Ruby and can be used directly without any metaprogramming
         | 
| 504 | 
            +
                  run_included_hooks(attached_class_name, nesting) if singleton_levels > 0
         | 
| 505 | 
            +
             | 
| 460 506 | 
             
                  linearize_mixins(ancestors, namespaces, nesting)
         | 
| 461 507 | 
             
                  linearize_superclass(
         | 
| 462 508 | 
             
                    ancestors,
         | 
| @@ -570,6 +616,34 @@ module RubyIndexer | |
| 570 616 |  | 
| 571 617 | 
             
                private
         | 
| 572 618 |  | 
| 619 | 
            +
                # Runs the registered included hooks
         | 
| 620 | 
            +
                sig { params(fully_qualified_name: String, nesting: T::Array[String]).void }
         | 
| 621 | 
            +
                def run_included_hooks(fully_qualified_name, nesting)
         | 
| 622 | 
            +
                  return if @included_hooks.empty?
         | 
| 623 | 
            +
             | 
| 624 | 
            +
                  namespaces = self[fully_qualified_name]&.grep(Entry::Namespace)
         | 
| 625 | 
            +
                  return unless namespaces
         | 
| 626 | 
            +
             | 
| 627 | 
            +
                  namespaces.each do |namespace|
         | 
| 628 | 
            +
                    namespace.mixin_operations.each do |operation|
         | 
| 629 | 
            +
                      next unless operation.is_a?(Entry::Include)
         | 
| 630 | 
            +
             | 
| 631 | 
            +
                      # First we resolve the include name, so that we know the actual module being referred to in the include
         | 
| 632 | 
            +
                      resolved_modules = resolve(operation.module_name, nesting)
         | 
| 633 | 
            +
                      next unless resolved_modules
         | 
| 634 | 
            +
             | 
| 635 | 
            +
                      module_name = T.must(resolved_modules.first).name
         | 
| 636 | 
            +
             | 
| 637 | 
            +
                      # Then we grab any hooks registered for that module
         | 
| 638 | 
            +
                      hooks = @included_hooks[module_name]
         | 
| 639 | 
            +
                      next unless hooks
         | 
| 640 | 
            +
             | 
| 641 | 
            +
                      # We invoke the hooks with the index and the namespace that included the module
         | 
| 642 | 
            +
                      hooks.each { |hook| hook.call(self, namespace) }
         | 
| 643 | 
            +
                    end
         | 
| 644 | 
            +
                  end
         | 
| 645 | 
            +
                end
         | 
| 646 | 
            +
             | 
| 573 647 | 
             
                # Linearize mixins for an array of namespace entries. This method will mutate the `ancestors` array with the
         | 
| 574 648 | 
             
                # linearized ancestors of the mixins
         | 
| 575 649 | 
             
                sig do
         | 
| @@ -6,6 +6,7 @@ require "did_you_mean" | |
| 6 6 |  | 
| 7 7 | 
             
            require "ruby_indexer/lib/ruby_indexer/indexable_path"
         | 
| 8 8 | 
             
            require "ruby_indexer/lib/ruby_indexer/declaration_listener"
         | 
| 9 | 
            +
            require "ruby_indexer/lib/ruby_indexer/enhancement"
         | 
| 9 10 | 
             
            require "ruby_indexer/lib/ruby_indexer/index"
         | 
| 10 11 | 
             
            require "ruby_indexer/lib/ruby_indexer/entry"
         | 
| 11 12 | 
             
            require "ruby_indexer/lib/ruby_indexer/configuration"
         | 
| @@ -14,12 +15,4 @@ require "ruby_indexer/lib/ruby_indexer/location" | |
| 14 15 | 
             
            require "ruby_indexer/lib/ruby_indexer/rbs_indexer"
         | 
| 15 16 |  | 
| 16 17 | 
             
            module RubyIndexer
         | 
| 17 | 
            -
              @configuration = T.let(Configuration.new, Configuration)
         | 
| 18 | 
            -
             | 
| 19 | 
            -
              class << self
         | 
| 20 | 
            -
                extend T::Sig
         | 
| 21 | 
            -
             | 
| 22 | 
            -
                sig { returns(Configuration) }
         | 
| 23 | 
            -
                attr_reader :configuration
         | 
| 24 | 
            -
              end
         | 
| 25 18 | 
             
            end
         | 
| @@ -0,0 +1,197 @@ | |
| 1 | 
            +
            # typed: true
         | 
| 2 | 
            +
            # frozen_string_literal: true
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            require_relative "test_case"
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            module RubyIndexer
         | 
| 7 | 
            +
              class EnhancementTest < TestCase
         | 
| 8 | 
            +
                def test_enhancing_indexing_included_hook
         | 
| 9 | 
            +
                  enhancement_class = Class.new do
         | 
| 10 | 
            +
                    include Enhancement
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                    def on_call_node(index, owner, node, file_path)
         | 
| 13 | 
            +
                      return unless owner
         | 
| 14 | 
            +
                      return unless node.name == :extend
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                      arguments = node.arguments&.arguments
         | 
| 17 | 
            +
                      return unless arguments
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                      location = node.location
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                      arguments.each do |node|
         | 
| 22 | 
            +
                        next unless node.is_a?(Prism::ConstantReadNode) || node.is_a?(Prism::ConstantPathNode)
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                        module_name = node.full_name
         | 
| 25 | 
            +
                        next unless module_name == "ActiveSupport::Concern"
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                        index.register_included_hook(owner.name) do |index, base|
         | 
| 28 | 
            +
                          class_methods_name = "#{owner.name}::ClassMethods"
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                          if index.indexed?(class_methods_name)
         | 
| 31 | 
            +
                            singleton = index.existing_or_new_singleton_class(base.name)
         | 
| 32 | 
            +
                            singleton.mixin_operations << Entry::Include.new(class_methods_name)
         | 
| 33 | 
            +
                          end
         | 
| 34 | 
            +
                        end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                        index.add(Entry::Method.new(
         | 
| 37 | 
            +
                          "new_method",
         | 
| 38 | 
            +
                          file_path,
         | 
| 39 | 
            +
                          location,
         | 
| 40 | 
            +
                          location,
         | 
| 41 | 
            +
                          [],
         | 
| 42 | 
            +
                          [Entry::Signature.new([Entry::RequiredParameter.new(name: :a)])],
         | 
| 43 | 
            +
                          Entry::Visibility::PUBLIC,
         | 
| 44 | 
            +
                          owner,
         | 
| 45 | 
            +
                        ))
         | 
| 46 | 
            +
                      rescue Prism::ConstantPathNode::DynamicPartsInConstantPathError,
         | 
| 47 | 
            +
                             Prism::ConstantPathNode::MissingNodesInConstantPathError
         | 
| 48 | 
            +
                        # Do nothing
         | 
| 49 | 
            +
                      end
         | 
| 50 | 
            +
                    end
         | 
| 51 | 
            +
                  end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                  @index.register_enhancement(enhancement_class.new)
         | 
| 54 | 
            +
                  index(<<~RUBY)
         | 
| 55 | 
            +
                    module ActiveSupport
         | 
| 56 | 
            +
                      module Concern
         | 
| 57 | 
            +
                        def self.extended(base)
         | 
| 58 | 
            +
                          base.class_eval("def new_method(a); end")
         | 
| 59 | 
            +
                        end
         | 
| 60 | 
            +
                      end
         | 
| 61 | 
            +
                    end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                    module ActiveRecord
         | 
| 64 | 
            +
                      module Associations
         | 
| 65 | 
            +
                        extend ActiveSupport::Concern
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                        module ClassMethods
         | 
| 68 | 
            +
                          def belongs_to(something); end
         | 
| 69 | 
            +
                        end
         | 
| 70 | 
            +
                      end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                      class Base
         | 
| 73 | 
            +
                        include Associations
         | 
| 74 | 
            +
                      end
         | 
| 75 | 
            +
                    end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                    class User < ActiveRecord::Base
         | 
| 78 | 
            +
                    end
         | 
| 79 | 
            +
                  RUBY
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                  assert_equal(
         | 
| 82 | 
            +
                    [
         | 
| 83 | 
            +
                      "User::<Class:User>",
         | 
| 84 | 
            +
                      "ActiveRecord::Base::<Class:Base>",
         | 
| 85 | 
            +
                      "ActiveRecord::Associations::ClassMethods",
         | 
| 86 | 
            +
                      "Object::<Class:Object>",
         | 
| 87 | 
            +
                      "BasicObject::<Class:BasicObject>",
         | 
| 88 | 
            +
                      "Class",
         | 
| 89 | 
            +
                      "Module",
         | 
| 90 | 
            +
                      "Object",
         | 
| 91 | 
            +
                      "Kernel",
         | 
| 92 | 
            +
                      "BasicObject",
         | 
| 93 | 
            +
                    ],
         | 
| 94 | 
            +
                    @index.linearized_ancestors_of("User::<Class:User>"),
         | 
| 95 | 
            +
                  )
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                  assert_entry("new_method", Entry::Method, "/fake/path/foo.rb:10-4:10-33")
         | 
| 98 | 
            +
                end
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                def test_enhancing_indexing_configuration_dsl
         | 
| 101 | 
            +
                  enhancement_class = Class.new do
         | 
| 102 | 
            +
                    include Enhancement
         | 
| 103 | 
            +
             | 
| 104 | 
            +
                    def on_call_node(index, owner, node, file_path)
         | 
| 105 | 
            +
                      return unless owner
         | 
| 106 | 
            +
             | 
| 107 | 
            +
                      name = node.name
         | 
| 108 | 
            +
                      return unless name == :has_many
         | 
| 109 | 
            +
             | 
| 110 | 
            +
                      arguments = node.arguments&.arguments
         | 
| 111 | 
            +
                      return unless arguments
         | 
| 112 | 
            +
             | 
| 113 | 
            +
                      association_name = arguments.first
         | 
| 114 | 
            +
                      return unless association_name.is_a?(Prism::SymbolNode)
         | 
| 115 | 
            +
             | 
| 116 | 
            +
                      location = association_name.location
         | 
| 117 | 
            +
             | 
| 118 | 
            +
                      index.add(Entry::Method.new(
         | 
| 119 | 
            +
                        T.must(association_name.value),
         | 
| 120 | 
            +
                        file_path,
         | 
| 121 | 
            +
                        location,
         | 
| 122 | 
            +
                        location,
         | 
| 123 | 
            +
                        [],
         | 
| 124 | 
            +
                        [],
         | 
| 125 | 
            +
                        Entry::Visibility::PUBLIC,
         | 
| 126 | 
            +
                        owner,
         | 
| 127 | 
            +
                      ))
         | 
| 128 | 
            +
                    end
         | 
| 129 | 
            +
                  end
         | 
| 130 | 
            +
             | 
| 131 | 
            +
                  @index.register_enhancement(enhancement_class.new)
         | 
| 132 | 
            +
                  index(<<~RUBY)
         | 
| 133 | 
            +
                    module ActiveSupport
         | 
| 134 | 
            +
                      module Concern
         | 
| 135 | 
            +
                        def self.extended(base)
         | 
| 136 | 
            +
                          base.class_eval("def new_method(a); end")
         | 
| 137 | 
            +
                        end
         | 
| 138 | 
            +
                      end
         | 
| 139 | 
            +
                    end
         | 
| 140 | 
            +
             | 
| 141 | 
            +
                    module ActiveRecord
         | 
| 142 | 
            +
                      module Associations
         | 
| 143 | 
            +
                        extend ActiveSupport::Concern
         | 
| 144 | 
            +
             | 
| 145 | 
            +
                        module ClassMethods
         | 
| 146 | 
            +
                          def belongs_to(something); end
         | 
| 147 | 
            +
                        end
         | 
| 148 | 
            +
                      end
         | 
| 149 | 
            +
             | 
| 150 | 
            +
                      class Base
         | 
| 151 | 
            +
                        include Associations
         | 
| 152 | 
            +
                      end
         | 
| 153 | 
            +
                    end
         | 
| 154 | 
            +
             | 
| 155 | 
            +
                    class User < ActiveRecord::Base
         | 
| 156 | 
            +
                      has_many :posts
         | 
| 157 | 
            +
                    end
         | 
| 158 | 
            +
                  RUBY
         | 
| 159 | 
            +
             | 
| 160 | 
            +
                  assert_entry("posts", Entry::Method, "/fake/path/foo.rb:23-11:23-17")
         | 
| 161 | 
            +
                end
         | 
| 162 | 
            +
             | 
| 163 | 
            +
                def test_error_handling_in_enhancement
         | 
| 164 | 
            +
                  enhancement_class = Class.new do
         | 
| 165 | 
            +
                    include Enhancement
         | 
| 166 | 
            +
             | 
| 167 | 
            +
                    def on_call_node(index, owner, node, file_path)
         | 
| 168 | 
            +
                      raise "Error"
         | 
| 169 | 
            +
                    end
         | 
| 170 | 
            +
             | 
| 171 | 
            +
                    class << self
         | 
| 172 | 
            +
                      def name
         | 
| 173 | 
            +
                        "TestEnhancement"
         | 
| 174 | 
            +
                      end
         | 
| 175 | 
            +
                    end
         | 
| 176 | 
            +
                  end
         | 
| 177 | 
            +
             | 
| 178 | 
            +
                  @index.register_enhancement(enhancement_class.new)
         | 
| 179 | 
            +
             | 
| 180 | 
            +
                  _stdout, stderr = capture_io do
         | 
| 181 | 
            +
                    index(<<~RUBY)
         | 
| 182 | 
            +
                      module ActiveSupport
         | 
| 183 | 
            +
                        module Concern
         | 
| 184 | 
            +
                          def self.extended(base)
         | 
| 185 | 
            +
                            base.class_eval("def new_method(a); end")
         | 
| 186 | 
            +
                          end
         | 
| 187 | 
            +
                        end
         | 
| 188 | 
            +
                      end
         | 
| 189 | 
            +
                    RUBY
         | 
| 190 | 
            +
                  end
         | 
| 191 | 
            +
             | 
| 192 | 
            +
                  assert_match(%r{Indexing error in /fake/path/foo\.rb with 'TestEnhancement' enhancement}, stderr)
         | 
| 193 | 
            +
                  # The module should still be indexed
         | 
| 194 | 
            +
                  assert_entry("ActiveSupport::Concern", Entry::Module, "/fake/path/foo.rb:1-2:5-5")
         | 
| 195 | 
            +
                end
         | 
| 196 | 
            +
              end
         | 
| 197 | 
            +
            end
         | 
    
        data/lib/ruby_lsp/addon.rb
    CHANGED
    
    | @@ -49,15 +49,18 @@ module RubyLsp | |
| 49 49 | 
             
                    super
         | 
| 50 50 | 
             
                  end
         | 
| 51 51 |  | 
| 52 | 
            -
                  # Discovers and loads all addons. Returns  | 
| 53 | 
            -
                  sig  | 
| 52 | 
            +
                  # Discovers and loads all addons. Returns a list of errors when trying to require addons
         | 
| 53 | 
            +
                  sig do
         | 
| 54 | 
            +
                    params(global_state: GlobalState, outgoing_queue: Thread::Queue).returns(T::Array[StandardError])
         | 
| 55 | 
            +
                  end
         | 
| 54 56 | 
             
                  def load_addons(global_state, outgoing_queue)
         | 
| 55 57 | 
             
                    # Require all addons entry points, which should be placed under
         | 
| 56 58 | 
             
                    # `some_gem/lib/ruby_lsp/your_gem_name/addon.rb`
         | 
| 57 | 
            -
                    Gem.find_files("ruby_lsp/**/addon.rb"). | 
| 59 | 
            +
                    errors = Gem.find_files("ruby_lsp/**/addon.rb").filter_map do |addon|
         | 
| 58 60 | 
             
                      require File.expand_path(addon)
         | 
| 61 | 
            +
                      nil
         | 
| 59 62 | 
             
                    rescue => e
         | 
| 60 | 
            -
                       | 
| 63 | 
            +
                      e
         | 
| 61 64 | 
             
                    end
         | 
| 62 65 |  | 
| 63 66 | 
             
                    # Instantiate all discovered addon classes
         | 
| @@ -71,6 +74,8 @@ module RubyLsp | |
| 71 74 | 
             
                    rescue => e
         | 
| 72 75 | 
             
                      addon.add_error(e)
         | 
| 73 76 | 
             
                    end
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                    errors
         | 
| 74 79 | 
             
                  end
         | 
| 75 80 |  | 
| 76 81 | 
             
                  # Intended for use by tests for addons
         | 
    
        data/lib/ruby_lsp/base_server.rb
    CHANGED
    
    | @@ -65,7 +65,7 @@ module RubyLsp | |
| 65 65 | 
             
                    when "initialize", "initialized", "textDocument/didOpen", "textDocument/didClose", "textDocument/didChange"
         | 
| 66 66 | 
             
                      process_message(message)
         | 
| 67 67 | 
             
                    when "shutdown"
         | 
| 68 | 
            -
                       | 
| 68 | 
            +
                      send_log_message("Shutting down Ruby LSP...")
         | 
| 69 69 |  | 
| 70 70 | 
             
                      shutdown
         | 
| 71 71 |  | 
| @@ -76,7 +76,7 @@ module RubyLsp | |
| 76 76 | 
             
                    when "exit"
         | 
| 77 77 | 
             
                      @mutex.synchronize do
         | 
| 78 78 | 
             
                        status = @incoming_queue.closed? ? 0 : 1
         | 
| 79 | 
            -
                         | 
| 79 | 
            +
                        send_log_message("Shutdown complete with status #{status}")
         | 
| 80 80 | 
             
                        exit(status)
         | 
| 81 81 | 
             
                      end
         | 
| 82 82 | 
             
                    else
         | 
| @@ -145,5 +145,10 @@ module RubyLsp | |
| 145 145 | 
             
                def send_empty_response(id)
         | 
| 146 146 | 
             
                  send_message(Result.new(id: id, response: nil))
         | 
| 147 147 | 
             
                end
         | 
| 148 | 
            +
             | 
| 149 | 
            +
                sig { params(message: String, type: Integer).void }
         | 
| 150 | 
            +
                def send_log_message(message, type: Constant::MessageType::LOG)
         | 
| 151 | 
            +
                  send_message(Notification.window_log_message(message, type: Constant::MessageType::LOG))
         | 
| 152 | 
            +
                end
         | 
| 148 153 | 
             
              end
         | 
| 149 154 | 
             
            end
         | 
    
        data/lib/ruby_lsp/document.rb
    CHANGED
    
    | @@ -10,16 +10,6 @@ module RubyLsp | |
| 10 10 | 
             
                  end
         | 
| 11 11 | 
             
                end
         | 
| 12 12 |  | 
| 13 | 
            -
                class SorbetLevel < T::Enum
         | 
| 14 | 
            -
                  enums do
         | 
| 15 | 
            -
                    None = new("none")
         | 
| 16 | 
            -
                    Ignore = new("ignore")
         | 
| 17 | 
            -
                    False = new("false")
         | 
| 18 | 
            -
                    True = new("true")
         | 
| 19 | 
            -
                    Strict = new("strict")
         | 
| 20 | 
            -
                  end
         | 
| 21 | 
            -
                end
         | 
| 22 | 
            -
             | 
| 23 13 | 
             
                extend T::Sig
         | 
| 24 14 | 
             
                extend T::Helpers
         | 
| 25 15 |  | 
| @@ -223,26 +213,6 @@ module RubyLsp | |
| 223 213 | 
             
                  NodeContext.new(closest, parent, nesting_nodes, call_node)
         | 
| 224 214 | 
             
                end
         | 
| 225 215 |  | 
| 226 | 
            -
                sig { returns(SorbetLevel) }
         | 
| 227 | 
            -
                def sorbet_level
         | 
| 228 | 
            -
                  sigil = parse_result.magic_comments.find do |comment|
         | 
| 229 | 
            -
                    comment.key == "typed"
         | 
| 230 | 
            -
                  end&.value
         | 
| 231 | 
            -
             | 
| 232 | 
            -
                  case sigil
         | 
| 233 | 
            -
                  when "ignore"
         | 
| 234 | 
            -
                    SorbetLevel::Ignore
         | 
| 235 | 
            -
                  when "false"
         | 
| 236 | 
            -
                    SorbetLevel::False
         | 
| 237 | 
            -
                  when "true"
         | 
| 238 | 
            -
                    SorbetLevel::True
         | 
| 239 | 
            -
                  when "strict", "strong"
         | 
| 240 | 
            -
                    SorbetLevel::Strict
         | 
| 241 | 
            -
                  else
         | 
| 242 | 
            -
                    SorbetLevel::None
         | 
| 243 | 
            -
                  end
         | 
| 244 | 
            -
                end
         | 
| 245 | 
            -
             | 
| 246 216 | 
             
                class Scanner
         | 
| 247 217 | 
             
                  extend T::Sig
         | 
| 248 218 |  |