ruby-lsp 0.27.0.beta1 → 0.27.0.beta2

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.
@@ -5,6 +5,21 @@ module RubyLsp
5
5
  # This class allows listeners to access contextual information about a node in the AST, such as its parent,
6
6
  # its namespace nesting, and the surrounding CallNode (e.g. a method call).
7
7
  class NodeContext
8
+ # Represents the surrounding method definition context, tracking both the method name and its receiver
9
+ class MethodDef
10
+ #: String
11
+ attr_reader :name
12
+
13
+ #: String?
14
+ attr_reader :receiver
15
+
16
+ #: (String name, String? receiver) -> void
17
+ def initialize(name, receiver)
18
+ @name = name
19
+ @receiver = receiver
20
+ end
21
+ end
22
+
8
23
  #: Prism::Node?
9
24
  attr_reader :node, :parent
10
25
 
@@ -14,7 +29,7 @@ module RubyLsp
14
29
  #: Prism::CallNode?
15
30
  attr_reader :call_node
16
31
 
17
- #: String?
32
+ #: MethodDef?
18
33
  attr_reader :surrounding_method
19
34
 
20
35
  #: (Prism::Node? node, Prism::Node? parent, Array[(Prism::ClassNode | Prism::ModuleNode | Prism::SingletonClassNode | Prism::DefNode | Prism::BlockNode | Prism::LambdaNode | Prism::ProgramNode)] nesting_nodes, Prism::CallNode? call_node) -> void
@@ -26,7 +41,7 @@ module RubyLsp
26
41
 
27
42
  nesting, surrounding_method = handle_nesting_nodes(nesting_nodes)
28
43
  @nesting = nesting #: Array[String]
29
- @surrounding_method = surrounding_method #: String?
44
+ @surrounding_method = surrounding_method #: MethodDef?
30
45
  end
31
46
 
32
47
  #: -> String
@@ -52,10 +67,10 @@ module RubyLsp
52
67
 
53
68
  private
54
69
 
55
- #: (Array[(Prism::ClassNode | Prism::ModuleNode | Prism::SingletonClassNode | Prism::DefNode | Prism::BlockNode | Prism::LambdaNode | Prism::ProgramNode)] nodes) -> [Array[String], String?]
70
+ #: (Array[(Prism::ClassNode | Prism::ModuleNode | Prism::SingletonClassNode | Prism::DefNode | Prism::BlockNode | Prism::LambdaNode | Prism::ProgramNode)] nodes) -> [Array[String], MethodDef?]
56
71
  def handle_nesting_nodes(nodes)
57
72
  nesting = []
58
- surrounding_method = nil #: String?
73
+ surrounding_method = nil #: MethodDef?
59
74
 
60
75
  @nesting_nodes.each do |node|
61
76
  case node
@@ -64,10 +79,18 @@ module RubyLsp
64
79
  when Prism::SingletonClassNode
65
80
  nesting << "<#{nesting.flat_map { |n| n.split("::") }.last}>"
66
81
  when Prism::DefNode
67
- surrounding_method = node.name.to_s
68
- next unless node.receiver.is_a?(Prism::SelfNode)
69
-
70
- nesting << "<#{nesting.flat_map { |n| n.split("::") }.last}>"
82
+ receiver = node.receiver
83
+
84
+ surrounding_method = case receiver
85
+ when nil
86
+ MethodDef.new(node.name.to_s, "none")
87
+ when Prism::SelfNode
88
+ MethodDef.new(node.name.to_s, "self")
89
+ when Prism::ConstantReadNode, Prism::ConstantPathNode
90
+ MethodDef.new(node.name.to_s, receiver.slice)
91
+ else
92
+ MethodDef.new(node.name.to_s, nil)
93
+ end
71
94
  end
72
95
  end
73
96
 
@@ -24,6 +24,7 @@ module RubyLsp
24
24
  def initialize(global_state, item)
25
25
  super()
