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
| @@ -19,8 +19,8 @@ module RubyLsp | |
| 19 19 | 
             
                end
         | 
| 20 20 | 
             
                attr_reader :code_units_cache
         | 
| 21 21 |  | 
| 22 | 
            -
                sig { params(source: String, version: Integer, uri: URI::Generic,  | 
| 23 | 
            -
                def initialize(source:, version:, uri:,  | 
| 22 | 
            +
                sig { params(source: String, version: Integer, uri: URI::Generic, global_state: GlobalState).void }
         | 
| 23 | 
            +
                def initialize(source:, version:, uri:, global_state:)
         | 
| 24 24 | 
             
                  # This has to be initialized before calling super because we call `parse` in the parent constructor, which
         | 
| 25 25 | 
             
                  # overrides this with the proper virtual host language source
         | 
| 26 26 | 
             
                  @host_language_source = T.let("", String)
         | 
| @@ -63,9 +63,11 @@ module RubyLsp | |
| 63 63 | 
             
                  ).returns(NodeContext)
         | 
| 64 64 | 
             
                end
         | 
| 65 65 | 
             
                def locate_node(position, node_types: [])
         | 
| 66 | 
            +
                  char_position, _ = find_index_by_position(position)
         | 
| 67 | 
            +
             | 
| 66 68 | 
             
                  RubyDocument.locate(
         | 
| 67 69 | 
             
                    @parse_result.value,
         | 
| 68 | 
            -
                     | 
| 70 | 
            +
                    char_position,
         | 
| 69 71 | 
             
                    code_units_cache: @code_units_cache,
         | 
| 70 72 | 
             
                    node_types: node_types,
         | 
| 71 73 | 
             
                  )
         | 
| @@ -29,6 +29,9 @@ module RubyLsp | |
| 29 29 | 
             
                sig { returns(ClientCapabilities) }
         | 
| 30 30 | 
             
                attr_reader :client_capabilities
         | 
| 31 31 |  | 
| 32 | 
            +
                sig { returns(URI::Generic) }
         | 
| 33 | 
            +
                attr_reader :workspace_uri
         | 
| 34 | 
            +
             | 
| 32 35 | 
             
                sig { void }
         | 
| 33 36 | 
             
                def initialize
         | 
| 34 37 | 
             
                  @workspace_uri = T.let(URI::Generic.from_path(path: Dir.pwd), URI::Generic)
         | 
| @@ -53,6 +56,12 @@ module RubyLsp | |
| 53 56 | 
             
                  )
         | 
| 54 57 | 
             
                  @client_capabilities = T.let(ClientCapabilities.new, ClientCapabilities)
         | 
| 55 58 | 
             
                  @enabled_feature_flags = T.let({}, T::Hash[Symbol, T::Boolean])
         | 
| 59 | 
            +
                  @mutex = T.let(Mutex.new, Mutex)
         | 
| 60 | 
            +
                end
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                sig { type_parameters(:T).params(block: T.proc.returns(T.type_parameter(:T))).returns(T.type_parameter(:T)) }
         | 
| 63 | 
            +
                def synchronize(&block)
         | 
| 64 | 
            +
                  @mutex.synchronize(&block)
         | 
| 56 65 | 
             
                end
         | 
| 57 66 |  | 
| 58 67 | 
             
                sig { params(addon_name: String).returns(T.nilable(T::Hash[Symbol, T.untyped])) }
         | 
| @@ -8,8 +8,8 @@ module RubyLsp | |
| 8 8 |  | 
| 9 9 | 
             
                ParseResultType = type_member { { fixed: T::Array[RBS::AST::Declarations::Base] } }
         | 
| 10 10 |  | 
| 11 | 
            -
                sig { params(source: String, version: Integer, uri: URI::Generic,  | 
| 12 | 
            -
                def initialize(source:, version:, uri:,  | 
| 11 | 
            +
                sig { params(source: String, version: Integer, uri: URI::Generic, global_state: GlobalState).void }
         | 
| 12 | 
            +
                def initialize(source:, version:, uri:, global_state:)
         | 
| 13 13 | 
             
                  @syntax_error = T.let(false, T::Boolean)
         | 
| 14 14 | 
             
                  super
         | 
| 15 15 | 
             
                end
         | 
| @@ -92,9 +92,7 @@ module RubyLsp | |
| 92 92 | 
             
                    source_range = @code_action.dig(:data, :range)
         | 
| 93 93 | 
             
                    return Error::EmptySelection if source_range[:start] == source_range[:end]
         | 
| 94 94 |  | 
| 95 | 
            -
                     | 
| 96 | 
            -
                    start_index = scanner.find_char_position(source_range[:start])
         | 
| 97 | 
            -
                    end_index = scanner.find_char_position(source_range[:end])
         | 
| 95 | 
            +
                    start_index, end_index = @document.find_index_by_position(source_range[:start], source_range[:end])
         | 
| 98 96 | 
             
                    extracted_source = T.must(@document.source[start_index...end_index])
         | 
| 99 97 |  | 
| 100 98 | 
             
                    # Find the closest statements node, so that we place the refactor in a valid position
         | 
| @@ -192,9 +190,7 @@ module RubyLsp | |
| 192 190 | 
             
                    source_range = @code_action.dig(:data, :range)
         | 
| 193 191 | 
             
                    return Error::EmptySelection if source_range[:start] == source_range[:end]
         | 
| 194 192 |  | 
| 195 | 
            -
                     | 
| 196 | 
            -
                    start_index = scanner.find_char_position(source_range[:start])
         | 
| 197 | 
            -
                    end_index = scanner.find_char_position(source_range[:end])
         | 
| 193 | 
            +
                    start_index, end_index = @document.find_index_by_position(source_range[:start], source_range[:end])
         | 
| 198 194 | 
             
                    extracted_source = T.must(@document.source[start_index...end_index])
         | 
| 199 195 |  | 
| 200 196 | 
             
                    # Find the closest method declaration node, so that we place the refactor in a valid position
         | 
| @@ -40,7 +40,8 @@ module RubyLsp | |
| 40 40 | 
             
                    @dispatcher = dispatcher
         | 
| 41 41 | 
             
                    # Completion always receives the position immediately after the character that was just typed. Here we adjust it
         | 
| 42 42 | 
             
                    # back by 1, so that we find the right node
         | 
| 43 | 
            -
                    char_position = document. | 
| 43 | 
            +
                    char_position, _ = document.find_index_by_position(params[:position])
         | 
| 44 | 
            +
                    char_position -= 1
         | 
| 44 45 | 
             
                    delegate_request_if_needed!(global_state, document, char_position)
         | 
| 45 46 |  | 
| 46 47 | 
             
                    node_context = RubyDocument.locate(
         | 
| @@ -29,7 +29,7 @@ module RubyLsp | |
| 29 29 | 
             
                    )
         | 
| 30 30 | 
             
                    @dispatcher = dispatcher
         | 
| 31 31 |  | 
| 32 | 
            -
                    char_position = document. | 
| 32 | 
            +
                    char_position, _ = document.find_index_by_position(position)
         | 
| 33 33 | 
             
                    delegate_request_if_needed!(global_state, document, char_position)
         | 
| 34 34 |  | 
| 35 35 | 
             
                    node_context = RubyDocument.locate(
         | 
| @@ -25,7 +25,7 @@ module RubyLsp | |
| 25 25 | 
             
                  end
         | 
| 26 26 | 
             
                  def initialize(global_state, document, position, dispatcher)
         | 
| 27 27 | 
             
                    super()
         | 
| 28 | 
            -
                    char_position = document. | 
| 28 | 
            +
                    char_position, _ = document.find_index_by_position(position)
         | 
| 29 29 | 
             
                    delegate_request_if_needed!(global_state, document, char_position)
         | 
| 30 30 |  | 
| 31 31 | 
             
                    node_context = RubyDocument.locate(
         | 
| @@ -34,7 +34,7 @@ module RubyLsp | |
| 34 34 | 
             
                  def initialize(document, global_state, position, dispatcher, sorbet_level)
         | 
| 35 35 | 
             
                    super()
         | 
| 36 36 |  | 
| 37 | 
            -
                    char_position = document. | 
| 37 | 
            +
                    char_position, _ = document.find_index_by_position(position)
         | 
| 38 38 | 
             
                    delegate_request_if_needed!(global_state, document, char_position)
         | 
| 39 39 |  | 
| 40 40 | 
             
                    node_context = RubyDocument.locate(
         | 
| @@ -24,7 +24,7 @@ module RubyLsp | |
| 24 24 |  | 
| 25 25 | 
             
                  sig { override.returns(T.nilable(Interface::Range)) }
         | 
| 26 26 | 
             
                  def perform
         | 
| 27 | 
            -
                    char_position = @document. | 
| 27 | 
            +
                    char_position, _ = @document.find_index_by_position(@position)
         | 
| 28 28 |  | 
| 29 29 | 
             
                    node_context = RubyDocument.locate(
         | 
| 30 30 | 
             
                      @document.parse_result.value,
         | 
| @@ -30,7 +30,7 @@ module RubyLsp | |
| 30 30 | 
             
                  sig { override.returns(T::Array[Interface::Location]) }
         | 
| 31 31 | 
             
                  def perform
         | 
| 32 32 | 
             
                    position = @params[:position]
         | 
| 33 | 
            -
                    char_position = @document. | 
| 33 | 
            +
                    char_position, _ = @document.find_index_by_position(position)
         | 
| 34 34 |  | 
| 35 35 | 
             
                    node_context = RubyDocument.locate(
         | 
| 36 36 | 
             
                      @document.parse_result.value,
         | 
| @@ -40,7 +40,7 @@ module RubyLsp | |
| 40 40 |  | 
| 41 41 | 
             
                  sig { override.returns(T.nilable(Interface::WorkspaceEdit)) }
         | 
| 42 42 | 
             
                  def perform
         | 
| 43 | 
            -
                    char_position = @document. | 
| 43 | 
            +
                    char_position, _ = @document.find_index_by_position(@position)
         | 
| 44 44 |  | 
| 45 45 | 
             
                    node_context = RubyDocument.locate(
         | 
| 46 46 | 
             
                      @document.parse_result.value,
         | 
| @@ -31,10 +31,7 @@ module RubyLsp | |
| 31 31 | 
             
                  sig { returns(String) }
         | 
| 32 32 | 
             
                  def ast_for_range
         | 
| 33 33 | 
             
                    range = T.must(@range)
         | 
| 34 | 
            -
             | 
| 35 | 
            -
                    scanner = @document.create_scanner
         | 
| 36 | 
            -
                    start_char = scanner.find_char_position(range[:start])
         | 
| 37 | 
            -
                    end_char = scanner.find_char_position(range[:end])
         | 
| 34 | 
            +
                    start_char, end_char = @document.find_index_by_position(range[:start], range[:end])
         | 
| 38 35 |  | 
| 39 36 | 
             
                    queue = @tree.statements.body.dup
         | 
| 40 37 | 
             
                    found_nodes = []
         | 
| @@ -36,7 +36,7 @@ module RubyLsp | |
| 36 36 | 
             
                  def initialize(document, global_state, position, context, dispatcher, sorbet_level) # rubocop:disable Metrics/ParameterLists
         | 
| 37 37 | 
             
                    super()
         | 
| 38 38 |  | 
| 39 | 
            -
                    char_position = document. | 
| 39 | 
            +
                    char_position, _ = document.find_index_by_position(position)
         | 
| 40 40 | 
             
                    delegate_request_if_needed!(global_state, document, char_position)
         | 
| 41 41 |  | 
| 42 42 | 
             
                    node_context = RubyDocument.locate(
         | 
| @@ -8,6 +8,22 @@ module RubyLsp | |
| 8 8 |  | 
| 9 9 | 
             
                ParseResultType = type_member { { fixed: Prism::ParseResult } }
         | 
| 10 10 |  | 
| 11 | 
            +
                METHODS_THAT_CHANGE_DECLARATIONS = [
         | 
| 12 | 
            +
                  :private_constant,
         | 
| 13 | 
            +
                  :attr_reader,
         | 
| 14 | 
            +
                  :attr_writer,
         | 
| 15 | 
            +
                  :attr_accessor,
         | 
| 16 | 
            +
                  :alias_method,
         | 
| 17 | 
            +
                  :include,
         | 
| 18 | 
            +
                  :prepend,
         | 
| 19 | 
            +
                  :extend,
         | 
| 20 | 
            +
                  :public,
         | 
| 21 | 
            +
                  :protected,
         | 
| 22 | 
            +
                  :private,
         | 
| 23 | 
            +
                  :module_function,
         | 
| 24 | 
            +
                  :private_class_method,
         | 
| 25 | 
            +
                ].freeze
         | 
| 26 | 
            +
             | 
| 11 27 | 
             
                class SorbetLevel < T::Enum
         | 
| 12 28 | 
             
                  enums do
         | 
| 13 29 | 
             
                    None = new("none")
         | 
| @@ -142,8 +158,8 @@ module RubyLsp | |
| 142 158 | 
             
                end
         | 
| 143 159 | 
             
                attr_reader :code_units_cache
         | 
| 144 160 |  | 
| 145 | 
            -
                sig { params(source: String, version: Integer, uri: URI::Generic,  | 
| 146 | 
            -
                def initialize(source:, version:, uri:,  | 
| 161 | 
            +
                sig { params(source: String, version: Integer, uri: URI::Generic, global_state: GlobalState).void }
         | 
| 162 | 
            +
                def initialize(source:, version:, uri:, global_state:)
         | 
| 147 163 | 
             
                  super
         | 
| 148 164 | 
             
                  @code_units_cache = T.let(@parse_result.code_units_cache(@encoding), T.any(
         | 
| 149 165 | 
             
                    T.proc.params(arg0: Integer).returns(Integer),
         | 
| @@ -198,9 +214,8 @@ module RubyLsp | |
| 198 214 | 
             
                  ).returns(T.nilable(Prism::Node))
         | 
| 199 215 | 
             
                end
         | 
| 200 216 | 
             
                def locate_first_within_range(range, node_types: [])
         | 
| 201 | 
            -
                   | 
| 202 | 
            -
             | 
| 203 | 
            -
                  end_position = scanner.find_char_position(range[:end])
         | 
| 217 | 
            +
                  start_position, end_position = find_index_by_position(range[:start], range[:end])
         | 
| 218 | 
            +
             | 
| 204 219 | 
             
                  desired_range = (start_position...end_position)
         | 
| 205 220 | 
             
                  queue = T.let(@parse_result.value.child_nodes.compact, T::Array[T.nilable(Prism::Node)])
         | 
| 206 221 |  | 
| @@ -232,12 +247,66 @@ module RubyLsp | |
| 232 247 | 
             
                  ).returns(NodeContext)
         | 
| 233 248 | 
             
                end
         | 
| 234 249 | 
             
                def locate_node(position, node_types: [])
         | 
| 250 | 
            +
                  char_position, _ = find_index_by_position(position)
         | 
| 251 | 
            +
             | 
| 235 252 | 
             
                  RubyDocument.locate(
         | 
| 236 253 | 
             
                    @parse_result.value,
         | 
| 237 | 
            -
                     | 
| 254 | 
            +
                    char_position,
         | 
| 238 255 | 
             
                    code_units_cache: @code_units_cache,
         | 
| 239 256 | 
             
                    node_types: node_types,
         | 
| 240 257 | 
             
                  )
         | 
| 241 258 | 
             
                end
         | 
| 259 | 
            +
             | 
| 260 | 
            +
                sig { returns(T::Boolean) }
         | 
| 261 | 
            +
                def last_edit_may_change_declarations?
         | 
| 262 | 
            +
                  # This method controls when we should index documents. If there's no recent edit and the document has just been
         | 
| 263 | 
            +
                  # opened, we need to index it
         | 
| 264 | 
            +
                  return true unless @last_edit
         | 
| 265 | 
            +
             | 
| 266 | 
            +
                  case @last_edit
         | 
| 267 | 
            +
                  when Delete
         | 
| 268 | 
            +
                    # Not optimized yet. It's not trivial to identify that a declaration has been removed since the source is no
         | 
| 269 | 
            +
                    # longer there and we don't remember the deleted text
         | 
| 270 | 
            +
                    true
         | 
| 271 | 
            +
                  when Insert, Replace
         | 
| 272 | 
            +
                    position_may_impact_declarations?(@last_edit.range[:start])
         | 
| 273 | 
            +
                  else
         | 
| 274 | 
            +
                    false
         | 
| 275 | 
            +
                  end
         | 
| 276 | 
            +
                end
         | 
| 277 | 
            +
             | 
| 278 | 
            +
                private
         | 
| 279 | 
            +
             | 
| 280 | 
            +
                sig { params(position: T::Hash[Symbol, Integer]).returns(T::Boolean) }
         | 
| 281 | 
            +
                def position_may_impact_declarations?(position)
         | 
| 282 | 
            +
                  node_context = locate_node(position)
         | 
| 283 | 
            +
                  node_at_edit = node_context.node
         | 
| 284 | 
            +
             | 
| 285 | 
            +
                  # Adjust to the parent when editing the constant of a class/module declaration
         | 
| 286 | 
            +
                  if node_at_edit.is_a?(Prism::ConstantReadNode) || node_at_edit.is_a?(Prism::ConstantPathNode)
         | 
| 287 | 
            +
                    node_at_edit = node_context.parent
         | 
| 288 | 
            +
                  end
         | 
| 289 | 
            +
             | 
| 290 | 
            +
                  case node_at_edit
         | 
| 291 | 
            +
                  when Prism::ClassNode, Prism::ModuleNode, Prism::SingletonClassNode, Prism::DefNode,
         | 
| 292 | 
            +
                      Prism::ConstantPathWriteNode, Prism::ConstantPathOrWriteNode, Prism::ConstantPathOperatorWriteNode,
         | 
| 293 | 
            +
                      Prism::ConstantPathAndWriteNode, Prism::ConstantOrWriteNode, Prism::ConstantWriteNode,
         | 
| 294 | 
            +
                      Prism::ConstantAndWriteNode, Prism::ConstantOperatorWriteNode, Prism::GlobalVariableAndWriteNode,
         | 
| 295 | 
            +
                      Prism::GlobalVariableOperatorWriteNode, Prism::GlobalVariableOrWriteNode, Prism::GlobalVariableTargetNode,
         | 
| 296 | 
            +
                      Prism::GlobalVariableWriteNode, Prism::InstanceVariableWriteNode, Prism::InstanceVariableAndWriteNode,
         | 
| 297 | 
            +
                      Prism::InstanceVariableOperatorWriteNode, Prism::InstanceVariableOrWriteNode,
         | 
| 298 | 
            +
                      Prism::InstanceVariableTargetNode, Prism::AliasMethodNode
         | 
| 299 | 
            +
                    true
         | 
| 300 | 
            +
                  when Prism::MultiWriteNode
         | 
| 301 | 
            +
                    [*node_at_edit.lefts, *node_at_edit.rest, *node_at_edit.rights].any? do |node|
         | 
| 302 | 
            +
                      node.is_a?(Prism::ConstantTargetNode) || node.is_a?(Prism::ConstantPathTargetNode)
         | 
| 303 | 
            +
                    end
         | 
| 304 | 
            +
                  when Prism::CallNode
         | 
| 305 | 
            +
                    receiver = node_at_edit.receiver
         | 
| 306 | 
            +
                    (!receiver || receiver.is_a?(Prism::SelfNode)) && METHODS_THAT_CHANGE_DECLARATIONS.include?(node_at_edit.name)
         | 
| 307 | 
            +
                  else
         | 
| 308 | 
            +
                    false
         | 
| 309 | 
            +
                  end
         | 
| 310 | 
            +
                end
         | 
| 242 311 | 
             
              end
         | 
| 243 312 | 
             
            end
         | 
    
        data/lib/ruby_lsp/server.rb
    CHANGED
    
    | @@ -107,7 +107,7 @@ module RubyLsp | |
| 107 107 | 
             
                      ),
         | 
| 108 108 | 
             
                    )
         | 
| 109 109 | 
             
                  when "$/cancelRequest"
         | 
| 110 | 
            -
                    @ | 
| 110 | 
            +
                    @global_state.synchronize { @cancelled_requests << message[:params][:id] }
         | 
| 111 111 | 
             
                  when nil
         | 
| 112 112 | 
             
                    process_response(message) if message[:result]
         | 
| 113 113 | 
             
                  end
         | 
| @@ -298,29 +298,11 @@ module RubyLsp | |
| 298 298 |  | 
| 299 299 | 
             
                  # Not every client supports dynamic registration or file watching
         | 
| 300 300 | 
             
                  if @global_state.client_capabilities.supports_watching_files
         | 
| 301 | 
            -
                    send_message(
         | 
| 302 | 
            -
             | 
| 303 | 
            -
             | 
| 304 | 
            -
             | 
| 305 | 
            -
             | 
| 306 | 
            -
                          registrations: [
         | 
| 307 | 
            -
                            # Register watching Ruby files
         | 
| 308 | 
            -
                            Interface::Registration.new(
         | 
| 309 | 
            -
                              id: "workspace/didChangeWatchedFiles",
         | 
| 310 | 
            -
                              method: "workspace/didChangeWatchedFiles",
         | 
| 311 | 
            -
                              register_options: Interface::DidChangeWatchedFilesRegistrationOptions.new(
         | 
| 312 | 
            -
                                watchers: [
         | 
| 313 | 
            -
                                  Interface::FileSystemWatcher.new(
         | 
| 314 | 
            -
                                    glob_pattern: "**/*.rb",
         | 
| 315 | 
            -
                                    kind: Constant::WatchKind::CREATE | Constant::WatchKind::CHANGE | Constant::WatchKind::DELETE,
         | 
| 316 | 
            -
                                  ),
         | 
| 317 | 
            -
                                ],
         | 
| 318 | 
            -
                              ),
         | 
| 319 | 
            -
                            ),
         | 
| 320 | 
            -
                          ],
         | 
| 321 | 
            -
                        ),
         | 
| 322 | 
            -
                      ),
         | 
| 323 | 
            -
                    )
         | 
| 301 | 
            +
                    send_message(Request.register_watched_files(@current_request_id, "**/*.rb"))
         | 
| 302 | 
            +
                    send_message(Request.register_watched_files(
         | 
| 303 | 
            +
                      @current_request_id,
         | 
| 304 | 
            +
                      Interface::RelativePattern.new(base_uri: @global_state.workspace_uri.to_s, pattern: ".rubocop.yml"),
         | 
| 305 | 
            +
                    ))
         | 
| 324 306 | 
             
                  end
         | 
| 325 307 |  | 
| 326 308 | 
             
                  process_indexing_configuration(options.dig(:initializationOptions, :indexing))
         | 
| @@ -377,58 +359,48 @@ module RubyLsp | |
| 377 359 |  | 
| 378 360 | 
             
                sig { params(message: T::Hash[Symbol, T.untyped]).void }
         | 
| 379 361 | 
             
                def text_document_did_open(message)
         | 
| 380 | 
            -
                   | 
| 381 | 
            -
             | 
| 382 | 
            -
             | 
| 383 | 
            -
                     | 
| 384 | 
            -
             | 
| 385 | 
            -
                     | 
| 386 | 
            -
             | 
| 387 | 
            -
                     | 
| 388 | 
            -
             | 
| 389 | 
            -
                    end
         | 
| 362 | 
            +
                  text_document = message.dig(:params, :textDocument)
         | 
| 363 | 
            +
                  language_id = case text_document[:languageId]
         | 
| 364 | 
            +
                  when "erb", "eruby"
         | 
| 365 | 
            +
                    Document::LanguageId::ERB
         | 
| 366 | 
            +
                  when "rbs"
         | 
| 367 | 
            +
                    Document::LanguageId::RBS
         | 
| 368 | 
            +
                  else
         | 
| 369 | 
            +
                    Document::LanguageId::Ruby
         | 
| 370 | 
            +
                  end
         | 
| 390 371 |  | 
| 391 | 
            -
             | 
| 392 | 
            -
             | 
| 393 | 
            -
             | 
| 394 | 
            -
             | 
| 395 | 
            -
             | 
| 396 | 
            -
             | 
| 397 | 
            -
                    )
         | 
| 372 | 
            +
                  document = @store.set(
         | 
| 373 | 
            +
                    uri: text_document[:uri],
         | 
| 374 | 
            +
                    source: text_document[:text],
         | 
| 375 | 
            +
                    version: text_document[:version],
         | 
| 376 | 
            +
                    language_id: language_id,
         | 
| 377 | 
            +
                  )
         | 
| 398 378 |  | 
| 399 | 
            -
             | 
| 400 | 
            -
             | 
| 401 | 
            -
             | 
| 402 | 
            -
             | 
| 403 | 
            -
             | 
| 379 | 
            +
                  if document.past_expensive_limit? && text_document[:uri].scheme == "file"
         | 
| 380 | 
            +
                    log_message = <<~MESSAGE
         | 
| 381 | 
            +
                      The file #{text_document[:uri].path} is too long. For performance reasons, semantic highlighting and
         | 
| 382 | 
            +
                      diagnostics will be disabled.
         | 
| 383 | 
            +
                    MESSAGE
         | 
| 404 384 |  | 
| 405 | 
            -
             | 
| 406 | 
            -
             | 
| 407 | 
            -
             | 
| 408 | 
            -
             | 
| 409 | 
            -
             | 
| 410 | 
            -
             | 
| 411 | 
            -
                          ),
         | 
| 385 | 
            +
                    send_message(
         | 
| 386 | 
            +
                      Notification.new(
         | 
| 387 | 
            +
                        method: "window/logMessage",
         | 
| 388 | 
            +
                        params: Interface::LogMessageParams.new(
         | 
| 389 | 
            +
                          type: Constant::MessageType::WARNING,
         | 
| 390 | 
            +
                          message: log_message,
         | 
| 412 391 | 
             
                        ),
         | 
| 413 | 
            -
                      )
         | 
| 414 | 
            -
                     | 
| 392 | 
            +
                      ),
         | 
| 393 | 
            +
                    )
         | 
| 415 394 | 
             
                  end
         | 
| 416 395 | 
             
                end
         | 
| 417 396 |  | 
| 418 397 | 
             
                sig { params(message: T::Hash[Symbol, T.untyped]).void }
         | 
| 419 398 | 
             
                def text_document_did_close(message)
         | 
| 420 | 
            -
                   | 
| 421 | 
            -
             | 
| 422 | 
            -
                    @store.delete(uri)
         | 
| 399 | 
            +
                  uri = message.dig(:params, :textDocument, :uri)
         | 
| 400 | 
            +
                  @store.delete(uri)
         | 
| 423 401 |  | 
| 424 | 
            -
             | 
| 425 | 
            -
             | 
| 426 | 
            -
                      Notification.new(
         | 
| 427 | 
            -
                        method: "textDocument/publishDiagnostics",
         | 
| 428 | 
            -
                        params: Interface::PublishDiagnosticsParams.new(uri: uri.to_s, diagnostics: []),
         | 
| 429 | 
            -
                      ),
         | 
| 430 | 
            -
                    )
         | 
| 431 | 
            -
                  end
         | 
| 402 | 
            +
                  # Clear diagnostics for the closed file, so that they no longer appear in the problems tab
         | 
| 403 | 
            +
                  send_message(Notification.publish_diagnostics(uri.to_s, []))
         | 
| 432 404 | 
             
                end
         | 
| 433 405 |  | 
| 434 406 | 
             
                sig { params(message: T::Hash[Symbol, T.untyped]).void }
         | 
| @@ -436,9 +408,7 @@ module RubyLsp | |
| 436 408 | 
             
                  params = message[:params]
         | 
| 437 409 | 
             
                  text_document = params[:textDocument]
         | 
| 438 410 |  | 
| 439 | 
            -
                  @ | 
| 440 | 
            -
                    @store.push_edits(uri: text_document[:uri], edits: params[:contentChanges], version: text_document[:version])
         | 
| 441 | 
            -
                  end
         | 
| 411 | 
            +
                  @store.push_edits(uri: text_document[:uri], edits: params[:contentChanges], version: text_document[:version])
         | 
| 442 412 | 
             
                end
         | 
| 443 413 |  | 
| 444 414 | 
             
                sig { params(message: T::Hash[Symbol, T.untyped]).void }
         | 
| @@ -493,7 +463,22 @@ module RubyLsp | |
| 493 463 | 
             
                  document_link = Requests::DocumentLink.new(uri, parse_result.comments, dispatcher)
         | 
| 494 464 | 
             
                  code_lens = Requests::CodeLens.new(@global_state, uri, dispatcher)
         | 
| 495 465 | 
             
                  inlay_hint = Requests::InlayHints.new(document, T.must(@store.features_configuration.dig(:inlayHint)), dispatcher)
         | 
| 496 | 
            -
             | 
| 466 | 
            +
             | 
| 467 | 
            +
                  if document.is_a?(RubyDocument) && document.last_edit_may_change_declarations?
         | 
| 468 | 
            +
                    # Re-index the file as it is modified. This mode of indexing updates entries only. Require path trees are only
         | 
| 469 | 
            +
                    # updated on save
         | 
| 470 | 
            +
                    @global_state.synchronize do
         | 
| 471 | 
            +
                      send_log_message("Detected that last edit may have modified declarations. Re-indexing #{uri}")
         | 
| 472 | 
            +
             | 
| 473 | 
            +
                      @global_state.index.handle_change(uri) do |index|
         | 
| 474 | 
            +
                        index.delete(uri, skip_require_paths_tree: true)
         | 
| 475 | 
            +
                        RubyIndexer::DeclarationListener.new(index, dispatcher, parse_result, uri, collect_comments: true)
         | 
| 476 | 
            +
                        dispatcher.dispatch(parse_result.value)
         | 
| 477 | 
            +
                      end
         | 
| 478 | 
            +
                    end
         | 
| 479 | 
            +
                  else
         | 
| 480 | 
            +
                    dispatcher.dispatch(parse_result.value)
         | 
| 481 | 
            +
                  end
         | 
| 497 482 |  | 
| 498 483 | 
             
                  # Store all responses retrieve in this round of visits in the cache and then return the response for the request
         | 
| 499 484 | 
             
                  # we actually received
         | 
| @@ -1011,26 +996,55 @@ module RubyLsp | |
| 1011 996 | 
             
                    uri = URI(change[:uri])
         | 
| 1012 997 | 
             
                    file_path = uri.to_standardized_path
         | 
| 1013 998 | 
             
                    next if file_path.nil? || File.directory?(file_path)
         | 
| 1014 | 
            -
                    next unless file_path.end_with?(".rb")
         | 
| 1015 999 |  | 
| 1016 | 
            -
                     | 
| 1017 | 
            -
             | 
| 1000 | 
            +
                    if file_path.end_with?(".rb")
         | 
| 1001 | 
            +
                      handle_ruby_file_change(index, file_path, change[:type])
         | 
| 1002 | 
            +
                      next
         | 
| 1003 | 
            +
                    end
         | 
| 1018 1004 |  | 
| 1019 | 
            -
                     | 
| 1005 | 
            +
                    file_name = File.basename(file_path)
         | 
| 1020 1006 |  | 
| 1021 | 
            -
                     | 
| 1022 | 
            -
             | 
| 1023 | 
            -
                      index.index_single(uri, content)
         | 
| 1024 | 
            -
                    when Constant::FileChangeType::CHANGED
         | 
| 1025 | 
            -
                      index.handle_change(uri, content)
         | 
| 1026 | 
            -
                    when Constant::FileChangeType::DELETED
         | 
| 1027 | 
            -
                      index.delete(uri)
         | 
| 1007 | 
            +
                    if file_name == ".rubocop.yml" || file_name == ".rubocop"
         | 
| 1008 | 
            +
                      handle_rubocop_config_change(uri)
         | 
| 1028 1009 | 
             
                    end
         | 
| 1029 1010 | 
             
                  end
         | 
| 1030 1011 |  | 
| 1031 1012 | 
             
                  Addon.file_watcher_addons.each { |addon| T.unsafe(addon).workspace_did_change_watched_files(changes) }
         | 
| 1032 1013 | 
             
                end
         | 
| 1033 1014 |  | 
| 1015 | 
            +
                sig { params(index: RubyIndexer::Index, file_path: String, change_type: Integer).void }
         | 
| 1016 | 
            +
                def handle_ruby_file_change(index, file_path, change_type)
         | 
| 1017 | 
            +
                  load_path_entry = $LOAD_PATH.find { |load_path| file_path.start_with?(load_path) }
         | 
| 1018 | 
            +
                  uri = URI::Generic.from_path(load_path_entry: load_path_entry, path: file_path)
         | 
| 1019 | 
            +
             | 
| 1020 | 
            +
                  content = File.read(file_path)
         | 
| 1021 | 
            +
             | 
| 1022 | 
            +
                  case change_type
         | 
| 1023 | 
            +
                  when Constant::FileChangeType::CREATED
         | 
| 1024 | 
            +
                    index.index_single(uri, content)
         | 
| 1025 | 
            +
                  when Constant::FileChangeType::CHANGED
         | 
| 1026 | 
            +
                    index.handle_change(uri, content)
         | 
| 1027 | 
            +
                  when Constant::FileChangeType::DELETED
         | 
| 1028 | 
            +
                    index.delete(uri)
         | 
| 1029 | 
            +
                  end
         | 
| 1030 | 
            +
                end
         | 
| 1031 | 
            +
             | 
| 1032 | 
            +
                sig { params(uri: URI::Generic).void }
         | 
| 1033 | 
            +
                def handle_rubocop_config_change(uri)
         | 
| 1034 | 
            +
                  return unless defined?(Requests::Support::RuboCopFormatter)
         | 
| 1035 | 
            +
             | 
| 1036 | 
            +
                  send_log_message("Reloading RuboCop since #{uri} changed")
         | 
| 1037 | 
            +
                  @global_state.register_formatter("rubocop", Requests::Support::RuboCopFormatter.new)
         | 
| 1038 | 
            +
             | 
| 1039 | 
            +
                  # Clear all existing diagnostics since the config changed. This has to happen under a mutex because the `state`
         | 
| 1040 | 
            +
                  # hash cannot be mutated during iteration or that will throw an error
         | 
| 1041 | 
            +
                  @global_state.synchronize do
         | 
| 1042 | 
            +
                    @store.each do |uri, _document|
         | 
| 1043 | 
            +
                      send_message(Notification.publish_diagnostics(uri.to_s, []))
         | 
| 1044 | 
            +
                    end
         | 
| 1045 | 
            +
                  end
         | 
| 1046 | 
            +
                end
         | 
| 1047 | 
            +
             | 
| 1034 1048 | 
             
                sig { params(message: T::Hash[Symbol, T.untyped]).void }
         | 
| 1035 1049 | 
             
                def workspace_symbol(message)
         | 
| 1036 1050 | 
             
                  send_message(
         | 
    
        data/lib/ruby_lsp/store.rb
    CHANGED
    
    | @@ -13,8 +13,9 @@ module RubyLsp | |
| 13 13 | 
             
                sig { returns(String) }
         | 
| 14 14 | 
             
                attr_accessor :client_name
         | 
| 15 15 |  | 
| 16 | 
            -
                sig { void }
         | 
| 17 | 
            -
                def initialize
         | 
| 16 | 
            +
                sig { params(global_state: GlobalState).void }
         | 
| 17 | 
            +
                def initialize(global_state)
         | 
| 18 | 
            +
                  @global_state = global_state
         | 
| 18 19 | 
             
                  @state = T.let({}, T::Hash[String, Document[T.untyped]])
         | 
| 19 20 | 
             
                  @features_configuration = T.let(
         | 
| 20 21 | 
             
                    {
         | 
| @@ -61,17 +62,18 @@ module RubyLsp | |
| 61 62 | 
             
                    source: String,
         | 
| 62 63 | 
             
                    version: Integer,
         | 
| 63 64 | 
             
                    language_id: Document::LanguageId,
         | 
| 64 | 
            -
                    encoding: Encoding,
         | 
| 65 65 | 
             
                  ).returns(Document[T.untyped])
         | 
| 66 66 | 
             
                end
         | 
| 67 | 
            -
                def set(uri:, source:, version:, language_id | 
| 68 | 
            -
                  @ | 
| 69 | 
            -
             | 
| 70 | 
            -
                     | 
| 71 | 
            -
             | 
| 72 | 
            -
                     | 
| 73 | 
            -
             | 
| 74 | 
            -
                     | 
| 67 | 
            +
                def set(uri:, source:, version:, language_id:)
         | 
| 68 | 
            +
                  @global_state.synchronize do
         | 
| 69 | 
            +
                    @state[uri.to_s] = case language_id
         | 
| 70 | 
            +
                    when Document::LanguageId::ERB
         | 
| 71 | 
            +
                      ERBDocument.new(source: source, version: version, uri: uri, global_state: @global_state)
         | 
| 72 | 
            +
                    when Document::LanguageId::RBS
         | 
| 73 | 
            +
                      RBSDocument.new(source: source, version: version, uri: uri, global_state: @global_state)
         | 
| 74 | 
            +
                    else
         | 
| 75 | 
            +
                      RubyDocument.new(source: source, version: version, uri: uri, global_state: @global_state)
         | 
| 76 | 
            +
                    end
         | 
| 75 77 | 
             
                  end
         | 
| 76 78 | 
             
                end
         | 
| 77 79 |  | 
| @@ -92,7 +94,9 @@ module RubyLsp | |
| 92 94 |  | 
| 93 95 | 
             
                sig { params(uri: URI::Generic).void }
         | 
| 94 96 | 
             
                def delete(uri)
         | 
| 95 | 
            -
                  @ | 
| 97 | 
            +
                  @global_state.synchronize do
         | 
| 98 | 
            +
                    @state.delete(uri.to_s)
         | 
| 99 | 
            +
                  end
         | 
| 96 100 | 
             
                end
         | 
| 97 101 |  | 
| 98 102 | 
             
                sig { params(uri: URI::Generic).returns(T::Boolean) }
         | 
| @@ -91,29 +91,45 @@ module RubyLsp | |
| 91 91 | 
             
                    return Type.new("#{last}::<Class:#{last}>") if parts.empty?
         | 
| 92 92 |  | 
| 93 93 | 
             
                    Type.new("#{parts.join("::")}::#{last}::<Class:#{last}>")
         | 
| 94 | 
            -
                   | 
| 94 | 
            +
                  when Prism::CallNode
         | 
| 95 | 
            +
                    raw_receiver = receiver.message
         | 
| 95 96 |  | 
| 96 | 
            -
                    raw_receiver  | 
| 97 | 
            -
                      receiver | 
| 98 | 
            -
             | 
| 99 | 
            -
                      receiver | 
| 100 | 
            -
                    end
         | 
| 97 | 
            +
                    if raw_receiver == "new"
         | 
| 98 | 
            +
                      # When invoking `new`, we recursively infer the type of the receiver to get the class type its being invoked
         | 
| 99 | 
            +
                      # on and then return the attached version of that type, since it's being instantiated.
         | 
| 100 | 
            +
                      type = infer_receiver_for_call_node(receiver, node_context)
         | 
| 101 101 |  | 
| 102 | 
            -
             | 
| 103 | 
            -
             | 
| 104 | 
            -
             | 
| 105 | 
            -
             | 
| 106 | 
            -
             | 
| 107 | 
            -
             | 
| 108 | 
            -
             | 
| 109 | 
            -
             | 
| 110 | 
            -
                       | 
| 111 | 
            -
                      name = entries&.first&.name
         | 
| 112 | 
            -
                      GuessedType.new(name) if name
         | 
| 102 | 
            +
                      return unless type
         | 
| 103 | 
            +
             | 
| 104 | 
            +
                      # If the method `new` was overridden, then we cannot assume that it will return a new instance of the class
         | 
| 105 | 
            +
                      new_method = @index.resolve_method("new", type.name)&.first
         | 
| 106 | 
            +
                      return if new_method && new_method.owner&.name != "Class"
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                      type.attached
         | 
| 109 | 
            +
                    elsif raw_receiver
         | 
| 110 | 
            +
                      guess_type(raw_receiver, node_context.nesting)
         | 
| 113 111 | 
             
                    end
         | 
| 112 | 
            +
                  else
         | 
| 113 | 
            +
                    guess_type(receiver.slice, node_context.nesting)
         | 
| 114 114 | 
             
                  end
         | 
| 115 115 | 
             
                end
         | 
| 116 116 |  | 
| 117 | 
            +
                sig { params(raw_receiver: String, nesting: T::Array[String]).returns(T.nilable(GuessedType)) }
         | 
| 118 | 
            +
                def guess_type(raw_receiver, nesting)
         | 
| 119 | 
            +
                  guessed_name = raw_receiver
         | 
| 120 | 
            +
                    .delete_prefix("@")
         | 
| 121 | 
            +
                    .delete_prefix("@@")
         | 
| 122 | 
            +
                    .split("_")
         | 
| 123 | 
            +
                    .map(&:capitalize)
         | 
| 124 | 
            +
                    .join
         | 
| 125 | 
            +
             | 
| 126 | 
            +
                  entries = @index.resolve(guessed_name, nesting) || @index.first_unqualified_const(guessed_name)
         | 
| 127 | 
            +
                  name = entries&.first&.name
         | 
| 128 | 
            +
                  return unless name
         | 
| 129 | 
            +
             | 
| 130 | 
            +
                  GuessedType.new(name)
         | 
| 131 | 
            +
                end
         | 
| 132 | 
            +
             | 
| 117 133 | 
             
                sig { params(node_context: NodeContext).returns(Type) }
         | 
| 118 134 | 
             
                def self_receiver_handling(node_context)
         | 
| 119 135 | 
             
                  nesting = node_context.nesting
         | 
| @@ -176,6 +192,12 @@ module RubyLsp | |
| 176 192 | 
             
                  def initialize(name)
         | 
| 177 193 | 
             
                    @name = name
         | 
| 178 194 | 
             
                  end
         | 
| 195 | 
            +
             | 
| 196 | 
            +
                  # Returns the attached version of this type by removing the `<Class:...>` part from its name
         | 
| 197 | 
            +
                  sig { returns(Type) }
         | 
| 198 | 
            +
                  def attached
         | 
| 199 | 
            +
                    Type.new(T.must(@name.split("::")[..-2]).join("::"))
         | 
| 200 | 
            +
                  end
         | 
| 179 201 | 
             
                end
         | 
| 180 202 |  | 
| 181 203 | 
             
                # A type that was guessed based on the receiver raw name
         |