ruby-lsp 0.23.1 → 0.23.3
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-launcher +15 -9
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +89 -58
- 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 +86 -8
- data/lib/ruby_lsp/base_server.rb +9 -15
- data/lib/ruby_lsp/document.rb +62 -9
- data/lib/ruby_lsp/erb_document.rb +5 -3
- data/lib/ruby_lsp/global_state.rb +9 -0
- data/lib/ruby_lsp/listeners/definition.rb +7 -2
- 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 +3 -3
- 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/requests/workspace_symbol.rb +2 -2
- data/lib/ruby_lsp/ruby_document.rb +75 -6
- data/lib/ruby_lsp/server.rb +68 -48
- data/lib/ruby_lsp/store.rb +7 -7
- 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: 95642b8034d20684e2596755f391591d6dfffc9021e23a6e2efd0b9a0e500eb0
         | 
| 4 | 
            +
              data.tar.gz: 6db469fda24144ce71eb0c2d8434251b3bf01a800035d85bea5e8e6801cf5120
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 69365797e36c6597b43b5067da2e10b6c634cdb9121081db0e7c16c657fe12e2aa72042ba7beb07480b1cbb4a24c9a37a7a71f5a49a1be42923adb7a85a80715
         | 
| 7 | 
            +
              data.tar.gz: 22695e4a4fd4e72738b43c32e9a693ed9895033e82240317700cde5583537b0ad6e7d8ce530a110624c57278d5a08c4eeb207ae010a4db99b3885bc241c5caa4
         | 
    
        data/VERSION
    CHANGED
    
    | @@ -1 +1 @@ | |
| 1 | 
            -
            0.23. | 
| 1 | 
            +
            0.23.3
         | 
    
        data/exe/ruby-lsp-launcher
    CHANGED
    
    | @@ -45,6 +45,12 @@ rescue Errno::ECHILD | |
| 45 45 | 
             
              # In theory, the child process can finish before we even get to the wait call, but that is not an error
         | 
| 46 46 | 
             
            end
         | 
| 47 47 |  | 
| 48 | 
            +
            error_path = File.join(".ruby-lsp", "install_error")
         | 
| 49 | 
            +
             | 
| 50 | 
            +
            install_error = if File.exist?(error_path)
         | 
| 51 | 
            +
              Marshal.load(File.read(error_path))
         | 
| 52 | 
            +
            end
         | 
| 53 | 
            +
             | 
| 48 54 | 
             
            begin
         | 
| 49 55 | 
             
              bundle_env_path = File.join(".ruby-lsp", "bundle_env")
         | 
| 50 56 | 
             
              # We can't require `bundler/setup` because that file prematurely exits the process if setup fails. However, we can't
         | 
| @@ -67,21 +73,21 @@ begin | |
| 67 73 | 
             
            rescue StandardError => e
         | 
| 68 74 | 
             
              # If installing gems failed for any reason, we don't want to exit the process prematurely. We can still provide most
         | 
| 69 75 | 
             
              # features in a degraded mode. We simply save the error so that we can report to the user that certain gems might be
         | 
| 70 | 
            -
              # missing, but we respect the LSP life cycle
         | 
| 71 | 
            -
               | 
| 72 | 
            -
               | 
| 76 | 
            +
              # missing, but we respect the LSP life cycle.
         | 
| 77 | 
            +
              #
         | 
| 78 | 
            +
              # If an install error occurred and one of the gems is not installed, Bundler.setup is guaranteed to fail with
         | 
| 79 | 
            +
              # `Bundler::GemNotFound`. We don't set `setup_error` here because that would essentially report the same problem twice
         | 
| 80 | 
            +
              # to telemetry, which is not useful
         | 
| 81 | 
            +
              unless install_error && e.is_a?(Bundler::GemNotFound)
         | 
| 82 | 
            +
                setup_error = e
         | 
| 83 | 
            +
                $stderr.puts("Failed to set up composed Bundle\n#{e.full_message}")
         | 
| 84 | 
            +
              end
         | 
| 73 85 |  | 
| 74 86 | 
             
              # If Bundler.setup fails, we need to restore the original $LOAD_PATH so that we can still require the Ruby LSP server
         | 
| 75 87 | 
             
              # in degraded mode
         | 
| 76 88 | 
             
              $LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
         | 
| 77 89 | 
             
            end
         | 