26
26
  @index = global_state.index #: RubyIndexer::Index
27
+ @graph = global_state.graph #: Rubydex::Graph
27
28
  @item = item
28
29
  end
29
30
 
@@ -40,7 +41,7 @@ module RubyLsp
40
41
  # For example, forgetting to return the `insertText` included in the original item will make the editor use the
41
42
  # `label` for the text edit instead
42
43
  label = @item[:label].dup
43
- return keyword_resolve(@item) if @item.dig(:data, :keyword)
44
+ return keyword_resolve if @item.dig(:data, :keyword)
44
45
 
45
46
  entries = @index[label] || []
46
47
 
@@ -80,29 +81,24 @@ module RubyLsp
80
81
 
81
82
  private
82
83
 
83
- #: (Hash[Symbol, untyped] item) -> Hash[Symbol, untyped]
84
- def keyword_resolve(item)
85
- keyword = item[:label]
86
- content = KEYWORD_DOCS[keyword]
87
-
88
- if content
89
- doc_uri = URI::Generic.from_path(path: File.join(STATIC_DOCS_PATH, "#{keyword}.md"))
84
+ #: () -> Hash[Symbol, untyped]
85
+ def keyword_resolve
86
+ keyword = @graph.keyword(@item[:label])
90
87
 
88
+ if keyword
91
89
  @item[:documentation] = Interface::MarkupContent.new(
92
90
  kind: "markdown",
93
91
  value: <<~MARKDOWN.chomp,
94
92
  ```ruby
95
- #{keyword}
93
+ #{keyword.name}
96
94
  ```
97
95
 
98
- [Read more](#{doc_uri})
99
-
100
- #{content}
96
+ #{keyword.documentation}
101
97
  MARKDOWN
102
98
  )
103
99
  end
104
100
 
105
- item
101
+ @item
106
102
  end
107
103
  end
108
104
  end
@@ -19,55 +19,19 @@ module RubyLsp
19
19
  @document = document
20
20
  @dispatcher = dispatcher
21
21
  @response_builder = ResponseBuilders::TestCollection.new #: ResponseBuilders::TestCollection
22
- @index = global_state.index #: RubyIndexer::Index
23
22
  end
24
23
 
25
24
  # @override
26
25
  #: -> Array[Support::TestItem]
27
26
  def perform
28
- uri = @document.uri
27
+ Listeners::TestStyle.new(@response_builder, @global_state, @dispatcher, @document.uri)
28
+ Listeners::SpecStyle.new(@response_builder, @global_state, @dispatcher, @document.uri)
29
29
 
30
- # We normally only index test files once they are opened in the editor to save memory and avoid doing
31
- # unnecessary work. If the file is already opened and we already indexed it, then we can just discover the tests
32
- # straight away.
33
- #
34
- # However, if the user navigates to a specific test file from the explorer with nothing opened in the UI, then
35
- # we will not have indexed the test file yet and trying to linearize the ancestor of the class will fail. In
36
- # this case, we have to instantiate the indexer listener first, so that we insert classes, modules and methods
37
- # in the index first and then discover the tests, all in the same traversal.
38
- if @index.entries_for(uri.to_s)
39
- Listeners::TestStyle.new(@response_builder, @global_state, @dispatcher, @document.uri)
40
- Listeners::SpecStyle.new(@response_builder, @global_state, @dispatcher, @document.uri)
41
-
42
- Addon.addons.each do |addon|
43
- addon.create_discover_tests_listener(@response_builder, @dispatcher, @document.uri)
44
- end
45
-
46
- @dispatcher.visit(@document.ast)
47
- else
48
- @global_state.synchronize do
49
- RubyIndexer::DeclarationListener.new(
50
- @index,
51
- @dispatcher,
52
- @document.parse_result,
53
- uri,
54
- collect_comments: true,
55
- )
56
-
57
- Listeners::TestStyle.new(@response_builder, @global_state, @dispatcher, @document.uri)
58
- Listeners::SpecStyle.new(@response_builder, @global_state, @dispatcher, @document.uri)
59
-
60
- Addon.addons.each do |addon|
61
- addon.create_discover_tests_listener(@response_builder, @dispatcher, @document.uri)
62
- end
63
-
64
- # Dispatch the events both for indexing the test file and discovering the tests. The order here is
65
- # important because we need the index to be aware of the existing classes/modules/methods before the test
66
- # listeners can do their work
67
- @dispatcher.visit(@document.ast)
68
- end
30
+ Addon.addons.each do |addon|
31
+ addon.create_discover_tests_listener(@response_builder, @dispatcher, @document.uri)
69
32
  end
70
33
 
34
+ @dispatcher.visit(@document.ast)
71
35
  @response_builder.response
72
36
  end
73
37
  end
@@ -26,7 +26,6 @@ module RubyLsp
26
26
  node_context = RubyDocument.locate(
27
27
  document.ast,
28
28
  char_position,
29
- node_types: Listeners::Hover::ALLOWED_TARGETS,
30
29
  code_units_cache: document.code_units_cache,
31
30
  )
32
31
  target = node_context.node
@@ -48,7 +47,7 @@ module RubyLsp
48
47
  @target = target #: Prism::Node?
49
48
  uri = document.uri
50
49
  @response_builder = ResponseBuilders::Hover.new #: ResponseBuilders::Hover
51
- Listeners::Hover.new(@response_builder, global_state, uri, node_context, dispatcher, sorbet_level)
50
+ Listeners::Hover.new(@response_builder, global_state, uri, node_context, dispatcher, sorbet_level, position)
52
51
  Addon.addons.each do |addon|
53
52
  addon.create_hover_listener(@response_builder, node_context, dispatcher)
54
53
  end
@@ -77,9 +76,7 @@ module RubyLsp
77
76
 
78
77
  #: (Prism::Node? parent, Prism::Node? target) -> bool
79
78
  def should_refine_target?(parent, target)
80
- (Listeners::Hover::ALLOWED_TARGETS.include?(parent.class) &&
81
- !Listeners::Hover::ALLOWED_TARGETS.include?(target.class)) ||
82
- (parent.is_a?(Prism::ConstantPathNode) && target.is_a?(Prism::ConstantReadNode))
79
+ parent.is_a?(Prism::ConstantPathNode) && target.is_a?(Prism::ConstantReadNode)
83
80
  end
84
81
 
85
82
  #: (Hash[Symbol, untyped] position, Prism::Node? target) -> bool
@@ -5,9 +5,7 @@ module RubyLsp
5
5
  module Requests
6
6
  # The [prepare type hierarchy
7
7
  # request](https://microsoft.github.io/language-server-protocol/specification#textDocument_prepareTypeHierarchy)
8
- # displays the list of ancestors (supertypes) and descendants (subtypes) for the selected type.
9
- #
10
- # Currently only supports supertypes due to a limitation of the index.
8
+ # displays the list of direct ancestors (supertypes) and descendants (subtypes) for the selected type.
11
9
  class PrepareTypeHierarchy < Request
12
10
  include Support::Common
13
11
 
@@ -18,12 +16,12 @@ module RubyLsp
18
16
  end
19
17
  end
20
18
 
21
- #: ((RubyDocument | ERBDocument) document, RubyIndexer::Index index, Hash[Symbol, untyped] position) -> void
22
- def initialize(document, index, position)
19
+ #: ((RubyDocument | ERBDocument) document, GlobalState global_state, Hash[Symbol, untyped] position) -> void
20
+ def initialize(document, global_state, position)
23
21
  super()
24
22
 
25
23
  @document = document
26
- @index = index
24
+ @graph = global_state.graph #: Rubydex::Graph
27
25
  @position = position
28
26
  end
29
27
 
@@ -36,32 +34,78 @@ module RubyLsp
36
34
  Prism::ConstantReadNode,
37
35
  Prism::ConstantWriteNode,
38
36
  Prism::ConstantPathNode,
37
+ Prism::SingletonClassNode,
39
38
  ],
