ruby-lsp 0.23.1 → 0.23.2
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/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +59 -30
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +21 -10
- data/lib/ruby_indexer/lib/ruby_indexer/visibility_scope.rb +36 -0
- data/lib/ruby_indexer/ruby_indexer.rb +1 -0
- data/lib/ruby_indexer/test/index_test.rb +2 -1
- data/lib/ruby_indexer/test/instance_variables_test.rb +20 -0
- data/lib/ruby_indexer/test/method_test.rb +71 -8
- data/lib/ruby_lsp/base_server.rb +8 -13
- data/lib/ruby_lsp/document.rb +74 -19
- data/lib/ruby_lsp/erb_document.rb +5 -3
- data/lib/ruby_lsp/global_state.rb +9 -0
- data/lib/ruby_lsp/rbs_document.rb +2 -2
- data/lib/ruby_lsp/requests/code_action_resolve.rb +2 -6
- data/lib/ruby_lsp/requests/completion.rb +2 -1
- data/lib/ruby_lsp/requests/definition.rb +1 -1
- data/lib/ruby_lsp/requests/document_highlight.rb +1 -1
- data/lib/ruby_lsp/requests/hover.rb +1 -1
- data/lib/ruby_lsp/requests/prepare_rename.rb +1 -1
- data/lib/ruby_lsp/requests/references.rb +1 -1
- data/lib/ruby_lsp/requests/rename.rb +1 -1
- data/lib/ruby_lsp/requests/show_syntax_tree.rb +1 -4
- data/lib/ruby_lsp/requests/signature_help.rb +1 -1
- data/lib/ruby_lsp/ruby_document.rb +75 -6
- data/lib/ruby_lsp/server.rb +95 -81
- data/lib/ruby_lsp/store.rb +16 -12
- data/lib/ruby_lsp/type_inferrer.rb +39 -17
- data/lib/ruby_lsp/utils.rb +43 -0
- metadata +3 -2
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 5d295233e64f7b79aa061f243c61c6d5bfb69cbdc5dac64474106473d7033bc8
         | 
| 4 | 
            +
              data.tar.gz: 559fbc2fb40b2a8043fefbf332cb4bf94ce8e3953299d6740d1d21034b79e92b
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 52e0176c0e9516801f94d9bd387ad6acfcd640c500443527448b69f3b55e2627aafa3bea75a60e131c21c0b06b309aba28a98b263344d20ce6406e1e66b69a37
         | 
| 7 | 
            +
              data.tar.gz: 52b47878d9c5a71dadd618a5562a30901abf3fe167f71e08d92f1a5418710a4e360043ae08d553975ad7008d7f1fb657a9b650d05a311ddcfc92157a7283ec6c
         | 
    
        data/VERSION
    CHANGED
    
    | @@ -1 +1 @@ | |
| 1 | 
            -
            0.23. | 
| 1 | 
            +
            0.23.2
         | 
| @@ -24,7 +24,7 @@ module RubyIndexer | |
| 24 24 | 
             
                  @index = index
         | 
| 25 25 | 
             
                  @uri = uri
         | 
| 26 26 | 
             
                  @enhancements = T.let(Enhancement.all(self), T::Array[Enhancement])
         | 