| 78 90 |  | 
| 79 | 
            -
            error_path = File.join(".ruby-lsp", "install_error")
         | 
| 80 | 
            -
             | 
| 81 | 
            -
            install_error = if File.exist?(error_path)
         | 
| 82 | 
            -
              Marshal.load(File.read(error_path))
         | 
| 83 | 
            -
            end
         | 
| 84 | 
            -
             | 
| 85 91 | 
             
            # Now that the bundle is set up, we can begin actually launching the server. Note that `Bundler.setup` will have already
         | 
| 86 92 | 
             
            # configured the load path using the version of the Ruby LSP present in the composed bundle. Do not push any Ruby LSP
         | 
| 87 93 | 
             
            # paths into the load path manually or we may end up requiring the wrong version of the gem
         | 
| @@ -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,14 @@ 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)
         | 
| 292 290 | 
             
                    handle_private_class_method(node)
         | 
| 293 291 | 
             
                  end
         | 
| 294 292 |  | 
| @@ -324,42 +322,61 @@ module RubyIndexer | |
| 324 322 |  | 
| 325 323 | 
             
                sig { params(node: Prism::DefNode).void }
         | 
| 326 324 | 
             
                def on_def_node_enter(node)
         | 
| 325 | 
            +
                  owner = @owner_stack.last
         | 
| 326 | 
            +
                  return unless owner
         | 
| 327 | 
            +
             | 
| 327 328 | 
             
                  @inside_def = true
         | 
| 328 329 | 
             
                  method_name = node.name.to_s
         | 
| 329 330 | 
             
                  comments = collect_comments(node)
         | 
| 331 | 
            +
                  scope = current_visibility_scope
         | 
| 330 332 |  | 
| 331 333 | 
             
                  case node.receiver
         | 
| 332 334 | 
             
                  when nil
         | 
| 335 | 
            +
                    location = Location.from_prism_location(node.location, @code_units_cache)
         | 
| 336 | 
            +
                    name_location = Location.from_prism_location(node.name_loc, @code_units_cache)
         | 
| 337 | 
            +
                    signatures = [Entry::Signature.new(list_params(node.parameters))]
         | 
| 338 | 
            +
             | 
| 333 339 | 
             
                    @index.add(Entry::Method.new(
         | 
| 334 340 | 
             
                      method_name,
         | 
| 335 341 | 
             
                      @uri,
         | 
| 336 | 
            -
                       | 
| 337 | 
            -
                       | 
| 342 | 
            +
                      location,
         | 
| 343 | 
            +
                      name_location,
         | 
| 338 344 | 
             
                      comments,
         | 
| 339 | 
            -
                       | 
| 340 | 
            -
                       | 
| 341 | 
            -
                       | 
| 345 | 
            +
                      signatures,
         | 
| 346 | 
            +
                      scope.visibility,
         | 
| 347 | 
            +
                      owner,
         | 
| 342 348 | 
             
                    ))
         | 
| 343 | 
            -
                  when Prism::SelfNode
         | 
| 344 | 
            -
                    owner = @owner_stack.last
         | 
| 345 349 |  | 
| 346 | 
            -
                    if  | 
| 350 | 
            +
                    if scope.module_func
         | 
| 347 351 | 
             
                      singleton = @index.existing_or_new_singleton_class(owner.name)
         | 
| 348 352 |  | 
| 349 353 | 
             
                      @index.add(Entry::Method.new(
         | 
| 350 354 | 
             
                        method_name,
         | 
| 351 355 | 
             
                        @uri,
         | 
| 352 | 
            -
                         | 
| 353 | 
            -
                         | 
| 356 | 
            +
                        location,
         | 
| 357 | 
            +
                        name_location,
         | 
| 354 358 | 
             
                        comments,
         | 
| 355 | 
            -
                         | 
| 356 | 
            -
                         | 
| 359 | 
            +
                        signatures,
         | 
| 360 | 
            +
                        Entry::Visibility::PUBLIC,
         | 
| 357 361 | 
             
                        singleton,
         | 
| 358 362 | 
             
                      ))
         | 
| 359 | 
            -
             | 
| 360 | 
            -
                      @owner_stack << singleton
         | 
| 361 | 
            -
                      @stack << "<Class:#{@stack.last}>"
         | 
| 362 363 | 
             
                    end
         | 
| 364 | 
            +
                  when Prism::SelfNode
         | 
