ruby-lsp 0.27.0.beta1 → 0.27.0.beta3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/exe/ruby-lsp +0 -46
- data/exe/ruby-lsp-check +0 -15
- data/lib/ruby_lsp/addon.rb +19 -19
- data/lib/ruby_lsp/global_state.rb +1 -6
- data/lib/ruby_lsp/internal.rb +3 -2
- data/lib/ruby_lsp/listeners/code_lens.rb +1 -1
- data/lib/ruby_lsp/listeners/completion.rb +246 -382
- data/lib/ruby_lsp/listeners/definition.rb +7 -10
- data/lib/ruby_lsp/listeners/document_link.rb +4 -0
- data/lib/ruby_lsp/listeners/hover.rb +234 -82
- data/lib/ruby_lsp/listeners/signature_help.rb +11 -12
- data/lib/ruby_lsp/listeners/spec_style.rb +6 -1
- data/lib/ruby_lsp/listeners/test_discovery.rb +38 -15
- data/lib/ruby_lsp/listeners/test_style.rb +21 -9
- data/lib/ruby_lsp/node_context.rb +31 -8
- data/lib/ruby_lsp/requests/completion_resolve.rb +55 -39
- 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 +180 -66
- data/lib/ruby_lsp/requests/rename.rb +1 -1
- data/lib/ruby_lsp/requests/request.rb +3 -33
- data/lib/ruby_lsp/requests/support/common.rb +82 -68
- data/lib/ruby_lsp/requests/type_hierarchy_supertypes.rb +82 -46
- data/lib/ruby_lsp/ruby_document.rb +0 -73
- data/lib/ruby_lsp/rubydex/declaration.rb +174 -0
- data/lib/ruby_lsp/rubydex/definition.rb +73 -0
- data/lib/ruby_lsp/rubydex/reference.rb +6 -1
- data/lib/ruby_lsp/rubydex/signature.rb +107 -0
- data/lib/ruby_lsp/scripts/compose_bundle.rb +1 -1
- data/lib/ruby_lsp/server.rb +56 -171
- data/lib/ruby_lsp/test_helper.rb +0 -1
- data/lib/ruby_lsp/test_reporters/lsp_reporter.rb +1 -1
- data/lib/ruby_lsp/type_inferrer.rb +89 -11
- metadata +12 -18
- data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +0 -276
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +0 -1101
- data/lib/ruby_indexer/lib/ruby_indexer/enhancement.rb +0 -44
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +0 -605
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +0 -1077
- data/lib/ruby_indexer/lib/ruby_indexer/location.rb +0 -37
- data/lib/ruby_indexer/lib/ruby_indexer/prefix_tree.rb +0 -149
- data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +0 -294
- data/lib/ruby_indexer/lib/ruby_indexer/reference_finder.rb +0 -335
- data/lib/ruby_indexer/lib/ruby_indexer/visibility_scope.rb +0 -32
- data/lib/ruby_indexer/ruby_indexer.rb +0 -20
- data/lib/ruby_lsp/static_docs.rb +0 -20
- data/static_docs/break.md +0 -103
- data/static_docs/yield.md +0 -81
- /data/lib/{ruby_indexer/lib/ruby_indexer → ruby_lsp}/uri.rb +0 -0
|
@@ -174,9 +174,10 @@ module RubyLsp
|
|
|
174
174
|
#: (Prism::ClassNode node) -> void
|
|
175
175
|
def on_class_node_enter(node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
|
|
176
176
|
with_test_ancestor_tracking(node) do |name, ancestors|
|
|
177
|
-
|
|
177
|
+
is_test_unit = test_unit_group?(ancestors, name)
|
|
178
|
+
@framework = :test_unit if is_test_unit
|
|
178
179
|
|
|
179
|
-
if
|
|
180
|
+
if is_test_unit || non_declarative_minitest?(ancestors, name)
|
|
180
181
|
test_item = Requests::Support::TestItem.new(
|
|
181
182
|
name,
|
|
182
183
|
name,
|
|
@@ -219,7 +220,7 @@ module RubyLsp
|
|
|
219
220
|
name = node.name.to_s
|
|
220
221
|
return unless name.start_with?("test_")
|
|
221
222
|
|
|
222
|
-
current_group_name =
|
|
223
|
+
current_group_name = calc_fully_qualified_name(nil)
|
|
223
224
|
parent = @parent_stack.last
|
|
224
225
|
return unless parent.is_a?(Requests::Support::TestItem)
|
|
225
226
|
|
|
@@ -259,17 +260,28 @@ module RubyLsp
|
|
|
259
260
|
@parent_stack[index] #: as Requests::Support::TestItem | ResponseBuilders::TestCollection
|
|
260
261
|
end
|
|
261
262
|
|
|
262
|
-
#: (Array[String]
|
|
263
|
+
#: (Array[String], String) -> bool
|
|
264
|
+
def test_unit_group?(ancestors, fully_qualified_name)
|
|
265
|
+
fully_qualified_name != "Test::Unit::TestCase" && ancestors.include?("Test::Unit::TestCase")
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
#: (Array[String], String) -> bool
|
|
263
269
|
def non_declarative_minitest?(attached_ancestors, fully_qualified_name)
|
|
270
|
+
return false if ["Minitest::Spec", "Minitest::Test", "ActiveSupport::TestCase"].include?(fully_qualified_name)
|
|
264
271
|
return false unless attached_ancestors.include?("Minitest::Test")
|
|
265
272
|
|
|
266
273
|
# We only support regular Minitest tests. The declarative syntax provided by ActiveSupport is handled by the
|
|
267
274
|
# Rails add-on
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
true
|
|
275
|
+
|
|
276
|
+
declaration = @graph[fully_qualified_name]
|
|
277
|
+
# If we don't find the fully qualified name in the graph, it means there's a dynamic portion in the test class
|
|
278
|
+
# definition. In that case, if the ancestors did include `Minitest::Test`, we always assume it's a test
|
|
279
|
+
return true unless declaration.is_a?(Rubydex::Namespace)
|
|
280
|
+
|
|
281
|
+
singleton = declaration.singleton_class
|
|
282
|
+
return !singleton.ancestors.map(&:name).include?("ActiveSupport::Testing::Declarative") if singleton
|
|
283
|
+
|
|
284
|
+
!attached_ancestors.include?("ActiveSupport::TestCase")
|
|
273
285
|
end
|
|
274
286
|
end
|
|
275
287
|
end
|
|
@@ -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
|
|
|
@@ -16,6 +16,12 @@ module RubyLsp
|
|
|
16
16
|
class CompletionResolve < Request
|
|
17
17
|
include Requests::Support::Common
|
|
18
18
|
|
|
19
|
+
METHOD_KINDS = [
|
|
20
|
+
Constant::CompletionItemKind::METHOD,
|
|
21
|
+
Constant::CompletionItemKind::CONSTRUCTOR,
|
|
22
|
+
Constant::CompletionItemKind::FUNCTION,
|
|
23
|
+
].freeze #: Array[Integer]
|
|
24
|
+
|
|
19
25
|
# set a limit on the number of documentation entries returned, to avoid rendering performance issues
|
|
20
26
|
# https://github.com/Shopify/ruby-lsp/pull/1798
|
|
21
27
|
MAX_DOCUMENTATION_ENTRIES = 10
|
|
@@ -23,7 +29,7 @@ module RubyLsp
|
|
|
23
29
|
#: (GlobalState global_state, Hash[Symbol, untyped] item) -> void
|
|
24
30
|
def initialize(global_state, item)
|
|
25
31
|
super()
|
|
26
|
-
@
|
|
32
|
+
@graph = global_state.graph #: Rubydex::Graph
|
|
27
33
|
@item = item
|
|
28
34
|
end
|
|
29
35
|
|
|
@@ -31,6 +37,8 @@ module RubyLsp
|
|
|
31
37
|
#: -> Hash[Symbol, untyped]
|
|
32
38
|
def perform
|
|
33
39
|
return @item if @item.dig(:data, :skip_resolve)
|
|
40
|
+
return keyword_resolve if @item.dig(:data, :keyword)
|
|
41
|
+
return @item if @item[:kind] == Constant::CompletionItemKind::FILE
|
|
34
42
|
|
|
35
43
|
# Based on the spec https://microsoft.github.io/language-server-protocol/specification#textDocument_completion,
|
|
36
44
|
# a completion resolve request must always return the original completion item without modifying ANY fields
|
|
@@ -39,70 +47,78 @@ module RubyLsp
|
|
|
39
47
|
#
|
|
40
48
|
# For example, forgetting to return the `insertText` included in the original item will make the editor use the
|
|
41
49
|
# `label` for the text edit instead
|
|
42
|
-
|
|
43
|
-
return
|
|
44
|
-
|
|
45
|
-
entries = @index[label] || []
|
|
46
|
-
|
|
47
|
-
owner_name = @item.dig(:data, :owner_name)
|
|
48
|
-
|
|
49
|
-
if owner_name
|
|
50
|
-
entries = entries.select do |entry|
|
|
51
|
-
(entry.is_a?(RubyIndexer::Entry::Member) || entry.is_a?(RubyIndexer::Entry::InstanceVariable) ||
|
|
52
|
-
entry.is_a?(RubyIndexer::Entry::MethodAlias) || entry.is_a?(RubyIndexer::Entry::ClassVariable)) &&
|
|
53
|
-
entry.owner&.name == owner_name
|
|
54
|
-
end
|
|
55
|
-
end
|
|
50
|
+
declaration = resolve_declaration
|
|
51
|
+
return @item unless declaration
|
|
56
52
|
|
|
57
|
-
|
|
53
|
+
guessed_type = @item.dig(:data, :guessed_type)
|
|
54
|
+
title = @item[:label].dup
|
|
58
55
|
|
|
59
|
-
if
|
|
60
|
-
|
|
61
|
-
|
|
56
|
+
if declaration.is_a?(Rubydex::Method)
|
|
57
|
+
title << declaration.decorated_parameters
|
|
58
|
+
title << declaration.formatted_signatures
|
|
62
59
|
end
|
|
63
60
|
|
|
64
|
-
guessed_type = @item.dig(:data, :guessed_type)
|
|
65
|
-
|
|
66
61
|
extra_links = if guessed_type
|
|
67
|
-
|
|
62
|
+
title << "\n\nGuessed receiver: #{guessed_type}"
|
|
68
63
|
"[Learn more about guessed types](#{GUESSED_TYPES_URL})"
|
|
69
64
|
end
|
|
70
65
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
66
|
+
@item[:documentation] = Interface::MarkupContent.new(
|
|
67
|
+
kind: "markdown",
|
|
68
|
+
value: markdown_from_definitions(
|
|
69
|
+
title,
|
|
70
|
+
declaration.definitions,
|
|
71
|
+
MAX_DOCUMENTATION_ENTRIES,
|
|
72
|
+
extra_links: extra_links,
|
|
73
|
+
),
|
|
74
|
+
)
|
|
77
75
|
|
|
78
76
|
@item
|
|
79
77
|
end
|
|
80
78
|
|
|
81
79
|
private
|
|
82
80
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
81
|
+
# Find the Rubydex declaration that matches the completion item. Constants are looked up by their fully qualified
|
|
82
|
+
# name (set when the completion was produced); members (methods, instance/class variables) are resolved by walking
|
|
83
|
+
# the owner namespace and its ancestors so that inherited and aliased members are surfaced correctly.
|
|
84
|
+
#: -> Rubydex::Declaration?
|
|
85
|
+
def resolve_declaration
|
|
86
|
+
data = @item[:data] || {}
|
|
87
|
+
|
|
88
|
+
if (fully_qualified_name = data[:fully_qualified_name])
|
|
89
|
+
@graph[fully_qualified_name]
|
|
90
|
+
elsif (owner_name = data[:owner_name])
|
|
91
|
+
owner = @graph[owner_name]
|
|
92
|
+
return unless owner.is_a?(Rubydex::Namespace)
|
|
93
|
+
|
|
94
|
+
member_name = if METHOD_KINDS.include?(@item[:kind])
|
|
95
|
+
"#{@item[:label]}()"
|
|
96
|
+
else
|
|
97
|
+
@item[:label]
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
owner.find_member(member_name)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
87
103
|
|
|
88
|
-
|
|
89
|
-
|
|
104
|
+
#: -> Hash[Symbol, untyped]
|
|
105
|
+
def keyword_resolve
|
|
106
|
+
keyword = @graph.keyword(@item[:label])
|
|
90
107
|
|
|
108
|
+
if keyword
|
|
91
109
|
@item[:documentation] = Interface::MarkupContent.new(
|
|
92
110
|
kind: "markdown",
|
|
93
111
|
value: <<~MARKDOWN.chomp,
|
|
94
112
|
```ruby
|
|
95
|
-
#{keyword}
|
|
113
|
+
#{keyword.name}
|
|
96
114
|
```
|
|
97
115
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
#{content}
|
|
116
|
+
#{keyword.documentation}
|
|
101
117
|
MARKDOWN
|
|
102
118
|
)
|
|
103
119
|
end
|
|
104
120
|
|
|
105
|
-
item
|
|
121
|
+
@item
|
|
106
122
|
end
|
|
107
123
|
end
|
|
108
124
|
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
|