| 27 | 
            -
                  @visibility_stack = T.let([ | 
| 27 | 
            +
                  @visibility_stack = T.let([VisibilityScope.public_scope], T::Array[VisibilityScope])
         | 
| 28 28 | 
             
                  @comments_by_line = T.let(
         | 
| 29 29 | 
             
                    parse_result.comments.to_h do |c|
         | 
| 30 30 | 
             
                      [c.location.start_line, c]
         | 
| @@ -64,7 +64,6 @@ module RubyIndexer | |
| 64 64 | 
             
                    :on_constant_path_or_write_node_enter,
         | 
| 65 65 | 
             
                    :on_constant_path_operator_write_node_enter,
         | 
| 66 66 | 
             
                    :on_constant_path_and_write_node_enter,
         | 
| 67 | 
            -
                    :on_constant_or_write_node_enter,
         | 
| 68 67 | 
             
                    :on_constant_write_node_enter,
         | 
| 69 68 | 
             
                    :on_constant_or_write_node_enter,
         | 
| 70 69 | 
             
                    :on_constant_and_write_node_enter,
         | 
| @@ -138,7 +137,7 @@ module RubyIndexer | |
| 138 137 |  | 
| 139 138 | 
             
                sig { params(node: Prism::SingletonClassNode).void }
         | 
| 140 139 | 
             
                def on_singleton_class_node_enter(node)
         | 
| 141 | 
            -
                  @visibility_stack.push( | 
| 140 | 
            +
                  @visibility_stack.push(VisibilityScope.public_scope)
         | 
| 142 141 |  | 
| 143 142 | 
             
                  current_owner = @owner_stack.last
         | 
| 144 143 |  | 
| @@ -280,15 +279,15 @@ module RubyIndexer | |
| 280 279 | 
             
                  when :include, :prepend, :extend
         | 
| 281 280 | 
             
                    handle_module_operation(node, message)
         | 
| 282 281 | 
             
                  when :public
         | 
| 283 | 
            -
                    @visibility_stack.push( | 
| 282 | 
            +
                    @visibility_stack.push(VisibilityScope.public_scope)
         | 
| 284 283 | 
             
                  when :protected
         | 
| 285 | 
            -
                    @visibility_stack.push(Entry::Visibility::PROTECTED)
         | 
| 284 | 
            +
                    @visibility_stack.push(VisibilityScope.new(visibility: Entry::Visibility::PROTECTED))
         | 
| 286 285 | 
             
                  when :private
         | 
| 287 | 
            -
                    @visibility_stack.push(Entry::Visibility::PRIVATE)
         | 
| 286 | 
            +
                    @visibility_stack.push(VisibilityScope.new(visibility: Entry::Visibility::PRIVATE))
         | 
| 288 287 | 
             
                  when :module_function
         | 
| 289 288 | 
             
                    handle_module_function(node)
         | 
| 290 289 | 
             
                  when :private_class_method
         | 
| 291 | 
            -
                    @visibility_stack.push(Entry::Visibility::PRIVATE)
         | 
| 290 | 
            +
                    @visibility_stack.push(VisibilityScope.new(visibility: Entry::Visibility::PRIVATE))
         | 
| 292 291 | 
             
                    handle_private_class_method(node)
         | 
| 293 292 | 
             
                  end
         | 
| 294 293 |  | 
| @@ -324,42 +323,61 @@ module RubyIndexer | |
| 324 323 |  | 
| 325 324 | 
             
                sig { params(node: Prism::DefNode).void }
         | 
| 326 325 | 
             
                def on_def_node_enter(node)
         | 
| 326 | 
            +
                  owner = @owner_stack.last
         | 
| 327 | 
            +
                  return unless owner
         | 
| 328 | 
            +
             | 
| 327 329 | 
             
                  @inside_def = true
         | 
| 328 330 | 
             
                  method_name = node.name.to_s
         | 
| 329 331 | 
             
                  comments = collect_comments(node)
         | 
| 332 | 
            +
                  scope = current_visibility_scope
         | 
| 330 333 |  | 
| 331 334 | 
             
                  case node.receiver
         | 
| 332 335 | 
             
                  when nil
         | 
| 336 | 
            +
                    location = Location.from_prism_location(node.location, @code_units_cache)
         | 
| 337 | 
            +
                    name_location = Location.from_prism_location(node.name_loc, @code_units_cache)
         | 
| 338 | 
            +
                    signatures = [Entry::Signature.new(list_params(node.parameters))]
         | 
| 339 | 
            +
             | 
| 333 340 | 
             
                    @index.add(Entry::Method.new(
         | 
| 334 341 | 
             
                      method_name,
         | 
| 335 342 | 
             
                      @uri,
         | 
| 336 | 
            -
                       | 
| 337 | 
            -
                       | 
| 343 | 
            +
                      location,
         | 
| 344 | 
            +
                      name_location,
         | 
| 338 345 | 
             
                      comments,
         | 
| 339 | 
            -
                       | 
| 340 | 
            -
                       | 
| 341 | 
            -
                       | 
| 346 | 
            +
                      signatures,
         | 
| 347 | 
            +
                      scope.visibility,
         | 
| 348 | 
            +
                      owner,
         | 
| 342 349 | 
             
                    ))
         | 
| 343 | 
            -
                  when Prism::SelfNode
         | 
| 344 | 
            -
                    owner = @owner_stack.last
         | 
| 345 350 |  | 
| 346 | 
            -
                    if  | 
| 351 | 
            +
                    if scope.module_func
         | 
| 347 352 | 
             
                      singleton = @index.existing_or_new_singleton_class(owner.name)
         | 
| 348 353 |  | 
| 349 354 | 
             
                      @index.add(Entry::Method.new(
         | 
| 350 355 | 
             
                        method_name,
         | 
| 351 356 | 
             
                        @uri,
         | 
| 352 | 
            -
                         | 
| 353 | 
            -
                         | 
| 357 | 
            +
                        location,
         | 
| 358 | 
            +
                        name_location,
         | 
| 354 359 | 
             
                        comments,
         | 
| 355 | 
            -
                         | 
| 356 | 
            -
                         | 
| 360 | 
            +
                        signatures,
         | 
| 361 | 
            +
                        Entry::Visibility::PUBLIC,
         | 
| 357 362 | 
             
                        singleton,
         | 
| 358 363 | 
             
                      ))
         | 
| 359 | 
            -
             | 
| 360 | 
            -
                      @owner_stack << singleton
         | 
| 361 | 
            -
                      @stack << "<Class:#{@stack.last}>"
         | 
| 362 364 | 
             
                    end
         | 
| 365 | 
            +
                  when Prism::SelfNode
         | 
| 366 | 
            +
                    singleton = @index.existing_or_new_singleton_class(owner.name)
         | 
| 367 | 
            +
             | 
| 368 | 
            +
                    @index.add(Entry::Method.new(
         | 
| 369 | 
            +
                      method_name,
         | 
| 370 | 
            +
                      @uri,
         | 
| 371 | 
            +
                      Location.from_prism_location(node.location, @code_units_cache),
         | 
| 372 | 
            +
                      Location.from_prism_location(node.name_loc, @code_units_cache),
         | 
| 373 | 
            +
                      comments,
         | 
| 374 | 
            +
                      [Entry::Signature.new(list_params(node.parameters))],
         | 
| 375 | 
            +
                      scope.visibility,
         | 
| 376 | 
            +
                      singleton,
         | 
| 377 | 
            +
                    ))
         | 
| 378 | 
            +
             | 
| 379 | 
            +
                    @owner_stack << singleton
         | 
| 380 | 
            +
                    @stack << "<Class:#{@stack.last}>"
         | 
| 363 381 | 
             
                  end
         | 
| 364 382 | 
             
                end
         | 
| 365 383 |  | 
| @@ -834,6 +852,8 @@ module RubyIndexer | |
| 834 852 | 
             
                  return unless receiver.nil? || receiver.is_a?(Prism::SelfNode)
         | 
| 835 853 |  | 
| 836 854 | 
             
                  comments = collect_comments(node)
         | 
| 855 | 
            +
                  scope = current_visibility_scope
         | 
| 856 | 
            +
             | 
| 837 857 | 
             
                  arguments.each do |argument|
         | 
| 838 858 | 
             
                    name, loc = case argument
         | 
| 839 859 | 
             
                    when Prism::SymbolNode
         | 
| @@ -850,7 +870,7 @@ module RubyIndexer | |
| 850 870 | 
             
                        @uri,
         | 
| 851 871 | 
             
                        Location.from_prism_location(loc, @code_units_cache),
         | 
| 852 872 | 
             
                        comments,
         | 
| 853 | 
            -
                         | 
| 873 | 
            +
                        scope.visibility,
         | 
| 854 874 | 
             
                        @owner_stack.last,
         | 
| 855 875 | 
             
                      ))
         | 
| 856 876 | 
             
                    end
         | 
| @@ -862,7 +882,7 @@ module RubyIndexer | |
| 862 882 | 
             
                      @uri,
         | 
| 863 883 | 
             
                      Location.from_prism_location(loc, @code_units_cache),
         | 
| 864 884 | 
             
                      comments,
         | 
| 865 | 
            -
                       | 
| 885 | 
            +
                      scope.visibility,
         | 
| 866 886 | 
             
                      @owner_stack.last,
         | 
| 867 887 | 
             
                    ))
         | 
| 868 888 | 
             
                  end
         | 
| @@ -904,11 +924,20 @@ module RubyIndexer | |
| 904 924 |  | 
| 905 925 | 
             
                sig { params(node: Prism::CallNode).void }
         | 
| 906 926 | 
             
                def handle_module_function(node)
         | 
| 927 | 
            +
                  # Invoking `module_function` in a class raises
         | 
| 928 | 
            +
                  owner = @owner_stack.last
         | 
| 929 | 
            +
                  return unless owner.is_a?(Entry::Module)
         | 
| 930 | 
            +
             | 
| 907 931 | 
             
                  arguments_node = node.arguments
         | 
| 908 | 
            -
                  return unless arguments_node
         | 
| 909 932 |  | 
| 910 | 
            -
                   | 
| 911 | 
            -
                   | 
| 933 | 
            +
                  # If `module_function` is invoked without arguments, all methods defined after it become singleton methods and the
         | 
| 934 | 
            +
                  # visibility for instance methods changes to private
         | 
| 935 | 
            +
                  unless arguments_node
         | 
| 936 | 
            +
                    @visibility_stack.push(VisibilityScope.module_function_scope)
         | 
| 937 | 
            +
                    return
         | 
| 938 | 
            +
                  end
         | 
| 939 | 
            +
             | 
| 940 | 
            +
                  owner_name = owner.name
         | 
| 912 941 |  | 
| 913 942 | 
             
                  arguments_node.arguments.each do |argument|
         | 
| 914 943 | 
             
                    method_name = case argument
         | 
| @@ -983,8 +1012,8 @@ module RubyIndexer | |
| 983 1012 | 
             
                  end
         | 
| 984 1013 | 
             
                end
         | 
| 985 1014 |  | 
| 986 | 
            -
                sig { returns( | 
| 987 | 
            -
                def  | 
| 1015 | 
            +
                sig { returns(VisibilityScope) }
         | 
| 1016 | 
            +
                def current_visibility_scope
         | 
| 988 1017 | 
             
                  T.must(@visibility_stack.last)
         | 
| 989 1018 | 
             
                end
         | 
| 990 1019 |  | 
| @@ -1091,7 +1120,7 @@ module RubyIndexer | |
| 1091 1120 |  | 
| 1092 1121 | 
             
                sig { params(short_name: String, entry: Entry::Namespace).void }
         | 
| 1093 1122 | 
             
                def advance_namespace_stack(short_name, entry)
         | 
| 1094 | 
            -
                  @visibility_stack.push( | 
| 1123 | 
            +
                  @visibility_stack.push(VisibilityScope.public_scope)
         | 
| 1095 1124 | 
             
                  @owner_stack << entry
         | 
| 1096 1125 | 
             
                  @index.add(entry)
         | 
| 1097 1126 | 
             
                  @stack << short_name
         | 
| @@ -48,6 +48,8 @@ module RubyIndexer | |
| 48 48 | 
             
                  )
         | 
| 49 49 |  | 
| 50 50 | 
             
                  @configuration = T.let(RubyIndexer::Configuration.new, Configuration)
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                  @initial_indexing_completed = T.let(false, T::Boolean)
         | 
| 51 53 | 
             
                end
         | 
| 52 54 |  | 
| 53 55 | 
             
                # Register an included `hook` that will be executed when `module_name` is included into any namespace
         | 
| @@ -56,8 +58,8 @@ module RubyIndexer | |
| 56 58 | 
             
                  (@included_hooks[module_name] ||= []) << hook
         | 
| 57 59 | 
             
                end
         | 
| 58 60 |  | 
| 59 | 
            -
                sig { params(uri: URI::Generic).void }
         | 
| 60 | 
            -
                def delete(uri)
         | 
| 61 | 
            +
                sig { params(uri: URI::Generic, skip_require_paths_tree: T::Boolean).void }
         | 
| 62 | 
            +
                def delete(uri, skip_require_paths_tree: false)
         | 
| 61 63 | 
             
                  key = uri.to_s
         | 
| 62 64 | 
             
                  # For each constant discovered in `path`, delete the associated entry from the index. If there are no entries
         | 
| 63 65 | 
             
                  # left, delete the constant from the index.
         | 
| @@ -80,6 +82,7 @@ module RubyIndexer | |
| 80 82 | 
             
                  end
         | 
| 81 83 |  | 
| 82 84 | 
             
                  @uris_to_entries.delete(key)
         | 
| 85 | 
            +
                  return if skip_require_paths_tree
         | 
| 83 86 |  | 
| 84 87 | 
             
                  require_path = uri.require_path
         | 
| 85 88 | 
             
                  @require_paths_tree.delete(require_path) if require_path
         | 
| @@ -357,12 +360,13 @@ module RubyIndexer | |
| 357 360 | 
             
                  # When troubleshooting an indexing issue, e.g. through irb, it's not obvious that `index_all` will augment the
         | 
| 358 361 | 
             
                  # existing index values, meaning it may contain 'stale' entries. This check ensures that the user is aware of this
         | 
| 359 362 | 
             
                  # behavior and can take appropriate action.
         | 
| 360 | 
            -
                   | 
| 361 | 
            -
                  if @entries.any?
         | 
| 363 | 
            +
                  if @initial_indexing_completed
         | 
| 362 364 | 
             
                    raise IndexNotEmptyError,
         | 
| 363 365 | 
             
                      "The index is not empty. To prevent invalid entries, `index_all` can only be called once."
         | 
| 364 366 | 
             
                  end
         | 
| 365 367 |  | 
| 368 | 
            +
                  @initial_indexing_completed = true
         | 
| 369 | 
            +
             | 
| 366 370 | 
             
                  RBSIndexer.new(self).index_ruby_core
         | 
| 367 371 | 
             
                  # Calculate how many paths are worth 1% of progress
         | 
| 368 372 | 
             
                  progress_step = (uris.length / 100.0).ceil
         | 
| @@ -618,17 +622,24 @@ module RubyIndexer | |
| 618 622 | 
             
                end
         | 
| 619 623 |  | 
| 620 624 | 
             
                # Synchronizes a change made to the given URI. This method will ensure that new declarations are indexed, removed
         | 
| 621 | 
            -
                # declarations removed and that the ancestor linearization cache is cleared if necessary
         | 
| 622 | 
            -
                 | 
| 623 | 
            -
                 | 
| 625 | 
            +
                # declarations removed and that the ancestor linearization cache is cleared if necessary. If a block is passed, the
         | 
| 626 | 
            +
                # consumer of this API has to handle deleting and inserting/updating entries in the index instead of passing the
         | 
| 627 | 
            +
                # document's source (used to handle unsaved changes to files)
         | 
| 628 | 
            +
                sig do
         | 
| 629 | 
            +
                  params(uri: URI::Generic, source: T.nilable(String), block: T.nilable(T.proc.params(index: Index).void)).void
         | 
| 630 | 
            +
                end
         | 
| 631 | 
            +
                def handle_change(uri, source = nil, &block)
         | 
| 624 632 | 
             
                  key = uri.to_s
         | 
| 625 633 | 
             
                  original_entries = @uris_to_entries[key]
         | 
| 626 634 |  | 
| 627 | 
            -
                   | 
| 628 | 
            -
             | 
| 635 | 
            +
                  if block
         | 
| 636 | 
            +
                    block.call(self)
         | 
| 637 | 
            +
                  else
         | 
| 638 | 
            +
                    delete(uri)
         | 
| 639 | 
            +
                    index_single(uri, T.must(source))
         | 
| 640 | 
            +
                  end
         | 
| 629 641 |  | 
| 630 642 | 
             
                  updated_entries = @uris_to_entries[key]
         | 
| 631 | 
            -
             | 
| 632 643 | 
             
                  return unless original_entries && updated_entries
         | 
| 633 644 |  | 
| 634 645 | 
             
                  # A change in one ancestor may impact several different others, which could be including that ancestor through
         | 
| @@ -0,0 +1,36 @@ | |
| 1 | 
            +
            # typed: strict
         | 
| 2 | 
            +
            # frozen_string_literal: true
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module RubyIndexer
         | 
| 5 | 
            +
              # Represents the visibility scope in a Ruby namespace. This keeps track of whether methods are in a public, private or
         | 
| 6 | 
            +
              # protected section, and whether they are module functions.
         | 
| 7 | 
            +
              class VisibilityScope
         | 
| 8 | 
            +
                extend T::Sig
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                class << self
         | 
| 11 | 
            +
                  extend T::Sig
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  sig { returns(T.attached_class) }
         | 
| 14 | 
            +
                  def module_function_scope
         | 
| 15 | 
            +
                    new(module_func: true, visibility: Entry::Visibility::PRIVATE)
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  sig { returns(T.attached_class) }
         | 
| 19 | 
            +
                  def public_scope
         | 
| 20 | 
            +
                    new
         | 
| 21 | 
            +
                  end
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                sig { returns(Entry::Visibility) }
         | 
| 25 | 
            +
                attr_reader :visibility
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                sig { returns(T::Boolean) }
         | 
| 28 | 
            +
                attr_reader :module_func
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                sig { params(visibility: Entry::Visibility, module_func: T::Boolean).void }
         | 
| 31 | 
            +
                def initialize(visibility: Entry::Visibility::PUBLIC, module_func: false)
         | 
| 32 | 
            +
                  @visibility = visibility
         | 
| 33 | 
            +
                  @module_func = module_func
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
              end
         | 
| 36 | 
            +
            end
         | 
| @@ -5,6 +5,7 @@ require "yaml" | |
| 5 5 | 
             
            require "did_you_mean"
         | 
| 6 6 |  | 
| 7 7 | 
             
            require "ruby_indexer/lib/ruby_indexer/uri"
         | 
| 8 | 
            +
            require "ruby_indexer/lib/ruby_indexer/visibility_scope"
         | 
| 8 9 | 
             
            require "ruby_indexer/lib/ruby_indexer/declaration_listener"
         | 
| 9 10 | 
             
            require "ruby_indexer/lib/ruby_indexer/reference_finder"
         | 
| 10 11 | 
             
            require "ruby_indexer/lib/ruby_indexer/enhancement"
         | 
| @@ -2061,7 +2061,8 @@ module RubyIndexer | |
| 2061 2061 | 
             
                end
         | 
| 2062 2062 |  | 
| 2063 2063 | 
             
                def test_prevents_multiple_calls_to_index_all
         | 
| 2064 | 
            -
                   | 
| 2064 | 
            +
                  @index.index_all
         | 
| 2065 | 
            +
             | 
| 2065 2066 | 
             
                  assert_raises(Index::IndexNotEmptyError) do
         | 
| 2066 2067 | 
             
                    @index.index_all
         | 
| 2067 2068 | 
             
                  end
         | 
| @@ -216,5 +216,25 @@ module RubyIndexer | |
| 216 216 | 
             
                  assert_instance_of(Entry::Class, owner)
         | 
| 217 217 | 
             
                  assert_equal("Foo", owner.name)
         | 
| 218 218 | 
             
                end
         | 
| 219 | 
            +
             | 
| 220 | 
            +
                def test_module_function_does_not_impact_instance_variables
         | 
| 221 | 
            +
                  # One possible way of implementing `module_function` would be to push a fake singleton class to the stack, so that
         | 
| 222 | 
            +
                  # methods are inserted into it. However, that would be incorrect because it would then bind instance variables to
         | 
| 223 | 
            +
                  # the wrong type. This test is here to prevent that from happening.
         | 
| 224 | 
            +
                  index(<<~RUBY)
         | 
| 225 | 
            +
                    module Foo
         | 
| 226 | 
            +
                      module_function
         | 
| 227 | 
            +
             | 
| 228 | 
            +
                      def something; end
         | 
| 229 | 
            +
             | 
| 230 | 
            +
                      @a = 123
         | 
| 231 | 
            +
                    end
         | 
| 232 | 
            +
                  RUBY
         | 
| 233 | 
            +
             | 
| 234 | 
            +
                  entry = T.must(@index["@a"]&.first)
         | 
| 235 | 
            +
                  owner = T.must(entry.owner)
         | 
| 236 | 
            +
                  assert_instance_of(Entry::SingletonClass, owner)
         | 
| 237 | 
            +
                  assert_equal("Foo::<Class:Foo>", owner.name)
         | 
| 238 | 
            +
                end
         | 
| 219 239 | 
             
              end
         | 
| 220 240 | 
             
            end
         | 
| @@ -88,19 +88,21 @@ module RubyIndexer | |
| 88 88 |  | 
| 89 89 | 
             
                def test_visibility_tracking
         | 
| 90 90 | 
             
                  index(<<~RUBY)
         | 
| 91 | 
            -
                     | 
| 92 | 
            -
             | 
| 91 | 
            +
                    class Foo
         | 
| 92 | 
            +
                      private def foo
         | 
| 93 | 
            +
                      end
         | 
| 93 94 |  | 
| 94 | 
            -
             | 
| 95 | 
            +
                      def bar; end
         | 
| 95 96 |  | 
| 96 | 
            -
             | 
| 97 | 
            +
                      protected
         | 
| 97 98 |  | 
| 98 | 
            -
             | 
| 99 | 
            +
                      def baz; end
         | 
| 100 | 
            +
                    end
         | 
| 99 101 | 
             
                  RUBY
         | 
| 100 102 |  | 
| 101 | 
            -
                  assert_entry("foo", Entry::Method, "/fake/path/foo.rb: | 
| 102 | 
            -
                  assert_entry("bar", Entry::Method, "/fake/path/foo.rb: | 
| 103 | 
            -
                  assert_entry("baz", Entry::Method, "/fake/path/foo.rb: | 
| 103 | 
            +
                  assert_entry("foo", Entry::Method, "/fake/path/foo.rb:1-10:2-5", visibility: Entry::Visibility::PRIVATE)
         | 
| 104 | 
            +
                  assert_entry("bar", Entry::Method, "/fake/path/foo.rb:4-2:4-14", visibility: Entry::Visibility::PUBLIC)
         | 
| 105 | 
            +
                  assert_entry("baz", Entry::Method, "/fake/path/foo.rb:8-2:8-14", visibility: Entry::Visibility::PROTECTED)
         | 
| 104 106 | 
             
                end
         | 
| 105 107 |  | 
| 106 108 | 
             
                def test_visibility_tracking_with_nested_class_or_modules
         | 
| @@ -846,6 +848,67 @@ module RubyIndexer | |
| 846 848 | 
             
                  assert_signature_matches(entry, "baz(1)")
         | 
| 847 849 | 
             
                end
         | 
| 848 850 |  | 
| 851 | 
            +
                def test_module_function_with_no_arguments
         | 
| 852 | 
            +
                  index(<<~RUBY)
         | 
| 853 | 
            +
                    module Foo
         | 
| 854 | 
            +
                      def bar; end
         | 
| 855 | 
            +
             | 
| 856 | 
            +
                      module_function
         | 
| 857 | 
            +
             | 
| 858 | 
            +
                      def baz; end
         | 
| 859 | 
            +
                      attr_reader :attribute
         | 
| 860 | 
            +
             | 
| 861 | 
            +
                      public
         | 
| 862 | 
            +
             | 
| 863 | 
            +
                      def qux; end
         | 
| 864 | 
            +
                    end
         | 
| 865 | 
            +
                  RUBY
         | 
| 866 | 
            +
             | 
| 867 | 
            +
                  entry = T.must(@index["bar"].first)
         | 
| 868 | 
            +
                  assert_predicate(entry, :public?)
         | 
| 869 | 
            +
                  assert_equal("Foo", T.must(entry.owner).name)
         | 
| 870 | 
            +
             | 
| 871 | 
            +
                  instance_baz, singleton_baz = T.must(@index["baz"])
         | 
| 872 | 
            +
                  assert_predicate(instance_baz, :private?)
         | 
| 873 | 
            +
                  assert_equal("Foo", T.must(instance_baz.owner).name)
         | 
| 874 | 
            +
             | 
| 875 | 
            +
                  assert_predicate(singleton_baz, :public?)
         | 
| 876 | 
            +
                  assert_equal("Foo::<Class:Foo>", T.must(singleton_baz.owner).name)
         | 
| 877 | 
            +
             | 
| 878 | 
            +
                  # After invoking `public`, the state of `module_function` is reset
         | 
| 879 | 
            +
                  instance_qux, singleton_qux = T.must(@index["qux"])
         | 
| 880 | 
            +
                  assert_nil(singleton_qux)
         | 
| 881 | 
            +
                  assert_predicate(instance_qux, :public?)
         | 
| 882 | 
            +
                  assert_equal("Foo", T.must(instance_baz.owner).name)
         | 
| 883 | 
            +
             | 
| 884 | 
            +
                  # Attributes are not turned into class methods, they do become private
         | 
| 885 | 
            +
                  instance_attribute, singleton_attribute = @index["attribute"]
         | 
| 886 | 
            +
                  assert_nil(singleton_attribute)
         | 
| 887 | 
            +
                  assert_equal("Foo", T.must(instance_attribute.owner).name)
         | 
| 888 | 
            +
                  assert_predicate(instance_attribute, :private?)
         | 
| 889 | 
            +
                end
         | 
| 890 | 
            +
             | 
| 891 | 
            +
                def test_module_function_does_nothing_in_classes
         | 
| 892 | 
            +
                  # Invoking `module_function` in a class raises an error. We simply ignore it
         | 
| 893 | 
            +
                  index(<<~RUBY)
         | 
| 894 | 
            +
                    class Foo
         | 
| 895 | 
            +
                      def bar; end
         | 
| 896 | 
            +
             | 
| 897 | 
            +
                      module_function
         | 
| 898 | 
            +
             | 
| 899 | 
            +
                      def baz; end
         | 
| 900 | 
            +
                    end
         | 
| 901 | 
            +
                  RUBY
         | 
| 902 | 
            +
             | 
| 903 | 
            +
                  entry = T.must(@index["bar"].first)
         | 
| 904 | 
            +
                  assert_predicate(entry, :public?)
         | 
| 905 | 
            +
                  assert_equal("Foo", T.must(entry.owner).name)
         | 
| 906 | 
            +
             | 
| 907 | 
            +
                  entry = T.must(@index["baz"].first)
         | 
| 908 | 
            +
                  assert_predicate(entry, :public?)
         | 
| 909 | 
            +
                  assert_equal("Foo", T.must(entry.owner).name)
         | 
| 910 | 
            +
                end
         | 
| 911 | 
            +
             | 
| 849 912 | 
             
                private
         | 
| 850 913 |  | 
| 851 914 | 
             
                sig { params(entry: Entry::Method, call_string: String).void }
         | 
    
        data/lib/ruby_lsp/base_server.rb
    CHANGED
    
    | @@ -18,22 +18,21 @@ module RubyLsp | |
| 18 18 | 
             
                  @incoming_queue = T.let(Thread::Queue.new, Thread::Queue)
         | 
| 19 19 | 
             
                  @outgoing_queue = T.let(Thread::Queue.new, Thread::Queue)
         | 
| 20 20 | 
             
                  @cancelled_requests = T.let([], T::Array[Integer])
         | 
| 21 | 
            -
                  @mutex = T.let(Mutex.new, Mutex)
         | 
| 22 21 | 
             
                  @worker = T.let(new_worker, Thread)
         | 
| 23 22 | 
             
                  @current_request_id = T.let(1, Integer)
         | 
| 24 | 
            -
                  @ | 
| 23 | 
            +
                  @global_state = T.let(GlobalState.new, GlobalState)
         | 
| 24 | 
            +
                  @store = T.let(Store.new(@global_state), Store)
         | 
| 25 25 | 
             
                  @outgoing_dispatcher = T.let(
         | 
| 26 26 | 
             
                    Thread.new do
         | 
| 27 27 | 
             
                      unless @test_mode
         | 
| 28 28 | 
             
                        while (message = @outgoing_queue.pop)
         | 
| 29 | 
            -
                          @ | 
| 29 | 
            +
                          @global_state.synchronize { @writer.write(message.to_hash) }
         | 
| 30 30 | 
             
                        end
         | 
| 31 31 | 
             
                      end
         | 
| 32 32 | 
             
                    end,
         | 
| 33 33 | 
             
                    Thread,
         | 
| 34 34 | 
             
                  )
         | 
| 35 35 |  | 
| 36 | 
            -
                  @global_state = T.let(GlobalState.new, GlobalState)
         | 
| 37 36 | 
             
                  Thread.main.priority = 1
         | 
| 38 37 |  | 
| 39 38 | 
             
                  # We read the initialize request in `exe/ruby-lsp` to be able to determine the workspace URI where Bundler should
         | 
| @@ -51,7 +50,7 @@ module RubyLsp | |
| 51 50 | 
             
                    # source. Altering the source reference during parsing will put the parser in an invalid internal state, since
         | 
| 52 51 | 
             
                    # it started parsing with one source but then it changed in the middle. We don't want to do this for text
         | 
| 53 52 | 
             
                    # synchronization notifications
         | 
| 54 | 
            -
                    @ | 
| 53 | 
            +
                    @global_state.synchronize do
         | 
| 55 54 | 
             
                      uri = message.dig(:params, :textDocument, :uri)
         | 
| 56 55 |  | 
| 57 56 | 
             
                      if uri
         | 
| @@ -95,14 +94,14 @@ module RubyLsp | |
| 95 94 | 
             
                         "$/cancelRequest"
         | 
| 96 95 | 
             
                      process_message(message)
         | 
| 97 96 | 
             
                    when "shutdown"
         | 
| 98 | 
            -
                      @ | 
| 97 | 
            +
                      @global_state.synchronize do
         | 
| 99 98 | 
             
                        send_log_message("Shutting down Ruby LSP...")
         | 
| 100 99 | 
             
                        shutdown
         | 
| 101 100 | 
             
                        run_shutdown
         | 
| 102 101 | 
             
                        @writer.write(Result.new(id: message[:id], response: nil).to_hash)
         | 
| 103 102 | 
             
                      end
         | 
| 104 103 | 
             
                    when "exit"
         | 
| 105 | 
            -
                      @ | 
| 104 | 
            +
                      @global_state.synchronize do
         | 
| 106 105 | 
             
                        status = @incoming_queue.closed? ? 0 : 1
         | 
| 107 106 | 
             
                        send_log_message("Shutdown complete with status #{status}")
         | 
| 108 107 | 
             
                        exit(status)
         | 
| @@ -157,13 +156,9 @@ module RubyLsp | |
| 157 156 | 
             
                      id = message[:id]
         | 
| 158 157 |  | 
| 159 158 | 
             
                      # Check if the request was cancelled before trying to process it
         | 
| 160 | 
            -
                      @ | 
| 159 | 
            +
                      @global_state.synchronize do
         | 
| 161 160 | 
             
                        if id && @cancelled_requests.include?(id)
         | 
| 162 | 
            -
                          send_message( | 
| 163 | 
            -
                            id: id,
         | 
| 164 | 
            -
                            code: Constant::ErrorCodes::REQUEST_CANCELLED,
         | 
| 165 | 
            -
                            message: "Request #{id} was cancelled",
         | 
| 166 | 
            -
                          ))
         | 
| 161 | 
            +
                          send_message(Result.new(id: id, response: nil))
         | 
| 167 162 | 
             
                          @cancelled_requests.delete(id)
         | 
| 168 163 | 
             
                          next
         | 
| 169 164 | 
             
                        end
         | 
    
        data/lib/ruby_lsp/document.rb
    CHANGED
    
    | @@ -40,19 +40,24 @@ module RubyLsp | |
| 40 40 | 
             
                sig { returns(Encoding) }
         | 
| 41 41 | 
             
                attr_reader :encoding
         | 
| 42 42 |  | 
| 43 | 
            +
                sig { returns(T.nilable(Edit)) }
         | 
| 44 | 
            +
                attr_reader :last_edit
         | 
| 45 | 
            +
             | 
| 43 46 | 
             
                sig { returns(T.any(Interface::SemanticTokens, Object)) }
         | 
| 44 47 | 
             
                attr_accessor :semantic_tokens
         | 
| 45 48 |  | 
| 46 | 
            -
                sig { params(source: String, version: Integer, uri: URI::Generic,  | 
| 47 | 
            -
                def initialize(source:, version:, uri:,  | 
| 49 | 
            +
                sig { params(source: String, version: Integer, uri: URI::Generic, global_state: GlobalState).void }
         | 
| 50 | 
            +
                def initialize(source:, version:, uri:, global_state:)
         | 
| 51 | 
            +
                  @source = source
         | 
| 52 | 
            +
                  @version = version
         | 
| 53 | 
            +
                  @global_state = global_state
         | 
| 48 54 | 
             
                  @cache = T.let(Hash.new(EMPTY_CACHE), T::Hash[String, T.untyped])
         | 
| 49 55 | 
             
                  @semantic_tokens = T.let(EMPTY_CACHE, T.any(Interface::SemanticTokens, Object))
         | 
| 50 | 
            -
                  @encoding = T.let(encoding, Encoding)
         | 
| 51 | 
            -
                  @source = T.let(source, String)
         | 
| 52 | 
            -
                  @version = T.let(version, Integer)
         | 
| 56 | 
            +
                  @encoding = T.let(global_state.encoding, Encoding)
         | 
| 53 57 | 
             
                  @uri = T.let(uri, URI::Generic)
         | 
| 54 58 | 
             
                  @needs_parsing = T.let(true, T::Boolean)
         | 
| 55 59 | 
             
                  @parse_result = T.let(T.unsafe(nil), ParseResultType)
         | 
| 60 | 
            +
                  @last_edit = T.let(nil, T.nilable(Edit))
         | 
| 56 61 | 
             
                  parse!
         | 
| 57 62 | 
             
                end
         | 
| 58 63 |  | 
| @@ -64,7 +69,6 @@ module RubyLsp | |
| 64 69 | 
             
                sig { abstract.returns(LanguageId) }
         | 
| 65 70 | 
             
                def language_id; end
         | 
| 66 71 |  | 
| 67 | 
            -
                # TODO: remove this method once all non-positional requests have been migrated to the listener pattern
         | 
| 68 72 | 
             
                sig do
         | 
| 69 73 | 
             
                  type_parameters(:T)
         | 
| 70 74 | 
             
                    .params(
         | 
| @@ -93,19 +97,34 @@ module RubyLsp | |
| 93 97 |  | 
| 94 98 | 
             
                sig { params(edits: T::Array[T::Hash[Symbol, T.untyped]], version: Integer).void }
         | 
| 95 99 | 
             
                def push_edits(edits, version:)
         | 
| 96 | 
            -
                   | 
| 97 | 
            -
                     | 
| 98 | 
            -
             | 
| 100 | 
            +
                  @global_state.synchronize do
         | 
| 101 | 
            +
                    edits.each do |edit|
         | 
| 102 | 
            +
                      range = edit[:range]
         | 
| 103 | 
            +
                      scanner = create_scanner
         | 
| 99 104 |  | 
| 100 | 
            -
             | 
| 101 | 
            -
             | 
| 105 | 
            +
                      start_position = scanner.find_char_position(range[:start])
         | 
| 106 | 
            +
                      end_position = scanner.find_char_position(range[:end])
         | 
| 102 107 |  | 
| 103 | 
            -
             | 
| 104 | 
            -
             | 
| 108 | 
            +
                      @source[start_position...end_position] = edit[:text]
         | 
| 109 | 
            +
                    end
         | 
| 105 110 |  | 
| 106 | 
            -
             | 
| 107 | 
            -
             | 
| 108 | 
            -
             | 
| 111 | 
            +
                    @version = version
         | 
| 112 | 
            +
                    @needs_parsing = true
         | 
| 113 | 
            +
                    @cache.clear
         | 
| 114 | 
            +
             | 
| 115 | 
            +
                    last_edit = edits.last
         | 
| 116 | 
            +
                    return unless last_edit
         | 
| 117 | 
            +
             | 
| 118 | 
            +
                    last_edit_range = last_edit[:range]
         | 
| 119 | 
            +
             | 
| 120 | 
            +
                    @last_edit = if last_edit_range[:start] == last_edit_range[:end]
         | 
| 121 | 
            +
                      Insert.new(last_edit_range)
         | 
| 122 | 
            +
                    elsif last_edit[:text].empty?
         | 
| 123 | 
            +
                      Delete.new(last_edit_range)
         | 
| 124 | 
            +
                    else
         | 
| 125 | 
            +
                      Replace.new(last_edit_range)
         | 
| 126 | 
            +
                    end
         | 
| 127 | 
            +
                  end
         | 
| 109 128 | 
             
                end
         | 
| 110 129 |  | 
| 111 130 | 
             
                # Returns `true` if the document was parsed and `false` if nothing needed parsing
         | 
| @@ -115,16 +134,52 @@ module RubyLsp | |
| 115 134 | 
             
                sig { abstract.returns(T::Boolean) }
         | 
| 116 135 | 
             
                def syntax_error?; end
         | 
| 117 136 |  | 
| 137 | 
            +
                sig { returns(T::Boolean) }
         | 
| 138 | 
            +
                def past_expensive_limit?
         | 
| 139 | 
            +
                  @source.length > MAXIMUM_CHARACTERS_FOR_EXPENSIVE_FEATURES
         | 
| 140 | 
            +
                end
         | 
| 141 | 
            +
             | 
| 142 | 
            +
                sig do
         | 
| 143 | 
            +
                  params(
         | 
| 144 | 
            +
                    start_pos: T::Hash[Symbol, T.untyped],
         | 
| 145 | 
            +
                    end_pos: T.nilable(T::Hash[Symbol, T.untyped]),
         | 
| 146 | 
            +
                  ).returns([Integer, T.nilable(Integer)])
         | 
| 147 | 
            +
                end
         | 
| 148 | 
            +
                def find_index_by_position(start_pos, end_pos = nil)
         | 
| 149 | 
            +
                  @global_state.synchronize do
         | 
| 150 | 
            +
                    scanner = create_scanner
         | 
| 151 | 
            +
                    start_index = scanner.find_char_position(start_pos)
         | 
| 152 | 
            +
                    end_index = scanner.find_char_position(end_pos) if end_pos
         | 
| 153 | 
            +
                    [start_index, end_index]
         | 
| 154 | 
            +
                  end
         | 
| 155 | 
            +
                end
         | 
| 156 | 
            +
             | 
| 157 | 
            +
                private
         | 
| 158 | 
            +
             | 
| 118 159 | 
             
                sig { returns(Scanner) }
         | 
| 119 160 | 
             
                def create_scanner
         | 
| 120 161 | 
             
                  Scanner.new(@source, @encoding)
         | 
| 121 162 | 
             
                end
         | 
| 122 163 |  | 
| 123 | 
            -
                 | 
| 124 | 
            -
             | 
| 125 | 
            -
                   | 
| 164 | 
            +
                class Edit
         | 
| 165 | 
            +
                  extend T::Sig
         | 
| 166 | 
            +
                  extend T::Helpers
         | 
| 167 | 
            +
             | 
| 168 | 
            +
                  abstract!
         | 
| 169 | 
            +
             | 
| 170 | 
            +
                  sig { returns(T::Hash[Symbol, T.untyped]) }
         | 
| 171 | 
            +
                  attr_reader :range
         | 
| 172 | 
            +
             | 
| 173 | 
            +
                  sig { params(range: T::Hash[Symbol, T.untyped]).void }
         | 
| 174 | 
            +
                  def initialize(range)
         | 
| 175 | 
            +
                    @range = range
         | 
| 176 | 
            +
                  end
         | 
| 126 177 | 
             
                end
         | 
| 127 178 |  | 
| 179 | 
            +
                class Insert < Edit; end
         | 
| 180 | 
            +
                class Replace < Edit; end
         | 
| 181 | 
            +
                class Delete < Edit; end
         | 
| 182 | 
            +
             | 
| 128 183 | 
             
                class Scanner
         | 
| 129 184 | 
             
                  extend T::Sig
         | 
| 130 185 |  |