| 365 | 
            +
                    singleton = @index.existing_or_new_singleton_class(owner.name)
         | 
| 366 | 
            +
             | 
| 367 | 
            +
                    @index.add(Entry::Method.new(
         | 
| 368 | 
            +
                      method_name,
         | 
| 369 | 
            +
                      @uri,
         | 
| 370 | 
            +
                      Location.from_prism_location(node.location, @code_units_cache),
         | 
| 371 | 
            +
                      Location.from_prism_location(node.name_loc, @code_units_cache),
         | 
| 372 | 
            +
                      comments,
         | 
| 373 | 
            +
                      [Entry::Signature.new(list_params(node.parameters))],
         | 
| 374 | 
            +
                      scope.visibility,
         | 
| 375 | 
            +
                      singleton,
         | 
| 376 | 
            +
                    ))
         | 
| 377 | 
            +
             | 
| 378 | 
            +
                    @owner_stack << singleton
         | 
| 379 | 
            +
                    @stack << "<Class:#{@stack.last}>"
         | 
| 363 380 | 
             
                  end
         | 
| 364 381 | 
             
                end
         | 
| 365 382 |  | 
| @@ -834,6 +851,8 @@ module RubyIndexer | |
| 834 851 | 
             
                  return unless receiver.nil? || receiver.is_a?(Prism::SelfNode)
         | 
| 835 852 |  | 
| 836 853 | 
             
                  comments = collect_comments(node)
         | 
| 854 | 
            +
                  scope = current_visibility_scope
         | 
| 855 | 
            +
             | 
| 837 856 | 
             
                  arguments.each do |argument|
         | 
| 838 857 | 
             
                    name, loc = case argument
         | 
| 839 858 | 
             
                    when Prism::SymbolNode
         | 
| @@ -850,7 +869,7 @@ module RubyIndexer | |
| 850 869 | 
             
                        @uri,
         | 
| 851 870 | 
             
                        Location.from_prism_location(loc, @code_units_cache),
         | 
| 852 871 | 
             
                        comments,
         | 
| 853 | 
            -
                         | 
| 872 | 
            +
                        scope.visibility,
         | 
| 854 873 | 
             
                        @owner_stack.last,
         | 
| 855 874 | 
             
                      ))
         | 
| 856 875 | 
             
                    end
         | 
| @@ -862,7 +881,7 @@ module RubyIndexer | |
| 862 881 | 
             
                      @uri,
         | 
| 863 882 | 
             
                      Location.from_prism_location(loc, @code_units_cache),
         | 
| 864 883 | 
             
                      comments,
         | 
| 865 | 
            -
                       | 
| 884 | 
            +
                      scope.visibility,
         | 
| 866 885 | 
             
                      @owner_stack.last,
         | 
| 867 886 | 
             
                    ))
         | 
| 868 887 | 
             
                  end
         | 
| @@ -904,11 +923,20 @@ module RubyIndexer | |
| 904 923 |  | 
| 905 924 | 
             
                sig { params(node: Prism::CallNode).void }
         | 
| 906 925 | 
             
                def handle_module_function(node)
         | 
| 926 | 
            +
                  # Invoking `module_function` in a class raises
         | 
| 927 | 
            +
                  owner = @owner_stack.last
         | 
| 928 | 
            +
                  return unless owner.is_a?(Entry::Module)
         | 
| 929 | 
            +
             | 
| 907 930 | 
             
                  arguments_node = node.arguments
         | 
| 908 | 
            -
                  return unless arguments_node
         | 
| 909 931 |  | 
| 910 | 
            -
                   | 
| 911 | 
            -
                   | 
| 932 | 
            +
                  # If `module_function` is invoked without arguments, all methods defined after it become singleton methods and the
         | 
| 933 | 
            +
                  # visibility for instance methods changes to private
         | 
| 934 | 
            +
                  unless arguments_node
         | 
| 935 | 
            +
                    @visibility_stack.push(VisibilityScope.module_function_scope)
         | 
| 936 | 
            +
                    return
         | 
| 937 | 
            +
                  end
         | 
| 938 | 
            +
             | 
| 939 | 
            +
                  owner_name = owner.name
         | 
| 912 940 |  | 
| 913 941 | 
             
                  arguments_node.arguments.each do |argument|
         | 
| 914 942 | 
             
                    method_name = case argument
         | 
