ruby-lsp 0.26.9 → 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/lib/ruby_indexer/declaration_listener.rb +2 -2
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +8 -8
- data/lib/ruby_indexer/ruby_indexer.rb +0 -1
- data/lib/ruby_lsp/addon.rb +19 -19
- data/lib/ruby_lsp/global_state.rb +11 -2
- data/lib/ruby_lsp/internal.rb +6 -1
- data/lib/ruby_lsp/listeners/definition.rb +65 -99
- data/lib/ruby_lsp/listeners/document_link.rb +4 -0
- data/lib/ruby_lsp/listeners/hover.rb +258 -123
- 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 +32 -9
- 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/on_type_formatting.rb +4 -0
- 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/rename.rb +64 -72
- data/lib/ruby_lsp/requests/request.rb +3 -33
- data/lib/ruby_lsp/requests/support/common.rb +53 -0
- data/lib/ruby_lsp/requests/type_hierarchy_supertypes.rb +82 -46
- data/lib/ruby_lsp/requests/workspace_symbol.rb +15 -37
- data/lib/ruby_lsp/rubydex/declaration.rb +48 -0
- data/lib/ruby_lsp/rubydex/definition.rb +257 -0
- data/lib/ruby_lsp/rubydex/reference.rb +21 -0
- data/lib/ruby_lsp/server.rb +82 -8
- data/lib/ruby_lsp/store.rb +0 -6
- data/lib/ruby_lsp/test_helper.rb +3 -0
- data/lib/ruby_lsp/type_inferrer.rb +111 -31
- metadata +18 -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
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Rubydex
|
|
5
|
+
# @abstract
|
|
6
|
+
class Definition
|
|
7
|
+
#: () -> RubyLsp::Interface::LocationLink
|
|
8
|
+
def to_lsp_location_link
|
|
9
|
+
selection_range = to_lsp_selection_range
|
|
10
|
+
|
|
11
|
+
RubyLsp::Interface::LocationLink.new(
|
|
12
|
+
target_uri: location.uri,
|
|
13
|
+
target_range: selection_range,
|
|
14
|
+
target_selection_range: to_lsp_name_range || selection_range,
|
|
15
|
+
)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# @abstract
|
|
19
|
+
#: () -> Integer
|
|
20
|
+
def to_lsp_kind
|
|
21
|
+
raise RubyLsp::AbstractMethodInvokedError
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Direct ancestor references contributed by this definition (superclass, includes, prepends).
|
|
25
|
+
# Extends are intentionally excluded here because they extend the singleton class, not the
|
|
26
|
+
# instance-side ancestor chain. Definition subclasses that can't contribute ancestors return [].
|
|
27
|
+
#: () -> Array[Rubydex::ConstantReference]
|
|
28
|
+
def direct_supertype_references
|
|
29
|
+
[]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
#: (String name, ?detail: String?) -> RubyLsp::Interface::TypeHierarchyItem
|
|
33
|
+
def to_lsp_type_hierarchy_item(name, detail: nil)
|
|
34
|
+
range = to_lsp_selection_range
|
|
35
|
+
|
|
36
|
+
RubyLsp::Interface::TypeHierarchyItem.new(
|
|
37
|
+
name: name,
|
|
38
|
+
kind: to_lsp_kind,
|
|
39
|
+
uri: location.uri,
|
|
40
|
+
range: range,
|
|
41
|
+
selection_range: to_lsp_name_range || range,
|
|
42
|
+
detail: detail,
|
|
43
|
+
data: { fully_qualified_name: name },
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
#: (String name) -> RubyLsp::Interface::WorkspaceSymbol
|
|
48
|
+
def to_lsp_workspace_symbol(name)
|
|
49
|
+
# We use the namespace as the container name, but we also use the full name as the regular name. The reason we do
|
|
50
|
+
# this is to allow people to search for fully qualified names (e.g.: `Foo::Bar`). If we only included the short
|
|
51
|
+
# name `Bar`, then searching for `Foo::Bar` would not return any results
|
|
52
|
+
*container, _short_name = name.split("::")
|
|
53
|
+
container_name = container.join("::")
|
|
54
|
+
|
|
55
|
+
RubyLsp::Interface::WorkspaceSymbol.new(
|
|
56
|
+
name: name,
|
|
57
|
+
container_name: container_name,
|
|
58
|
+
kind: to_lsp_kind,
|
|
59
|
+
location: to_lsp_selection_location,
|
|
60
|
+
)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
#: () -> RubyLsp::Interface::Range
|
|
64
|
+
def to_lsp_selection_range
|
|
65
|
+
loc = location
|
|
66
|
+
|
|
67
|
+
RubyLsp::Interface::Range.new(
|
|
68
|
+
start: RubyLsp::Interface::Position.new(line: loc.start_line, character: loc.start_column),
|
|
69
|
+
end: RubyLsp::Interface::Position.new(line: loc.end_line, character: loc.end_column),
|
|
70
|
+
)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
#: () -> RubyLsp::Interface::Location
|
|
74
|
+
def to_lsp_selection_location
|
|
75
|
+
location = self.location
|
|
76
|
+
|
|
77
|
+
RubyLsp::Interface::Location.new(
|
|
78
|
+
uri: location.uri,
|
|
79
|
+
range: RubyLsp::Interface::Range.new(
|
|
80
|
+
start: RubyLsp::Interface::Position.new(line: location.start_line, character: location.start_column),
|
|
81
|
+
end: RubyLsp::Interface::Position.new(line: location.end_line, character: location.end_column),
|
|
82
|
+
),
|
|
83
|
+
)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
#: () -> RubyLsp::Interface::Range?
|
|
87
|
+
def to_lsp_name_range
|
|
88
|
+
loc = name_location
|
|
89
|
+
return unless loc
|
|
90
|
+
|
|
91
|
+
RubyLsp::Interface::Range.new(
|
|
92
|
+
start: RubyLsp::Interface::Position.new(line: loc.start_line, character: loc.start_column),
|
|
93
|
+
end: RubyLsp::Interface::Position.new(line: loc.end_line, character: loc.end_column),
|
|
94
|
+
)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
#: () -> RubyLsp::Interface::Location?
|
|
98
|
+
def to_lsp_name_location
|
|
99
|
+
location = name_location
|
|
100
|
+
return unless location
|
|
101
|
+
|
|
102
|
+
RubyLsp::Interface::Location.new(
|
|
103
|
+
uri: location.uri,
|
|
104
|
+
range: RubyLsp::Interface::Range.new(
|
|
105
|
+
start: RubyLsp::Interface::Position.new(line: location.start_line, character: location.start_column),
|
|
106
|
+
end: RubyLsp::Interface::Position.new(line: location.end_line, character: location.end_column),
|
|
107
|
+
),
|
|
108
|
+
)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Shared supertype aggregation for Rubydex definition types that carry namespace mixins
|
|
113
|
+
# (`ClassDefinition`, `ModuleDefinition`, `SingletonClassDefinition`). The including class is
|
|
114
|
+
# expected to provide `#mixins`, which every Rubydex namespace definition already does.
|
|
115
|
+
# @abstract
|
|
116
|
+
module NamespaceDefinition
|
|
117
|
+
# @abstract
|
|
118
|
+
#: () -> Array[Rubydex::Mixin]
|
|
119
|
+
def mixins
|
|
120
|
+
raise RubyLsp::AbstractMethodInvokedError
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
#: () -> Array[Rubydex::ConstantReference]
|
|
124
|
+
def direct_supertype_references
|
|
125
|
+
mixins.filter_map do |mixin|
|
|
126
|
+
mixin.constant_reference if mixin.is_a?(Include) || mixin.is_a?(Prepend)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
class ClassDefinition
|
|
132
|
+
include NamespaceDefinition
|
|
133
|
+
|
|
134
|
+
# @override
|
|
135
|
+
#: () -> Integer
|
|
136
|
+
def to_lsp_kind
|
|
137
|
+
RubyLsp::Constant::SymbolKind::CLASS
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# @override
|
|
141
|
+
#: () -> Array[Rubydex::ConstantReference]
|
|
142
|
+
def direct_supertype_references
|
|
143
|
+
refs = super
|
|
144
|
+
superclass_ref = superclass
|
|
145
|
+
refs << superclass_ref if superclass_ref
|
|
146
|
+
refs
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
class ModuleDefinition
|
|
151
|
+
include NamespaceDefinition
|
|
152
|
+
|
|
153
|
+
# @override
|
|
154
|
+
#: () -> Integer
|
|
155
|
+
def to_lsp_kind
|
|
156
|
+
RubyLsp::Constant::SymbolKind::NAMESPACE
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
class SingletonClassDefinition
|
|
161
|
+
include NamespaceDefinition
|
|
162
|
+
|
|
163
|
+
# @override
|
|
164
|
+
#: () -> Integer
|
|
165
|
+
def to_lsp_kind
|
|
166
|
+
RubyLsp::Constant::SymbolKind::CLASS
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
class ConstantDefinition
|
|
171
|
+
# @override
|
|
172
|
+
#: () -> Integer
|
|
173
|
+
def to_lsp_kind
|
|
174
|
+
RubyLsp::Constant::SymbolKind::CONSTANT
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
class ConstantAliasDefinition
|
|
179
|
+
# @override
|
|
180
|
+
#: () -> Integer
|
|
181
|
+
def to_lsp_kind
|
|
182
|
+
RubyLsp::Constant::SymbolKind::CONSTANT
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
class MethodDefinition
|
|
187
|
+
# @override
|
|
188
|
+
#: () -> Integer
|
|
189
|
+
def to_lsp_kind
|
|
190
|
+
name == "initialize()" ? RubyLsp::Constant::SymbolKind::CONSTRUCTOR : RubyLsp::Constant::SymbolKind::METHOD
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
class MethodAliasDefinition
|
|
195
|
+
# @override
|
|
196
|
+
#: () -> Integer
|
|
197
|
+
def to_lsp_kind
|
|
198
|
+
RubyLsp::Constant::SymbolKind::METHOD
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
class AttrReaderDefinition
|
|
203
|
+
# @override
|
|
204
|
+
#: () -> Integer
|
|
205
|
+
def to_lsp_kind
|
|
206
|
+
RubyLsp::Constant::SymbolKind::PROPERTY
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
class AttrWriterDefinition
|
|
211
|
+
# @override
|
|
212
|
+
#: () -> Integer
|
|
213
|
+
def to_lsp_kind
|
|
214
|
+
RubyLsp::Constant::SymbolKind::PROPERTY
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
class AttrAccessorDefinition
|
|
219
|
+
# @override
|
|
220
|
+
#: () -> Integer
|
|
221
|
+
def to_lsp_kind
|
|
222
|
+
RubyLsp::Constant::SymbolKind::PROPERTY
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
class InstanceVariableDefinition
|
|
227
|
+
# @override
|
|
228
|
+
#: () -> Integer
|
|
229
|
+
def to_lsp_kind
|
|
230
|
+
RubyLsp::Constant::SymbolKind::FIELD
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
class ClassVariableDefinition
|
|
235
|
+
# @override
|
|
236
|
+
#: () -> Integer
|
|
237
|
+
def to_lsp_kind
|
|
238
|
+
RubyLsp::Constant::SymbolKind::FIELD
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
class GlobalVariableDefinition
|
|
243
|
+
# @override
|
|
244
|
+
#: () -> Integer
|
|
245
|
+
def to_lsp_kind
|
|
246
|
+
RubyLsp::Constant::SymbolKind::VARIABLE
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
class GlobalVariableAliasDefinition
|
|
251
|
+
# @override
|
|
252
|
+
#: () -> Integer
|
|
253
|
+
def to_lsp_kind
|
|
254
|
+
RubyLsp::Constant::SymbolKind::VARIABLE
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Rubydex
|
|
5
|
+
class Reference
|
|
6
|
+
#: () -> RubyLsp::Interface::Range
|
|
7
|
+
def to_lsp_range
|
|
8
|
+
loc = location
|
|
9
|
+
|
|
10
|
+
RubyLsp::Interface::Range.new(
|
|
11
|
+
start: RubyLsp::Interface::Position.new(line: loc.start_line, character: loc.start_column),
|
|
12
|
+
end: RubyLsp::Interface::Position.new(line: loc.end_line, character: loc.end_column),
|
|
13
|
+
)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
#: () -> RubyLsp::Interface::Location
|
|
17
|
+
def to_lsp_location
|
|
18
|
+
RubyLsp::Interface::Location.new(uri: location.uri, range: to_lsp_range)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
data/lib/ruby_lsp/server.rb
CHANGED
|
@@ -3,7 +3,11 @@
|
|
|
3
3
|
|
|
4
4
|
module RubyLsp
|
|
5
5
|
class Server < BaseServer
|
|
6
|
-
NON_REPORTABLE_SETUP_ERRORS = [
|
|
6
|
+
NON_REPORTABLE_SETUP_ERRORS = [
|
|
7
|
+
Bundler::GemNotFound,
|
|
8
|
+
Bundler::GitError,
|
|
9
|
+
Bundler::Dsl::DSLError,
|
|
10
|
+
].freeze #: Array[singleton(StandardError)]
|
|
7
11
|
|
|
8
12
|
# Only for testing
|
|
9
13
|
#: GlobalState
|
|
@@ -120,6 +124,16 @@ module RubyLsp
|
|
|
120
124
|
@global_state.synchronize { @cancelled_requests << message[:params][:id] }
|
|
121
125
|
when nil
|
|
122
126
|
process_response(message) if message[:result]
|
|
127
|
+
else
|
|
128
|
+
id = message[:id]
|
|
129
|
+
|
|
130
|
+
if id
|
|
131
|
+
send_message(Error.new(
|
|
132
|
+
id: id,
|
|
133
|
+
code: Constant::ErrorCodes::METHOD_NOT_FOUND,
|
|
134
|
+
message: "Method not found: #{message[:method]}",
|
|
135
|
+
))
|
|
136
|
+
end
|
|
123
137
|
end
|
|
124
138
|
rescue DelegateRequestError
|
|
125
139
|
send_message(Error.new(id: message[:id], code: DelegateRequestError::CODE, message: "DELEGATE_REQUEST"))
|
|
@@ -415,7 +429,19 @@ module RubyLsp
|
|
|
415
429
|
params = message[:params]
|
|
416
430
|
text_document = params[:textDocument]
|
|
417
431
|
|
|
418
|
-
@store.
|
|
432
|
+
document = @store.get(text_document[:uri])
|
|
433
|
+
document.push_edits(params[:contentChanges], version: text_document[:version])
|
|
434
|
+
|
|
435
|
+
language_id = document.language_id
|
|
436
|
+
|
|
437
|
+
if [:ruby, :rbs].include?(language_id)
|
|
438
|
+
graph = @global_state.graph
|
|
439
|
+
|
|
440
|
+
benchmark("index_source") do
|
|
441
|
+
graph.index_source(text_document[:uri].to_s, document.source, language_id.to_s)
|
|
442
|
+
end
|
|
443
|
+
benchmark("incremental_resolve") { graph.resolve }
|
|
444
|
+
end
|
|
419
445
|
end
|
|
420
446
|
|
|
421
447
|
#: (Hash[Symbol, untyped] message) -> void
|
|
@@ -1033,6 +1059,23 @@ module RubyLsp
|
|
|
1033
1059
|
# is fine, but we shouldn't process the same file changes more than once
|
|
1034
1060
|
changes.uniq!
|
|
1035
1061
|
|
|
1062
|
+
graph = @global_state.graph
|
|
1063
|
+
|
|
1064
|
+
# Handle deletions and accumulate additions and changes for indexing
|
|
1065
|
+
additions_and_changes = changes.each_with_object([]) do |change, acc|
|
|
1066
|
+
if change[:type] == Constant::FileChangeType::DELETED
|
|
1067
|
+
graph.delete_document(change[:uri])
|
|
1068
|
+
else
|
|
1069
|
+
path = URI(change[:uri]).to_standardized_path
|
|
1070
|
+
next if path.nil?
|
|
1071
|
+
next unless File.directory?(path) || [".rb", ".rbs"].include?(File.extname(path))
|
|
1072
|
+
|
|
1073
|
+
acc << path
|
|
1074
|
+
end
|
|
1075
|
+
end
|
|
1076
|
+
benchmark("index_all") { graph.index_all(additions_and_changes) }
|
|
1077
|
+
benchmark("incremental_resolve") { graph.resolve }
|
|
1078
|
+
|
|
1036
1079
|
index = @global_state.index
|
|
1037
1080
|
changes.each do |change|
|
|
1038
1081
|
# File change events include folders, but we're only interested in files
|
|
@@ -1169,7 +1212,7 @@ module RubyLsp
|
|
|
1169
1212
|
|
|
1170
1213
|
response = Requests::PrepareTypeHierarchy.new(
|
|
1171
1214
|
document,
|
|
1172
|
-
@global_state
|
|
1215
|
+
@global_state,
|
|
1173
1216
|
params[:position],
|
|
1174
1217
|
).perform
|
|
1175
1218
|
|
|
@@ -1179,7 +1222,7 @@ module RubyLsp
|
|
|
1179
1222
|
#: (Hash[Symbol, untyped] message) -> void
|
|
1180
1223
|
def type_hierarchy_supertypes(message)
|
|
1181
1224
|
response = Requests::TypeHierarchySupertypes.new(
|
|
1182
|
-
@global_state
|
|
1225
|
+
@global_state,
|
|
1183
1226
|
message.dig(:params, :item),
|
|
1184
1227
|
).perform
|
|
1185
1228
|
send_message(Result.new(id: message[:id], response: response))
|
|
@@ -1228,12 +1271,18 @@ module RubyLsp
|
|
|
1228
1271
|
|
|
1229
1272
|
#: -> void
|
|
1230
1273
|
def perform_initial_indexing
|
|
1274
|
+
progress("indexing-progress", message: "Indexing workspace...")
|
|
1275
|
+
benchmark("index_workspace") { @global_state.graph.index_workspace }
|
|
1276
|
+
|
|
1277
|
+
progress("indexing-progress", message: "Resolving graph...")
|
|
1278
|
+
benchmark("full_resolve") { @global_state.graph.resolve }
|
|
1279
|
+
|
|
1231
1280
|
# The begin progress invocation happens during `initialize`, so that the notification is sent before we are
|
|
1232
1281
|
# stuck indexing files
|
|
1233
1282
|
Thread.new do
|
|
1234
1283
|
begin
|
|
1235
1284
|
@global_state.index.index_all do |percentage|
|
|
1236
|
-
progress("indexing-progress", percentage)
|
|
1285
|
+
progress("indexing-progress", percentage: percentage)
|
|
1237
1286
|
true
|
|
1238
1287
|
rescue ClosedQueueError
|
|
1239
1288
|
# Since we run indexing on a separate thread, it's possible to kill the server before indexing is complete.
|
|
@@ -1287,11 +1336,13 @@ module RubyLsp
|
|
|
1287
1336
|
send_message(Notification.progress_begin(id, title, percentage: percentage, message: "#{percentage}% completed"))
|
|
1288
1337
|
end
|
|
1289
1338
|
|
|
1290
|
-
#: (String
|
|
1291
|
-
def progress(id, percentage)
|
|
1339
|
+
#: (String, ?message: String?, ?percentage: Integer?) -> void
|
|
1340
|
+
def progress(id, message: nil, percentage: nil)
|
|
1292
1341
|
return unless @global_state.client_capabilities.supports_progress
|
|
1293
1342
|
|
|
1294
|
-
|
|
1343
|
+
message ||= "#{percentage}% completed" if percentage
|
|
1344
|
+
|
|
1345
|
+
send_message(Notification.progress_report(id, percentage: percentage, message: message))
|
|
1295
1346
|
end
|
|
1296
1347
|
|
|
1297
1348
|
#: (String id) -> void
|
|
@@ -1524,5 +1575,28 @@ module RubyLsp
|
|
|
1524
1575
|
response: code_lens,
|
|
1525
1576
|
))
|
|
1526
1577
|
end
|
|
1578
|
+
|
|
1579
|
+
#: [T] (String) { () -> T } -> T
|
|
1580
|
+
def benchmark(label, &block)
|
|
1581
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
|
|
1582
|
+
result = block.call
|
|
1583
|
+
duration = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond) - start
|
|
1584
|
+
|
|
1585
|
+
send_message(Notification.telemetry({
|
|
1586
|
+
type: "data",
|
|
1587
|
+
eventName: "ruby_lsp.response_time",
|
|
1588
|
+
data: {
|
|
1589
|
+
type: "histogram",
|
|
1590
|
+
value: duration,
|
|
1591
|
+
attributes: {
|
|
1592
|
+
message: label,
|
|
1593
|
+
lspVersion: RubyLsp::VERSION,
|
|
1594
|
+
rubyVersion: RUBY_VERSION,
|
|
1595
|
+
},
|
|
1596
|
+
},
|
|
1597
|
+
}))
|
|
1598
|
+
|
|
1599
|
+
result
|
|
1600
|
+
end
|
|
1527
1601
|
end
|
|
1528
1602
|
end
|
data/lib/ruby_lsp/store.rb
CHANGED
|
@@ -53,12 +53,6 @@ module RubyLsp
|
|
|
53
53
|
end
|
|
54
54
|
end
|
|
55
55
|
|
|
56
|
-
#: (uri: URI::Generic, edits: Array[Hash[Symbol, untyped]], version: Integer) -> void
|
|
57
|
-
def push_edits(uri:, edits:, version:)
|
|
58
|
-
@state[uri.to_s] #: as !nil
|
|
59
|
-
.push_edits(edits, version: version)
|
|
60
|
-
end
|
|
61
|
-
|
|
62
56
|
#: -> void
|
|
63
57
|
def clear
|
|
64
58
|
@state.clear
|
data/lib/ruby_lsp/test_helper.rb
CHANGED
|
@@ -30,6 +30,9 @@ module RubyLsp
|
|
|
30
30
|
})
|
|
31
31
|
|
|
32
32
|
server.global_state.index.index_single(uri, source)
|
|
33
|
+
graph = server.global_state.graph
|
|
34
|
+
graph.index_source(uri.to_s, source, "ruby")
|
|
35
|
+
graph.resolve
|
|
33
36
|
end
|
|
34
37
|
|
|
35
38
|
server.load_addons(include_project_addons: false) if load_addons
|
|
@@ -5,9 +5,9 @@ module RubyLsp
|
|
|
5
5
|
# A minimalistic type checker to try to resolve types that can be inferred without requiring a type system or
|
|
6
6
|
# annotations
|
|
7
7
|
class TypeInferrer
|
|
8
|
-
#: (
|
|
9
|
-
def initialize(
|
|
10
|
-
@
|
|
8
|
+
#: (Rubydex::Graph) -> void
|
|
9
|
+
def initialize(graph)
|
|
10
|
+
@graph = graph
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
#: (NodeContext node_context) -> Type?
|
|
@@ -19,7 +19,7 @@ module RubyLsp
|
|
|
19
19
|
infer_receiver_for_call_node(node, node_context)
|
|
20
20
|
when Prism::InstanceVariableReadNode, Prism::InstanceVariableAndWriteNode, Prism::InstanceVariableWriteNode,
|
|
21
21
|
Prism::InstanceVariableOperatorWriteNode, Prism::InstanceVariableOrWriteNode, Prism::InstanceVariableTargetNode,
|
|
22
|
-
Prism::SuperNode, Prism::ForwardingSuperNode
|
|
22
|
+
Prism::SuperNode, Prism::ForwardingSuperNode, Prism::DefNode
|
|
23
23
|
self_receiver_handling(node_context)
|
|
24
24
|
when Prism::ClassVariableAndWriteNode, Prism::ClassVariableWriteNode, Prism::ClassVariableOperatorWriteNode,
|
|
25
25
|
Prism::ClassVariableOrWriteNode, Prism::ClassVariableReadNode, Prism::ClassVariableTargetNode
|
|
@@ -81,14 +81,13 @@ module RubyLsp
|
|
|
81
81
|
receiver_name = RubyIndexer::Index.constant_name(receiver)
|
|
82
82
|
return unless receiver_name
|
|
83
83
|
|
|
84
|
-
resolved_receiver = @
|
|
85
|
-
|
|
86
|
-
return unless name
|
|
84
|
+
resolved_receiver = @graph.resolve_constant(receiver_name, node_context.nesting)
|
|
85
|
+
return unless resolved_receiver
|
|
87
86
|
|
|
88
|
-
*parts, last = name.split("::")
|
|
89
|
-
return Type.new("#{last}
|
|
87
|
+
*parts, last = resolved_receiver.name.split("::")
|
|
88
|
+
return Type.new("#{last}::<#{last}>") if parts.empty?
|
|
90
89
|
|
|
91
|
-
Type.new("#{parts.join("::")}::#{last}
|
|
90
|
+
Type.new("#{parts.join("::")}::#{last}::<#{last}>")
|
|
92
91
|
when Prism::CallNode
|
|
93
92
|
raw_receiver = receiver.message
|
|
94
93
|
|
|
@@ -96,12 +95,14 @@ module RubyLsp
|
|
|
96
95
|
# When invoking `new`, we recursively infer the type of the receiver to get the class type its being invoked
|
|
97
96
|
# on and then return the attached version of that type, since it's being instantiated.
|
|
98
97
|
type = infer_receiver_for_call_node(receiver, node_context)
|
|
99
|
-
|
|
100
98
|
return unless type
|
|
101
99
|
|
|
102
100
|
# If the method `new` was overridden, then we cannot assume that it will return a new instance of the class
|
|
103
|
-
|
|
104
|
-
return
|
|
101
|
+
declaration = @graph[type.name] #: as Rubydex::Namespace?
|
|
102
|
+
return unless declaration
|
|
103
|
+
|
|
104
|
+
new_method = declaration.find_member("new()")
|
|
105
|
+
return if new_method && new_method.owner.name != "Class"
|
|
105
106
|
|
|
106
107
|
type.attached
|
|
107
108
|
elsif raw_receiver
|
|
@@ -121,45 +122,124 @@ module RubyLsp
|
|
|
121
122
|
.map(&:capitalize)
|
|
122
123
|
.join
|
|
123
124
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
return unless
|
|
125
|
+
declaration = @graph.resolve_constant(guessed_name, nesting)
|
|
126
|
+
declaration ||= @graph.search(guessed_name).first
|
|
127
|
+
return unless declaration
|
|
127
128
|
|
|
128
|
-
GuessedType.new(name)
|
|
129
|
+
GuessedType.new(declaration.name)
|
|
129
130
|
end
|
|
130
131
|
|
|
131
|
-
#: (NodeContext node_context) -> Type
|
|
132
|
+
#: (NodeContext node_context) -> Type?
|
|
132
133
|
def self_receiver_handling(node_context)
|
|
133
134
|
nesting = node_context.nesting
|
|
134
135
|
# If we're at the top level, then the invocation is happening on `<main>`, which is a special singleton that
|
|
135
136
|
# inherits from Object
|
|
136
137
|
return Type.new("Object") if nesting.empty?
|
|
137
|
-
|
|
138
|
+
|
|
139
|
+
surrounding_method = node_context.surrounding_method
|
|
140
|
+
|
|
141
|
+
if surrounding_method
|
|
142
|
+
receiver_name = surrounding_method.receiver
|
|
143
|
+
|
|
144
|
+
case receiver_name
|
|
145
|
+
when "self"
|
|
146
|
+
# `def self.foo` — self is the singleton of the enclosing class/module
|
|
147
|
+
return resolve_singleton_type_from_nesting(nesting)
|
|
148
|
+
when "none"
|
|
149
|
+
# Instance method — self is an instance of the enclosing class/module
|
|
150
|
+
return resolve_type_from_nesting(nesting)
|
|
151
|
+
when nil
|
|
152
|
+
# Dynamic receiver that we cannot handle
|
|
153
|
+
return
|
|
154
|
+
else
|
|
155
|
+
# Explicit constant receiver (e.g. `def Bar.baz`) — self is that constant's singleton class
|
|
156
|
+
resolved = resolve_receiver_singleton_type(receiver_name, nesting)
|
|
157
|
+
return resolved if resolved
|
|
158
|
+
|
|
159
|
+
return resolve_type_from_nesting(nesting)
|
|
160
|
+
end
|
|
161
|
+
end
|
|
138
162
|
|
|
139
163
|
# If we're not inside a method, then we're inside the body of a class or module, which is a singleton
|
|
140
|
-
# context.
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
164
|
+
# context. Resolve through the graph to get the correct fully qualified name
|
|
165
|
+
resolve_singleton_type_from_nesting(nesting)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Resolves the fully qualified name of the innermost constant from the nesting and returns it as a type.
|
|
169
|
+
# For instance methods, the nesting won't have singleton markers, so the result is an instance type.
|
|
170
|
+
# For `def self.` methods, the nesting includes a singleton marker, which is preserved in the result.
|
|
171
|
+
#: (Array[String] nesting) -> Type
|
|
172
|
+
def resolve_type_from_nesting(nesting)
|
|
173
|
+
resolved_name = resolve_nesting_fully_qualified_name(nesting)
|
|
174
|
+
Type.new(resolved_name)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Resolves the nesting and returns a singleton type (appends `::<Last>`)
|
|
178
|
+
#: (Array[String] nesting) -> Type
|
|
179
|
+
def resolve_singleton_type_from_nesting(nesting)
|
|
180
|
+
resolved_name = resolve_nesting_fully_qualified_name(nesting)
|
|
181
|
+
last_part = resolved_name.split("::").last #: as !nil
|
|
182
|
+
Type.new("#{resolved_name}::<#{last_part}>")
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Resolves the innermost constant in the nesting through the graph, handling compact-path definitions
|
|
186
|
+
# like `class Bar::Baz` inside a different module where the lexical nesting doesn't reflect the true
|
|
187
|
+
# constant hierarchy. Falls back to lexical joining if resolution fails.
|
|
188
|
+
#: (Array[String] nesting) -> String
|
|
189
|
+
def resolve_nesting_fully_qualified_name(nesting)
|
|
190
|
+
nesting_parts = nesting.dup
|
|
191
|
+
trailing_singletons = [] #: Array[String]
|
|
192
|
+
|
|
193
|
+
nesting_parts.reverse_each do |part|
|
|
194
|
+
break unless part.start_with?("<")
|
|
195
|
+
|
|
196
|
+
popped = nesting_parts.pop #: as !nil
|
|
197
|
+
trailing_singletons.unshift(popped)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
if nesting_parts.any?
|
|
201
|
+
resolved = @graph.resolve_constant(
|
|
202
|
+
nesting_parts.last, #: as !nil
|
|
203
|
+
nesting_parts[0...-1], #: as !nil
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
if resolved
|
|
207
|
+
parts = resolved.name.split("::") + trailing_singletons
|
|
208
|
+
return parts.join("::")
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Fallback to lexical joining if resolution fails
|
|
213
|
+
nesting.flat_map { |part| part.split("::") }.join("::")
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
#: (String, Array[String]) -> Type?
|
|
217
|
+
def resolve_receiver_singleton_type(receiver_name, nesting)
|
|
218
|
+
receiver_declaration = @graph.resolve_constant(receiver_name, nesting)
|
|
219
|
+
return unless receiver_declaration.is_a?(Rubydex::Namespace)
|
|
220
|
+
|
|
221
|
+
singleton = receiver_declaration.singleton_class
|
|
222
|
+
return unless singleton
|
|
223
|
+
|
|
224
|
+
Type.new(singleton.name)
|
|
146
225
|
end
|
|
147
226
|
|
|
148
227
|
#: (NodeContext node_context) -> Type?
|
|
149
228
|
def infer_receiver_for_class_variables(node_context)
|
|
150
229
|
nesting_parts = node_context.nesting.dup
|
|
151
|
-
|
|
152
230
|
return Type.new("Object") if nesting_parts.empty?
|
|
153
231
|
|
|
154
232
|
nesting_parts.reverse_each do |part|
|
|
155
|
-
break unless part.
|
|
233
|
+
break unless part.start_with?("<")
|
|
156
234
|
|
|
157
235
|
nesting_parts.pop
|
|
158
236
|
end
|
|
159
237
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
238
|
+
resolved_receiver = @graph.resolve_constant(
|
|
239
|
+
nesting_parts.last, #: as !nil
|
|
240
|
+
nesting_parts[0...-1], #: as !nil
|
|
241
|
+
)
|
|
242
|
+
return unless resolved_receiver
|
|
163
243
|
|
|
164
244
|
Type.new(resolved_receiver.name)
|
|
165
245
|
end
|
|
@@ -174,7 +254,7 @@ module RubyLsp
|
|
|
174
254
|
@name = name
|
|
175
255
|
end
|
|
176
256
|
|
|
177
|
-
# Returns the attached version of this type by removing the
|
|
257
|
+
# Returns the attached version of this type by removing the `<...>` part from its name
|
|
178
258
|
#: -> Type
|
|
179
259
|
def attached
|
|
180
260
|
Type.new(
|