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.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/lib/ruby_indexer/ruby_indexer.rb +0 -1
- data/lib/ruby_lsp/addon.rb +19 -19
- data/lib/ruby_lsp/global_state.rb +1 -1
- data/lib/ruby_lsp/internal.rb +1 -1
- data/lib/ruby_lsp/listeners/definition.rb +1 -1
- data/lib/ruby_lsp/listeners/document_link.rb +4 -0
- data/lib/ruby_lsp/listeners/hover.rb +223 -73
- data/lib/ruby_lsp/listeners/spec_style.rb +6 -1
- data/lib/ruby_lsp/listeners/test_discovery.rb +21 -14
- data/lib/ruby_lsp/listeners/test_style.rb +20 -8
- data/lib/ruby_lsp/node_context.rb +31 -8
- data/lib/ruby_lsp/requests/completion_resolve.rb +9 -13
- data/lib/ruby_lsp/requests/discover_tests.rb +5 -41
- data/lib/ruby_lsp/requests/hover.rb +2 -5
- data/lib/ruby_lsp/requests/prepare_type_hierarchy.rb +66 -22
- data/lib/ruby_lsp/requests/references.rb +170 -70
- data/lib/ruby_lsp/requests/request.rb +3 -33
- data/lib/ruby_lsp/requests/support/common.rb +13 -0
- data/lib/ruby_lsp/requests/type_hierarchy_supertypes.rb +82 -46
- data/lib/ruby_lsp/rubydex/declaration.rb +48 -0
- data/lib/ruby_lsp/rubydex/definition.rb +57 -0
- data/lib/ruby_lsp/rubydex/reference.rb +6 -1
- data/lib/ruby_lsp/server.rb +49 -9
- data/lib/ruby_lsp/type_inferrer.rb +87 -9
- metadata +2 -5
- data/lib/ruby_indexer/lib/ruby_indexer/reference_finder.rb +0 -335
- data/lib/ruby_lsp/static_docs.rb +0 -20
- data/static_docs/break.md +0 -103
- data/static_docs/yield.md +0 -81
|
@@ -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
|
-
#:
|
|
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 #:
|
|
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],
|
|
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 #:
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
|
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
|
-
#: (
|
|
84
|
-
def keyword_resolve
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
31
|
-
|
|
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
|
-
(
|
|
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,
|
|
22
|
-
def initialize(document,
|
|
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
|
-
@
|
|
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
|
-
|
|
44
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
return unless entries
|
|
47
|
+
declaration = @graph.resolve_constant(pair.first, pair.last)
|
|
48
|
+
return unless declaration.is_a?(Rubydex::Namespace)
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
117
|
+
@locations
|
|
118
|
+
end
|
|
59
119
|
|
|
60
|
-
|
|
61
|
-
return @locations unless reference_target
|
|
120
|
+
private
|
|
62
121
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
76
|
-
|
|
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
|
-
|
|
148
|
+
collect_references(method_references_for(message), declarations, include_declarations)
|
|
80
149
|
end
|
|
81
150
|
|
|
82
|
-
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|