| @@ -946,45 +974,48 @@ module RubyIndexer | |
| 946 974 |  | 
| 947 975 | 
             
                sig { params(node: Prism::CallNode).void }
         | 
| 948 976 | 
             
                def handle_private_class_method(node)
         | 
| 949 | 
            -
                  node.arguments&.arguments | 
| 950 | 
            -
             | 
| 951 | 
            -
                    when Prism::StringNode, Prism::SymbolNode
         | 
| 952 | 
            -
                      [argument]
         | 
| 953 | 
            -
                    when Prism::ArrayNode
         | 
| 954 | 
            -
                      argument.elements
         | 
| 955 | 
            -
                    else
         | 
| 956 | 
            -
                      []
         | 
| 957 | 
            -
                    end
         | 
| 977 | 
            +
                  arguments = node.arguments&.arguments
         | 
| 978 | 
            +
                  return unless arguments
         | 
| 958 979 |  | 
| 959 | 
            -
             | 
| 960 | 
            -
             | 
| 961 | 
            -
             | 
| 962 | 
            -
                     | 
| 980 | 
            +
                  # If we're passing a method definition directly to `private_class_method`, push a new private scope. That will be
         | 
| 981 | 
            +
                  # applied when the indexer finds the method definition and then popped on `call_node_leave`
         | 
| 982 | 
            +
                  if arguments.first.is_a?(Prism::DefNode)
         | 
| 983 | 
            +
                    @visibility_stack.push(VisibilityScope.new(visibility: Entry::Visibility::PRIVATE))
         | 
| 984 | 
            +
                    return
         | 
| 985 | 
            +
                  end
         | 
| 963 986 |  | 
| 964 | 
            -
             | 
| 965 | 
            -
             | 
| 966 | 
            -
                      when Prism::StringNode
         | 
| 967 | 
            -
                        string_or_symbol_node.content
         | 
| 968 | 
            -
                      when Prism::SymbolNode
         | 
| 969 | 
            -
                        string_or_symbol_node.value
         | 
| 970 | 
            -
                      end
         | 
| 971 | 
            -
                      next unless method_name
         | 
| 987 | 
            +
                  owner_name = @owner_stack.last&.name
         | 
| 988 | 
            +
                  return unless owner_name
         | 
| 972 989 |  | 
| 973 | 
            -
             | 
| 974 | 
            -
             | 
| 990 | 
            +
                  # private_class_method accepts strings, symbols or arrays of strings and symbols as arguments. Here we build a
         | 
| 991 | 
            +
                  # single list of all of the method names that have to be made private
         | 
| 992 | 
            +
                  arrays, others = T.cast(
         | 
| 993 | 
            +
                    arguments.partition { |argument| argument.is_a?(Prism::ArrayNode) },
         | 
| 994 | 
            +
                    [T::Array[Prism::ArrayNode], T::Array[Prism::Node]],
         | 
| 995 | 
            +
                  )
         | 
| 996 | 
            +
                  arrays.each { |array| others.concat(array.elements) }
         | 
| 975 997 |  | 
| 976 | 
            -
             | 
| 977 | 
            -
             | 
| 998 | 
            +
                  names = others.filter_map do |argument|
         | 
| 999 | 
            +
                    case argument
         | 
| 1000 | 
            +
                    when Prism::StringNode
         | 
| 1001 | 
            +
                      argument.unescaped
         | 
| 1002 | 
            +
                    when Prism::SymbolNode
         | 
| 1003 | 
            +
                      argument.value
         | 
| 1004 | 
            +
                    end
         | 
| 1005 | 
            +
                  end
         | 
| 978 1006 |  | 
| 979 | 
            -
             | 
| 980 | 
            -
             | 
| 981 | 
            -
             | 
| 1007 | 
            +
                  names.each do |name|
         | 
| 1008 | 
            +
                    entries = @index.resolve_method(name, @index.existing_or_new_singleton_class(owner_name).name)
         | 
| 1009 | 
            +
                    next unless entries
         | 
| 1010 | 
            +
             | 
| 1011 | 
            +
                    entries.each do |entry|
         | 
| 1012 | 
            +
                      entry.visibility = Entry::Visibility::PRIVATE
         | 
| 982 1013 | 
             
                    end
         | 
| 983 1014 | 
             
                  end
         | 
| 984 1015 | 
             
                end
         | 