40
39
  )
41
40
 
42
- node = context.node
43
- parent = context.parent
44
- return unless node && parent
41
+ node = context.node #: as (Prism::ConstantReadNode | Prism::ConstantPathNode | Prism::ConstantWriteNode | Prism::SingletonClassNode)?
42
+ return unless node
43
+
44
+ pair = name_and_nesting(node, context)
45
+ return unless pair
45
46
 
46
- target = determine_target(node, parent, @position)
47
- entries = @index.resolve(target.slice, context.nesting)
48
- return unless entries
47
+ declaration = @graph.resolve_constant(pair.first, pair.last)
48
+ return unless declaration.is_a?(Rubydex::Namespace)
49
49
 
50
- # While the spec allows for multiple entries, VSCode seems to only support one
51
- # We'll just return the first one for now
52
- first_entry = entries.first #: as !nil
53
- range = range_from_location(first_entry.location)
50
+ primary = declaration.definitions.first
51
+ return unless primary
54
52
 
55
53
  [
56
- Interface::TypeHierarchyItem.new(
57
- name: first_entry.name,
58
- kind: kind_for_entry(first_entry),
59
- uri: first_entry.uri.to_s,
60
- range: range,
61
- selection_range: range,
54
+ primary.to_lsp_type_hierarchy_item(
55
+ declaration.name,
56
+ detail: declaration.lsp_type_hierarchy_detail,
62
57
  ),
63
58
  ]
64
59
  end
60
+
61
+ private
62
+
63
+ # Returns the `(name, nesting)` pair to pass to `Rubydex::Graph#resolve_constant`, covering three cases:
64
+ #
65
+ #: ((Prism::ConstantReadNode | Prism::ConstantPathNode | Prism::ConstantWriteNode | Prism::SingletonClassNode), NodeContext) -> [String, Array[String]]?
66
+ def name_and_nesting(node, context)
67
+ parent = context.parent
68
+ nesting = context.nesting
69
+
70
+ singleton_node = singleton_class_node_for(node, parent)
71
+ return singleton_lookup(singleton_node, nesting) if singleton_node
72
+
73
+ target = parent ? determine_target(node, parent, @position) : node
74
+ [target.slice, nesting]
75
+ end
76
+
77
+ # Ensures that we're returning the target of the singleton class block regardless of whether the cursor is on the
78
+ # `class` keyword or the constant reference for the target
79
+ #: ((Prism::ConstantReadNode | Prism::ConstantPathNode | Prism::ConstantWriteNode | Prism::SingletonClassNode), Prism::Node?) -> Prism::SingletonClassNode?
80
+ def singleton_class_node_for(node, parent)
81
+ return node if node.is_a?(Prism::SingletonClassNode)
82
+ return unless parent.is_a?(Prism::SingletonClassNode) && parent.expression == node
83
+
84
+ parent
85
+ end
86
+
87
+ # Builds the synthesized singleton class name (e.g. `Foo::<Foo>`) for a `class << X` block, together with the
88
+ # outer lexical nesting. `NodeContext` already appends a `<ClassName>` marker as the last element of the nesting
89
+ # whenever the cursor sits inside (or on) a `SingletonClassNode`, so we drop that marker to obtain the scope in
90
+ # which the singleton should be resolved.
91
+ #: (Prism::SingletonClassNode, Array[String]) -> [String, Array[String]]?
92
+ def singleton_lookup(singleton_node, nesting)
93
+ outer = nesting[0...-1] || []
94
+
95
+ case expression = singleton_node.expression
96
+ when Prism::SelfNode
97
+ name = nesting.last
98
+ return unless name
99
+
100
+ [name, outer]
101
+ when Prism::ConstantReadNode, Prism::ConstantPathNode
102
+ name = constant_name(expression)
103
+ return unless name
104
+
105
+ unqualified = name.split("::").last #: as !nil
106
+ ["#{name}::<#{unqualified}>", outer]
107
+ end
108
+ end
65
109
  end
66
110
  end
67
111
  end
@@ -9,126 +9,226 @@ module RubyLsp
9
9
  class References < Request
10
10
  include Support::Common
11
11
 
12
+ MAX_NUMBER_OF_METHOD_CANDIDATES_WITHOUT_RECEIVER = 30
13
+
12
14
  #: (GlobalState global_state, Store store, (RubyDocument | ERBDocument) document, Hash[Symbol, untyped] params) -> void
13
15
  def initialize(global_state, store, document, params)
14
16
  super()
15
17
  @global_state = global_state
18
+ @type_inferrer = global_state.type_inferrer #: TypeInferrer
19
+ @graph = global_state.graph #: Rubydex::Graph
16
20
  @store = store
17
21
  @document = document
18
22
  @params = params
19
23
  @locations = [] #: Array[Interface::Location]
24
+ @char_position = 0 #: Integer
20
25
  end
21
26
 
22
27
  # @override
23
28
  #: -> Array[Interface::Location]
24
29
  def perform
25
- position = @params[:position]
26
- char_position, _ = @document.find_index_by_position(position)
30
+ include_declarations = @params.dig(:context, :includeDeclaration) || false
31
+ @char_position, _ = @document.find_index_by_position(@params[:position])
27
32
 
28
33
  node_context = RubyDocument.locate(
29
34
  @document.ast,
30
- char_position,
35
+ @char_position,
31
36
  node_types: [
32
37
  Prism::ConstantReadNode,
33
38
  Prism::ConstantPathNode,
34
39
  Prism::ConstantPathTargetNode,
40
+ Prism::ConstantAndWriteNode,
41
+ Prism::ConstantOperatorWriteNode,
42
+ Prism::ConstantOrWriteNode,
43
+ Prism::ConstantTargetNode,
44
+ Prism::ConstantWriteNode,
35
45
  Prism::InstanceVariableAndWriteNode,
36
46
  Prism::InstanceVariableOperatorWriteNode,
37
47
  Prism::InstanceVariableOrWriteNode,
38
48
  Prism::InstanceVariableReadNode,
39
49
  Prism::InstanceVariableTargetNode,
40
50
  Prism::InstanceVariableWriteNode,
51
+ Prism::ClassVariableAndWriteNode,
52
+ Prism::ClassVariableOperatorWriteNode,
53
+ Prism::ClassVariableOrWriteNode,
54
+ Prism::ClassVariableReadNode,
55
+ Prism::ClassVariableTargetNode,
56
+ Prism::ClassVariableWriteNode,
57
+ Prism::GlobalVariableAndWriteNode,
58
+ Prism::GlobalVariableOperatorWriteNode,
59
+ Prism::GlobalVariableOrWriteNode,
60
+ Prism::GlobalVariableReadNode,
61
+ Prism::GlobalVariableTargetNode,
62
+ Prism::GlobalVariableWriteNode,
41
63
  Prism::CallNode,
64
+ Prism::CallAndWriteNode,
65
+ Prism::CallOperatorWriteNode,
66
+ Prism::CallOrWriteNode,
42
67
  Prism::DefNode,
43
68
  ],
44
69
  code_units_cache: @document.code_units_cache,
45
70
  )
46
71
  target = node_context.node
47
- parent = node_context.parent
48
72
  return @locations if !target || target.is_a?(Prism::ProgramNode)
49
73
 
50
- if target.is_a?(Prism::ConstantReadNode) && parent.is_a?(Prism::ConstantPathNode)
51
- target = determine_target(
52
- target,
53
- parent,
54
- position,
55
- )
74
+ case target
75
+ when Prism::ConstantReadNode, Prism::ConstantPathNode, Prism::ConstantPathTargetNode
76
+ name = constant_name(target)
77
+ handle_constant_references(name, node_context, include_declarations) if name
78
+ when Prism::ConstantTargetNode
79
+ handle_constant_references(target.name.to_s, node_context, include_declarations)
80
+ when Prism::ConstantAndWriteNode, Prism::ConstantOperatorWriteNode, Prism::ConstantOrWriteNode,
81
+ Prism::ConstantWriteNode
82
+ if cursor_on_name?(target.name_loc)
83
+ handle_constant_references(target.name.to_s, node_context, include_declarations)
84
+ end
85
+ when Prism::InstanceVariableReadNode, Prism::InstanceVariableTargetNode,
86
+ Prism::ClassVariableReadNode, Prism::ClassVariableTargetNode
87
+ handle_variable_references(target.name.to_s, node_context, include_declarations)
88
+ when Prism::InstanceVariableAndWriteNode, Prism::InstanceVariableOperatorWriteNode,
89
+ Prism::InstanceVariableOrWriteNode, Prism::InstanceVariableWriteNode,
90
+ Prism::ClassVariableAndWriteNode, Prism::ClassVariableOperatorWriteNode,
91
+ Prism::ClassVariableOrWriteNode, Prism::ClassVariableWriteNode
92
+ if cursor_on_name?(target.name_loc)
93
+ handle_variable_references(target.name.to_s, node_context, include_declarations)
94
+ end
95
+ when Prism::GlobalVariableReadNode, Prism::GlobalVariableTargetNode
96
+ handle_global_variable_references(target.name.to_s, include_declarations)
97
+ when Prism::GlobalVariableAndWriteNode, Prism::GlobalVariableOperatorWriteNode,
98
+ Prism::GlobalVariableOrWriteNode, Prism::GlobalVariableWriteNode
99
+ if cursor_on_name?(target.name_loc)
100
+ handle_global_variable_references(target.name.to_s, include_declarations)
101
+ end
102
+ when Prism::CallNode
103
+ message_loc = target.message_loc
104
+ message = target.message
105
+ if message && message_loc && cursor_on_name?(message_loc)
106
+ resolve_method_references(message, node_context, include_declarations)
107
+ end
108
+ when Prism::CallAndWriteNode, Prism::CallOperatorWriteNode, Prism::CallOrWriteNode
109
+ message_loc = target.message_loc
110
+ if message_loc && cursor_on_name?(message_loc)
111
+ resolve_method_references(target.read_name.to_s, node_context, include_declarations)
112
+ end
113
+ when Prism::DefNode
114
+ handle_def_node_references(target, node_context, include_declarations) if cursor_on_name?(target.name_loc)
56
115
  end
57
116
 
58
- target = target #: as Prism::ConstantReadNode | Prism::ConstantPathNode | Prism::ConstantPathTargetNode | Prism::InstanceVariableAndWriteNode | Prism::InstanceVariableOperatorWriteNode | Prism::InstanceVariableOrWriteNode | Prism::InstanceVariableReadNode | Prism::InstanceVariableTargetNode | Prism::InstanceVariableWriteNode | Prism::CallNode | Prism::DefNode,
117
+ @locations
118
+ end
59
119
 
60
- reference_target = create_reference_target(target, node_context)
61
- return @locations unless reference_target
120
+ private
62
121
 
63
- Dir.glob(File.join(@global_state.workspace_path, "**/*.rb")).each do |path|
64
- uri = URI::Generic.from_path(path: path)
65
- # If the document is being managed by the client, then we should use whatever is present in the store instead
66
- # of reading from disk
67
- next if @store.key?(uri)
122
+ #: (String name, NodeContext node_context, bool include_declarations) -> void
123
+ def handle_constant_references(name, node_context, include_declarations)
124
+ declaration = @graph.resolve_constant(name, node_context.nesting)
125
+ return unless declaration
68
126
 
69
- parse_result = Prism.parse_lex_file(path)
70
- collect_references(reference_target, parse_result, uri)
71
- rescue Errno::EISDIR, Errno::ENOENT
72
- # If `path` is a directory, just ignore it and continue. If the file doesn't exist, then we also ignore it.
127
+ collect_references(declaration.references, [declaration], include_declarations)
128
+ end
129
+
130
+ #: (String message, NodeContext node_context, bool include_declarations) -> void
131
+ def resolve_method_references(message, node_context, include_declarations)
132
+ receiver_type = @type_inferrer.infer_receiver_type(node_context)
133
+
134
+ declaration = if receiver_type
135
+ owner = @graph[receiver_type.name]
136
+ owner.find_member("#{message}()") if owner.is_a?(Rubydex::Namespace)
73
137
  end
74
138
 
75
- @store.each do |_uri, document|
76
- collect_references(reference_target, document.parse_result, document.uri)
139
+ declarations = if receiver_type.nil? || (receiver_type.is_a?(TypeInferrer::GuessedType) && declaration.nil?)
140
+ @graph.search("##{message}()").take(MAX_NUMBER_OF_METHOD_CANDIDATES_WITHOUT_RECEIVER)
141
+ elsif declaration
142
+ [declaration]
143
+ else
144
+ []
77
145
  end
146
+ return if declarations.empty?
78
147
 
79
- @locations
148
+ collect_references(method_references_for(message), declarations, include_declarations)
80
149
  end
81
150
 
82
- private
151
+ # Handles instance and class variable references. Resolves the receiver type from the node context to locate
152
+ # the owning namespace, then looks up the member through the ancestor chain via `find_member`.
153
+ #: (String name, NodeContext node_context, bool include_declarations) -> void
154
+ def handle_variable_references(name, node_context, include_declarations)
155
+ type = @type_inferrer.infer_receiver_type(node_context)
156
+ return unless type
83
157
 
84
- #: ((Prism::ConstantReadNode | Prism::ConstantPathNode | Prism::ConstantPathTargetNode | Prism::InstanceVariableAndWriteNode | Prism::InstanceVariableOperatorWriteNode | Prism::InstanceVariableOrWriteNode | Prism::InstanceVariableReadNode | Prism::InstanceVariableTargetNode | Prism::InstanceVariableWriteNode | Prism::CallNode | Prism::DefNode) target_node, NodeContext node_context) -> RubyIndexer::ReferenceFinder::Target?
85
- def create_reference_target(target_node, node_context)
86
- case target_node
87
- when Prism::ConstantReadNode, Prism::ConstantPathNode, Prism::ConstantPathTargetNode
88
- name = RubyIndexer::Index.constant_name(target_node)
89
- return unless name
90
-
91
- entries = @global_state.index.resolve(name, node_context.nesting)
92
- return unless entries
93
-
94
- fully_qualified_name = entries.first #: as !nil
95
- .name
96
- RubyIndexer::ReferenceFinder::ConstTarget.new(fully_qualified_name)
97
- when
98
- Prism::InstanceVariableAndWriteNode,
99
- Prism::InstanceVariableOperatorWriteNode,
100
- Prism::InstanceVariableOrWriteNode,
101
- Prism::InstanceVariableReadNode,
102
- Prism::InstanceVariableTargetNode,
103
- Prism::InstanceVariableWriteNode
104
- receiver_type = @global_state.type_inferrer.infer_receiver_type(node_context)
105
- return unless receiver_type
106
-
107
- ancestors = @global_state.index.linearized_ancestors_of(receiver_type.name)
108
- RubyIndexer::ReferenceFinder::InstanceVariableTarget.new(target_node.name.to_s, ancestors)
109
- when Prism::CallNode, Prism::DefNode
110
- RubyIndexer::ReferenceFinder::MethodTarget.new(target_node.name.to_s)
111
- end
158
+ owner = @graph[type.name]
159
+ return unless owner.is_a?(Rubydex::Namespace)
160
+
161
+ declaration = owner.find_member(name)
162
+ return unless declaration
163
+
164
+ collect_references(declaration.references, [declaration], include_declarations)
112
165
  end
113
166
 
114
- #: (RubyIndexer::ReferenceFinder::Target target, Prism::LexResult parse_result, URI::Generic uri) -> void
115
- def collect_references(target, parse_result, uri)
116
- dispatcher = Prism::Dispatcher.new
117
- finder = RubyIndexer::ReferenceFinder.new(
118
- target,
119
- @global_state.index,
120
- dispatcher,
121
- uri,
122
- include_declarations: @params.dig(:context, :includeDeclaration) || true,
123
- )
124
- dispatcher.visit(parse_result.value.first)
167
+ # Handles global variable references. Globals are keyed by their full name (including `$`) in the graph, so we
168
+ # can look them up directly without needing to resolve a receiver type.
169
+ #: (String name, bool include_declarations) -> void
170
+ def handle_global_variable_references(name, include_declarations)
171
+ declaration = @graph[name]
172
+ return unless declaration
173
+
174
+ collect_references(declaration.references, [declaration], include_declarations)
175
+ end
125
176
 
126
- finder.references.each do |reference|
127
- @locations << Interface::Location.new(
128
- uri: uri.to_s,
129
- range: range_from_location(reference.location),
130
- )
177
+ #: (Prism::DefNode target, NodeContext node_context, bool include_declarations) -> void
178
+ def handle_def_node_references(target, node_context, include_declarations)
179
+ method_name = target.name.to_s
180
+
181
+ owner_type = @type_inferrer.infer_receiver_type(node_context)
182
+ return unless owner_type
183
+
184
+ owner = @graph[owner_type.name]
185
+ return unless owner.is_a?(Rubydex::Namespace)
186
+
187
+ declaration = owner.find_member("#{method_name}()")
188
+ return unless declaration
189
+
190
+ collect_references(method_references_for(method_name), [declaration], include_declarations)
191
+ end
192
+
193
+ # Method references in Rubydex are not yet resolved to specific declarations, so we filter from the global
194
+ # method references by name
195
+ #: (String) -> Array[Rubydex::MethodReference]
196
+ def method_references_for(method_name)
197
+ @graph.method_references.select { |reference| reference.name == method_name }
198
+ end
199
+
200
+ #: (Enumerable[Rubydex::Reference] references, Array[Rubydex::Declaration] declarations, bool include_declarations) -> void
201
+ def collect_references(references, declarations, include_declarations)
202
+ references.each do |reference|
203
+ next if rubydex_internal_uri?(reference.location.uri)
204
+
205
+ @locations << reference.to_lsp_location
131
206
  end
207
+
208
+ return unless include_declarations
209
+
210
+ declarations.each do |declaration|
211
+ declaration.definitions.each do |definition|
212
+ next if rubydex_internal_uri?(definition.location.uri)
213
+
214
+ @locations << definition.to_lsp_selection_location
215
+ end
216
+ end
217
+ end
218
+
219
+ #: (String uri) -> bool
220
+ def rubydex_internal_uri?(uri)
221
+ URI(uri).scheme == "rubydex"
222
+ end
223
+
224
+ # Write, operator-write, and call-with-message nodes cover more than just the identifier —
225
+ # they span the whole assignment or call expression. We only resolve references when the
226
+ # cursor is positioned directly on the name itself, not on operators, values, or arguments.
227
+ #: (Prism::Location name_loc) -> bool
228
+ def cursor_on_name?(name_loc)
229
+ start = name_loc.cached_start_code_units_offset(@document.code_units_cache)
230
+ finish = name_loc.cached_end_code_units_offset(@document.code_units_cache)
231
+ (start...finish).cover?(@char_position)
132
232
  end
133
233
  end
134
234
  end