| 985 1016 |  | 
| 986 | 
            -
                sig { returns( | 
| 987 | 
            -
                def  | 
| 1017 | 
            +
                sig { returns(VisibilityScope) }
         | 
| 1018 | 
            +
                def current_visibility_scope
         | 
| 988 1019 | 
             
                  T.must(@visibility_stack.last)
         | 
| 989 1020 | 
             
                end
         | 
| 990 1021 |  | 
| @@ -1091,7 +1122,7 @@ module RubyIndexer | |
| 1091 1122 |  | 
| 1092 1123 | 
             
                sig { params(short_name: String, entry: Entry::Namespace).void }
         | 
| 1093 1124 | 
             
                def advance_namespace_stack(short_name, entry)
         | 
| 1094 | 
            -
                  @visibility_stack.push( | 
| 1125 | 
            +
                  @visibility_stack.push(VisibilityScope.public_scope)
         | 
| 1095 1126 | 
             
                  @owner_stack << entry
         | 
| 1096 1127 | 
             
                  @index.add(entry)
         | 
| 1097 1128 | 
             
                  @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,82 @@ 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 | 
            +
             | 
| 912 | 
            +
                def test_making_several_class_methods_private
         | 
| 913 | 
            +
                  index(<<~RUBY)
         | 
| 914 | 
            +
                    class Foo
         | 
| 915 | 
            +
                      def self.bar; end
         | 
| 916 | 
            +
                      def self.baz; end
         | 
| 917 | 
            +
                      def self.qux; end
         | 
| 918 | 
            +
             | 
| 919 | 
            +
                      private_class_method :bar, :baz, :qux
         | 
| 920 | 
            +
             | 
| 921 | 
            +
                      def initialize
         | 
| 922 | 
            +
                      end
         | 
| 923 | 
            +
                    end
         | 
| 924 | 
            +
                  RUBY
         | 
| 925 | 
            +
                end
         | 
| 926 | 
            +
             | 
| 849 927 | 
             
                private
         | 
| 850 928 |  | 
| 851 929 | 
             
                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
         | 
| @@ -91,18 +90,17 @@ module RubyLsp | |
| 91 90 | 
             
                    # The following requests need to be executed in the main thread directly to avoid concurrency issues. Everything
         | 
| 92 91 | 
             
                    # else is pushed into the incoming queue
         | 
| 93 92 | 
             
                    case method
         | 
| 94 | 
            -
                    when "initialize", "initialized", "textDocument/didOpen", "textDocument/didClose", "textDocument/didChange" | 
| 95 | 
            -
                         "$/cancelRequest"
         | 
| 93 | 
            +
                    when "initialize", "initialized", "textDocument/didOpen", "textDocument/didClose", "textDocument/didChange"
         | 
| 96 94 | 
             
                      process_message(message)
         | 
| 97 95 | 
             
                    when "shutdown"
         | 
| 98 | 
            -
                      @ | 
| 96 | 
            +
                      @global_state.synchronize do
         | 
| 99 97 | 
             
                        send_log_message("Shutting down Ruby LSP...")
         | 
| 100 98 | 
             
                        shutdown
         | 
| 101 99 | 
             
                        run_shutdown
         | 
| 102 100 | 
             
                        @writer.write(Result.new(id: message[:id], response: nil).to_hash)
         | 
| 103 101 | 
             
                      end
         | 
| 104 102 | 
             
                    when "exit"
         | 
| 105 | 
            -
                      @ | 
| 103 | 
            +
                      @global_state.synchronize do
         | 
| 106 104 | 
             
                        status = @incoming_queue.closed? ? 0 : 1
         | 
| 107 105 | 
             
                        send_log_message("Shutdown complete with status #{status}")
         | 
| 108 106 | 
             
                        exit(status)
         | 
| @@ -157,13 +155,9 @@ module RubyLsp | |
| 157 155 | 
             
                      id = message[:id]
         | 
| 158 156 |  | 
| 159 157 | 
             
                      # Check if the request was cancelled before trying to process it
         | 
| 160 | 
            -
                      @ | 
| 158 | 
            +
                      @global_state.synchronize do
         | 
| 161 159 | 
             
                        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 | 
            -
                          ))
         | 
| 160 | 
            +
                          send_message(Result.new(id: id, response: nil))
         | 
| 167 161 | 
             
                          @cancelled_requests.delete(id)
         | 
| 168 162 | 
             
                          next
         | 
| 169 163 | 
             
                        end
         |