ruby-lsp 0.17.3 → 0.17.5
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/README.md +4 -0
- data/VERSION +1 -1
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +251 -100
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +173 -114
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +337 -77
- data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +43 -14
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +79 -3
- data/lib/ruby_indexer/test/index_test.rb +563 -29
- data/lib/ruby_indexer/test/instance_variables_test.rb +84 -7
- data/lib/ruby_indexer/test/method_test.rb +75 -25
- data/lib/ruby_indexer/test/rbs_indexer_test.rb +38 -2
- data/lib/ruby_indexer/test/test_case.rb +1 -5
- data/lib/ruby_lsp/addon.rb +13 -1
- data/lib/ruby_lsp/document.rb +50 -23
- data/lib/ruby_lsp/erb_document.rb +125 -0
- data/lib/ruby_lsp/global_state.rb +11 -4
- data/lib/ruby_lsp/internal.rb +3 -0
- data/lib/ruby_lsp/listeners/completion.rb +69 -34
- data/lib/ruby_lsp/listeners/definition.rb +34 -23
- data/lib/ruby_lsp/listeners/hover.rb +14 -7
- data/lib/ruby_lsp/listeners/signature_help.rb +5 -2
- data/lib/ruby_lsp/node_context.rb +6 -1
- data/lib/ruby_lsp/requests/code_action_resolve.rb +2 -2
- data/lib/ruby_lsp/requests/completion.rb +6 -5
- data/lib/ruby_lsp/requests/completion_resolve.rb +7 -4
- data/lib/ruby_lsp/requests/definition.rb +4 -3
- data/lib/ruby_lsp/requests/formatting.rb +2 -0
- data/lib/ruby_lsp/requests/prepare_type_hierarchy.rb +88 -0
- data/lib/ruby_lsp/requests/selection_ranges.rb +1 -1
- data/lib/ruby_lsp/requests/show_syntax_tree.rb +3 -2
- data/lib/ruby_lsp/requests/support/common.rb +19 -1
- data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +12 -4
- data/lib/ruby_lsp/requests/type_hierarchy_supertypes.rb +87 -0
- data/lib/ruby_lsp/requests/workspace_symbol.rb +1 -21
- data/lib/ruby_lsp/requests.rb +2 -0
- data/lib/ruby_lsp/ruby_document.rb +10 -0
- data/lib/ruby_lsp/server.rb +95 -26
- data/lib/ruby_lsp/store.rb +23 -8
- data/lib/ruby_lsp/test_helper.rb +3 -1
- data/lib/ruby_lsp/type_inferrer.rb +86 -0
- metadata +10 -6
@@ -0,0 +1,125 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module RubyLsp
|
5
|
+
class ERBDocument < Document
|
6
|
+
extend T::Sig
|
7
|
+
|
8
|
+
sig { override.returns(Prism::ParseResult) }
|
9
|
+
def parse
|
10
|
+
return @parse_result unless @needs_parsing
|
11
|
+
|
12
|
+
@needs_parsing = false
|
13
|
+
scanner = ERBScanner.new(@source)
|
14
|
+
scanner.scan
|
15
|
+
@parse_result = Prism.parse(scanner.ruby)
|
16
|
+
end
|
17
|
+
|
18
|
+
sig { override.returns(T::Boolean) }
|
19
|
+
def syntax_error?
|
20
|
+
@parse_result.failure?
|
21
|
+
end
|
22
|
+
|
23
|
+
sig { override.returns(LanguageId) }
|
24
|
+
def language_id
|
25
|
+
LanguageId::ERB
|
26
|
+
end
|
27
|
+
|
28
|
+
class ERBScanner
|
29
|
+
extend T::Sig
|
30
|
+
|
31
|
+
sig { returns(String) }
|
32
|
+
attr_reader :ruby, :html
|
33
|
+
|
34
|
+
sig { params(source: String).void }
|
35
|
+
def initialize(source)
|
36
|
+
@source = source
|
37
|
+
@html = T.let(+"", String)
|
38
|
+
@ruby = T.let(+"", String)
|
39
|
+
@current_pos = T.let(0, Integer)
|
40
|
+
@inside_ruby = T.let(false, T::Boolean)
|
41
|
+
end
|
42
|
+
|
43
|
+
sig { void }
|
44
|
+
def scan
|
45
|
+
while @current_pos < @source.length
|
46
|
+
scan_char
|
47
|
+
@current_pos += 1
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
sig { void }
|
54
|
+
def scan_char
|
55
|
+
char = @source[@current_pos]
|
56
|
+
|
57
|
+
case char
|
58
|
+
when "<"
|
59
|
+
if next_char == "%"
|
60
|
+
@inside_ruby = true
|
61
|
+
@current_pos += 1
|
62
|
+
push_char(" ")
|
63
|
+
|
64
|
+
if next_char == "=" && @source[@current_pos + 2] == "="
|
65
|
+
@current_pos += 2
|
66
|
+
push_char(" ")
|
67
|
+
elsif next_char == "=" || next_char == "-"
|
68
|
+
@current_pos += 1
|
69
|
+
push_char(" ")
|
70
|
+
end
|
71
|
+
else
|
72
|
+
push_char(T.must(char))
|
73
|
+
end
|
74
|
+
when "-"
|
75
|
+
if @inside_ruby && next_char == "%" &&
|
76
|
+
@source[@current_pos + 2] == ">"
|
77
|
+
@current_pos += 2
|
78
|
+
push_char(" ")
|
79
|
+
@inside_ruby = false
|
80
|
+
else
|
81
|
+
push_char(T.must(char))
|
82
|
+
end
|
83
|
+
when "%"
|
84
|
+
if @inside_ruby && next_char == ">"
|
85
|
+
@inside_ruby = false
|
86
|
+
@current_pos += 1
|
87
|
+
push_char(" ")
|
88
|
+
else
|
89
|
+
push_char(T.must(char))
|
90
|
+
end
|
91
|
+
when "\r"
|
92
|
+
@ruby << char
|
93
|
+
@html << char
|
94
|
+
|
95
|
+
if next_char == "\n"
|
96
|
+
@ruby << next_char
|
97
|
+
@html << next_char
|
98
|
+
@current_pos += 1
|
99
|
+
end
|
100
|
+
when "\n"
|
101
|
+
@ruby << char
|
102
|
+
@html << char
|
103
|
+
else
|
104
|
+
push_char(T.must(char))
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
sig { params(char: String).void }
|
109
|
+
def push_char(char)
|
110
|
+
if @inside_ruby
|
111
|
+
@ruby << char
|
112
|
+
@html << " " * char.length
|
113
|
+
else
|
114
|
+
@ruby << " " * char.length
|
115
|
+
@html << char
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
sig { returns(String) }
|
120
|
+
def next_char
|
121
|
+
@source[@current_pos + 1] || ""
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -12,7 +12,7 @@ module RubyLsp
|
|
12
12
|
attr_accessor :formatter
|
13
13
|
|
14
14
|
sig { returns(T::Boolean) }
|
15
|
-
attr_reader :
|
15
|
+
attr_reader :has_type_checker
|
16
16
|
|
17
17
|
sig { returns(RubyIndexer::Index) }
|
18
18
|
attr_reader :index
|
@@ -21,7 +21,10 @@ module RubyLsp
|
|
21
21
|
attr_reader :encoding
|
22
22
|
|
23
23
|
sig { returns(T::Boolean) }
|
24
|
-
attr_reader :supports_watching_files
|
24
|
+
attr_reader :supports_watching_files, :experimental_features
|
25
|
+
|
26
|
+
sig { returns(TypeInferrer) }
|
27
|
+
attr_reader :type_inferrer
|
25
28
|
|
26
29
|
sig { void }
|
27
30
|
def initialize
|
@@ -31,10 +34,12 @@ module RubyLsp
|
|
31
34
|
@formatter = T.let("auto", String)
|
32
35
|
@linters = T.let([], T::Array[String])
|
33
36
|
@test_library = T.let("minitest", String)
|
34
|
-
@
|
37
|
+
@has_type_checker = T.let(true, T::Boolean)
|
35
38
|
@index = T.let(RubyIndexer::Index.new, RubyIndexer::Index)
|
39
|
+
@type_inferrer = T.let(TypeInferrer.new(@index), TypeInferrer)
|
36
40
|
@supported_formatters = T.let({}, T::Hash[String, Requests::Support::Formatter])
|
37
41
|
@supports_watching_files = T.let(false, T::Boolean)
|
42
|
+
@experimental_features = T.let(false, T::Boolean)
|
38
43
|
end
|
39
44
|
|
40
45
|
sig { params(identifier: String, instance: Requests::Support::Formatter).void }
|
@@ -66,7 +71,7 @@ module RubyLsp
|
|
66
71
|
specified_linters = options.dig(:initializationOptions, :linters)
|
67
72
|
@linters = specified_linters || detect_linters(direct_dependencies)
|
68
73
|
@test_library = detect_test_library(direct_dependencies)
|
69
|
-
@
|
74
|
+
@has_type_checker = detect_typechecker(direct_dependencies)
|
70
75
|
|
71
76
|
encodings = options.dig(:capabilities, :general, :positionEncodings)
|
72
77
|
@encoding = if !encodings || encodings.empty?
|
@@ -83,6 +88,8 @@ module RubyLsp
|
|
83
88
|
if file_watching_caps&.dig(:dynamicRegistration) && file_watching_caps&.dig(:relativePatternSupport)
|
84
89
|
@supports_watching_files = true
|
85
90
|
end
|
91
|
+
|
92
|
+
@experimental_features = options.dig(:initializationOptions, :experimentalFeaturesEnabled) || false
|
86
93
|
end
|
87
94
|
|
88
95
|
sig { returns(String) }
|
data/lib/ruby_lsp/internal.rb
CHANGED
@@ -15,6 +15,7 @@ Bundler.ui.level = :silent
|
|
15
15
|
require "uri"
|
16
16
|
require "cgi"
|
17
17
|
require "set"
|
18
|
+
require "strscan"
|
18
19
|
require "prism"
|
19
20
|
require "prism/visitor"
|
20
21
|
require "language_server-protocol"
|
@@ -28,11 +29,13 @@ require "ruby_lsp/utils"
|
|
28
29
|
require "ruby_lsp/parameter_scope"
|
29
30
|
require "ruby_lsp/global_state"
|
30
31
|
require "ruby_lsp/server"
|
32
|
+
require "ruby_lsp/type_inferrer"
|
31
33
|
require "ruby_lsp/requests"
|
32
34
|
require "ruby_lsp/response_builders"
|
33
35
|
require "ruby_lsp/node_context"
|
34
36
|
require "ruby_lsp/document"
|
35
37
|
require "ruby_lsp/ruby_document"
|
38
|
+
require "ruby_lsp/erb_document"
|
36
39
|
require "ruby_lsp/store"
|
37
40
|
require "ruby_lsp/addon"
|
38
41
|
require "ruby_lsp/requests/support/rubocop_runner"
|
@@ -15,15 +15,26 @@ module RubyLsp
|
|
15
15
|
typechecker_enabled: T::Boolean,
|
16
16
|
dispatcher: Prism::Dispatcher,
|
17
17
|
uri: URI::Generic,
|
18
|
+
trigger_character: T.nilable(String),
|
18
19
|
).void
|
19
20
|
end
|
20
|
-
def initialize(
|
21
|
+
def initialize( # rubocop:disable Metrics/ParameterLists
|
22
|
+
response_builder,
|
23
|
+
global_state,
|
24
|
+
node_context,
|
25
|
+
typechecker_enabled,
|
26
|
+
dispatcher,
|
27
|
+
uri,
|
28
|
+
trigger_character
|
29
|
+
)
|
21
30
|
@response_builder = response_builder
|
22
31
|
@global_state = global_state
|
23
32
|
@index = T.let(global_state.index, RubyIndexer::Index)
|
33
|
+
@type_inferrer = T.let(global_state.type_inferrer, TypeInferrer)
|
24
34
|
@node_context = node_context
|
25
35
|
@typechecker_enabled = typechecker_enabled
|
26
36
|
@uri = uri
|
37
|
+
@trigger_character = trigger_character
|
27
38
|
|
28
39
|
dispatcher.register(
|
29
40
|
self,
|
@@ -42,7 +53,7 @@ module RubyLsp
|
|
42
53
|
# Handle completion on regular constant references (e.g. `Bar`)
|
43
54
|
sig { params(node: Prism::ConstantReadNode).void }
|
44
55
|
def on_constant_read_node_enter(node)
|
45
|
-
return if @global_state.
|
56
|
+
return if @global_state.has_type_checker
|
46
57
|
|
47
58
|
name = constant_name(node)
|
48
59
|
return if name.nil?
|
@@ -63,7 +74,7 @@ module RubyLsp
|
|
63
74
|
# Handle completion on namespaced constant references (e.g. `Foo::Bar`)
|
64
75
|
sig { params(node: Prism::ConstantPathNode).void }
|
65
76
|
def on_constant_path_node_enter(node)
|
66
|
-
return if @global_state.
|
77
|
+
return if @global_state.has_type_checker
|
67
78
|
|
68
79
|
name = constant_name(node)
|
69
80
|
return if name.nil?
|
@@ -107,7 +118,7 @@ module RubyLsp
|
|
107
118
|
when "require_relative"
|
108
119
|
complete_require_relative(node)
|
109
120
|
else
|
110
|
-
|
121
|
+
complete_methods(node, name) unless @typechecker_enabled
|
111
122
|
end
|
112
123
|
end
|
113
124
|
|
@@ -158,7 +169,7 @@ module RubyLsp
|
|
158
169
|
name.delete_suffix("::")
|
159
170
|
else
|
160
171
|
*namespace, incomplete_name = name.split("::")
|
161
|
-
|
172
|
+
namespace.join("::")
|
162
173
|
end
|
163
174
|
|
164
175
|
nesting = @node_context.nesting
|
@@ -192,11 +203,19 @@ module RubyLsp
|
|
192
203
|
|
193
204
|
sig { params(name: String, location: Prism::Location).void }
|
194
205
|
def handle_instance_variable_completion(name, location)
|
195
|
-
@
|
206
|
+
type = @type_inferrer.infer_receiver_type(@node_context)
|
207
|
+
return unless type
|
208
|
+
|
209
|
+
@index.instance_variable_completion_candidates(name, type).each do |entry|
|
196
210
|
variable_name = entry.name
|
197
211
|
|
212
|
+
label_details = Interface::CompletionItemLabelDetails.new(
|
213
|
+
description: entry.file_name,
|
214
|
+
)
|
215
|
+
|
198
216
|
@response_builder << Interface::CompletionItem.new(
|
199
217
|
label: variable_name,
|
218
|
+
label_details: label_details,
|
200
219
|
text_edit: Interface::TextEdit.new(
|
201
220
|
range: range_from_location(location),
|
202
221
|
new_text: variable_name,
|
@@ -257,40 +276,50 @@ module RubyLsp
|
|
257
276
|
end
|
258
277
|
|
259
278
|
sig { params(node: Prism::CallNode, name: String).void }
|
260
|
-
def
|
261
|
-
|
262
|
-
return unless
|
279
|
+
def complete_methods(node, name)
|
280
|
+
type = @type_inferrer.infer_receiver_type(@node_context)
|
281
|
+
return unless type
|
263
282
|
|
264
|
-
|
283
|
+
# When the trigger character is a dot, Prism matches the name of the call node to whatever is next in the source
|
284
|
+
# code, leading to us searching for the wrong name. What we want to do instead is show every available method
|
285
|
+
# when dot is pressed
|
286
|
+
method_name = @trigger_character == "." ? nil : name
|
265
287
|
|
266
|
-
|
267
|
-
|
288
|
+
range = if method_name
|
289
|
+
range_from_location(T.must(node.message_loc))
|
290
|
+
else
|
291
|
+
loc = node.call_operator_loc
|
292
|
+
|
293
|
+
if loc
|
294
|
+
Interface::Range.new(
|
295
|
+
start: Interface::Position.new(line: loc.start_line - 1, character: loc.start_column + 1),
|
296
|
+
end: Interface::Position.new(line: loc.start_line - 1, character: loc.start_column + 1),
|
297
|
+
)
|
298
|
+
end
|
268
299
|
end
|
269
|
-
end
|
270
300
|
|
271
|
-
|
272
|
-
params(
|
273
|
-
entry: RubyIndexer::Entry::Member,
|
274
|
-
node: Prism::CallNode,
|
275
|
-
).returns(Interface::CompletionItem)
|
276
|
-
end
|
277
|
-
def build_method_completion(entry, node)
|
278
|
-
name = entry.name
|
301
|
+
return unless range
|
279
302
|
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
kind: Constant::CompletionItemKind::METHOD,
|
285
|
-
label_details: Interface::CompletionItemLabelDetails.new(
|
286
|
-
detail: "(#{entry.parameters.map(&:decorated_name).join(", ")})",
|
303
|
+
@index.method_completion_candidates(method_name, type).each do |entry|
|
304
|
+
entry_name = entry.name
|
305
|
+
|
306
|
+
label_details = Interface::CompletionItemLabelDetails.new(
|
287
307
|
description: entry.file_name,
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
308
|
+
detail: entry.decorated_parameters,
|
309
|
+
)
|
310
|
+
@response_builder << Interface::CompletionItem.new(
|
311
|
+
label: entry_name,
|
312
|
+
filter_text: entry_name,
|
313
|
+
label_details: label_details,
|
314
|
+
text_edit: Interface::TextEdit.new(range: range, new_text: entry_name),
|
315
|
+
kind: Constant::CompletionItemKind::METHOD,
|
316
|
+
data: {
|
317
|
+
owner_name: entry.owner&.name,
|
318
|
+
},
|
319
|
+
)
|
320
|
+
end
|
321
|
+
rescue RubyIndexer::Index::NonExistingNamespaceError
|
322
|
+
# We have not indexed this namespace, so we can't provide any completions
|
294
323
|
end
|
295
324
|
|
296
325
|
sig { params(label: String, node: Prism::StringNode).returns(Interface::CompletionItem) }
|
@@ -374,8 +403,14 @@ module RubyLsp
|
|
374
403
|
# When using a top level constant reference (e.g.: `::Bar`), the editor includes the `::` as part of the filter.
|
375
404
|
# For these top level references, we need to include the `::` as part of the filter text or else it won't match
|
376
405
|
# the right entries in the index
|
406
|
+
|
407
|
+
label_details = Interface::CompletionItemLabelDetails.new(
|
408
|
+
description: entries.map(&:file_name).join(","),
|
409
|
+
)
|
410
|
+
|
377
411
|
Interface::CompletionItem.new(
|
378
412
|
label: real_name,
|
413
|
+
label_details: label_details,
|
379
414
|
filter_text: filter_text,
|
380
415
|
text_edit: Interface::TextEdit.new(
|
381
416
|
range: range,
|
@@ -11,18 +11,24 @@ module RubyLsp
|
|
11
11
|
|
12
12
|
sig do
|
13
13
|
params(
|
14
|
-
response_builder: ResponseBuilders::CollectionResponseBuilder[
|
14
|
+
response_builder: ResponseBuilders::CollectionResponseBuilder[T.any(
|
15
|
+
Interface::Location,
|
16
|
+
Interface::LocationLink,
|
17
|
+
)],
|
15
18
|
global_state: GlobalState,
|
19
|
+
language_id: Document::LanguageId,
|
16
20
|
uri: URI::Generic,
|
17
21
|
node_context: NodeContext,
|
18
22
|
dispatcher: Prism::Dispatcher,
|
19
23
|
typechecker_enabled: T::Boolean,
|
20
24
|
).void
|
21
25
|
end
|
22
|
-
def initialize(response_builder, global_state, uri, node_context, dispatcher, typechecker_enabled) # rubocop:disable Metrics/ParameterLists
|
26
|
+
def initialize(response_builder, global_state, language_id, uri, node_context, dispatcher, typechecker_enabled) # rubocop:disable Metrics/ParameterLists
|
23
27
|
@response_builder = response_builder
|
24
28
|
@global_state = global_state
|
25
29
|
@index = T.let(global_state.index, RubyIndexer::Index)
|
30
|
+
@type_inferrer = T.let(global_state.type_inferrer, TypeInferrer)
|
31
|
+
@language_id = language_id
|
26
32
|
@uri = uri
|
27
33
|
@node_context = node_context
|
28
34
|
@typechecker_enabled = typechecker_enabled
|
@@ -48,7 +54,15 @@ module RubyLsp
|
|
48
54
|
message = node.message
|
49
55
|
return unless message
|
50
56
|
|
51
|
-
|
57
|
+
inferrer_receiver_type = @type_inferrer.infer_receiver_type(@node_context)
|
58
|
+
|
59
|
+
# Until we can properly infer the receiver type in erb files (maybe with ruby-lsp-rails),
|
60
|
+
# treating method calls' type as `nil` will allow users to get some completion support first
|
61
|
+
if @language_id == Document::LanguageId::ERB && inferrer_receiver_type == "Object"
|
62
|
+
inferrer_receiver_type = nil
|
63
|
+
end
|
64
|
+
|
65
|
+
handle_method_definition(message, inferrer_receiver_type)
|
52
66
|
end
|
53
67
|
|
54
68
|
sig { params(node: Prism::StringNode).void }
|
@@ -70,7 +84,7 @@ module RubyLsp
|
|
70
84
|
value = expression.value
|
71
85
|
return unless value
|
72
86
|
|
73
|
-
handle_method_definition(value,
|
87
|
+
handle_method_definition(value, nil)
|
74
88
|
end
|
75
89
|
|
76
90
|
sig { params(node: Prism::ConstantPathNode).void }
|
@@ -123,7 +137,10 @@ module RubyLsp
|
|
123
137
|
|
124
138
|
sig { params(name: String).void }
|
125
139
|
def handle_instance_variable_definition(name)
|
126
|
-
|
140
|
+
type = @type_inferrer.infer_receiver_type(@node_context)
|
141
|
+
return unless type
|
142
|
+
|
143
|
+
entries = @index.resolve_instance_variable(name, type)
|
127
144
|
return unless entries
|
128
145
|
|
129
146
|
entries.each do |entry|
|
@@ -141,10 +158,10 @@ module RubyLsp
|
|
141
158
|
# If by any chance we haven't indexed the owner, then there's no way to find the right declaration
|
142
159
|
end
|
143
160
|
|
144
|
-
sig { params(message: String,
|
145
|
-
def handle_method_definition(message,
|
146
|
-
methods = if
|
147
|
-
@index.resolve_method(message,
|
161
|
+
sig { params(message: String, receiver_type: T.nilable(String)).void }
|
162
|
+
def handle_method_definition(message, receiver_type)
|
163
|
+
methods = if receiver_type
|
164
|
+
@index.resolve_method(message, receiver_type)
|
148
165
|
else
|
149
166
|
# If the method doesn't have a receiver, then we provide a few candidates to jump to
|
150
167
|
# But we don't want to provide too many candidates, as it can be overwhelming
|
@@ -154,16 +171,13 @@ module RubyLsp
|
|
154
171
|
return unless methods
|
155
172
|
|
156
173
|
methods.each do |target_method|
|
157
|
-
location = target_method.location
|
158
174
|
file_path = target_method.file_path
|
159
175
|
next if @typechecker_enabled && not_in_dependencies?(file_path)
|
160
176
|
|
161
|
-
@response_builder << Interface::
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
end: Interface::Position.new(line: location.end_line - 1, character: location.end_column),
|
166
|
-
),
|
177
|
+
@response_builder << Interface::LocationLink.new(
|
178
|
+
target_uri: URI::Generic.from_path(path: file_path).to_s,
|
179
|
+
target_range: range_from_location(target_method.location),
|
180
|
+
target_selection_range: range_from_location(target_method.name_location),
|
167
181
|
)
|
168
182
|
end
|
169
183
|
end
|
@@ -214,19 +228,16 @@ module RubyLsp
|
|
214
228
|
return if first_entry.private? && first_entry.name != "#{@node_context.fully_qualified_name}::#{value}"
|
215
229
|
|
216
230
|
entries.each do |entry|
|
217
|
-
location = entry.location
|
218
231
|
# If the project has Sorbet, then we only want to handle go to definition for constants defined in gems, as an
|
219
232
|
# additional behavior on top of jumping to RBIs. Sorbet can already handle go to definition for all constants
|
220
233
|
# in the project, even if the files are typed false
|
221
234
|
file_path = entry.file_path
|
222
235
|
next if @typechecker_enabled && not_in_dependencies?(file_path)
|
223
236
|
|
224
|
-
@response_builder << Interface::
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
end: Interface::Position.new(line: location.end_line - 1, character: location.end_column),
|
229
|
-
),
|
237
|
+
@response_builder << Interface::LocationLink.new(
|
238
|
+
target_uri: URI::Generic.from_path(path: file_path).to_s,
|
239
|
+
target_range: range_from_location(entry.location),
|
240
|
+
target_selection_range: range_from_location(entry.name_location),
|
230
241
|
)
|
231
242
|
end
|
232
243
|
end
|
@@ -47,6 +47,7 @@ module RubyLsp
|
|
47
47
|
@response_builder = response_builder
|
48
48
|
@global_state = global_state
|
49
49
|
@index = T.let(global_state.index, RubyIndexer::Index)
|
50
|
+
@type_inferrer = T.let(global_state.type_inferrer, TypeInferrer)
|
50
51
|
@path = T.let(uri.to_standardized_path, T.nilable(String))
|
51
52
|
@node_context = node_context
|
52
53
|
@typechecker_enabled = typechecker_enabled
|
@@ -78,14 +79,14 @@ module RubyLsp
|
|
78
79
|
|
79
80
|
sig { params(node: Prism::ConstantWriteNode).void }
|
80
81
|
def on_constant_write_node_enter(node)
|
81
|
-
return if @global_state.
|
82
|
+
return if @global_state.has_type_checker
|
82
83
|
|
83
84
|
generate_hover(node.name.to_s, node.name_loc)
|
84
85
|
end
|
85
86
|
|
86
87
|
sig { params(node: Prism::ConstantPathNode).void }
|
87
88
|
def on_constant_path_node_enter(node)
|
88
|
-
return if @global_state.
|
89
|
+
return if @global_state.has_type_checker
|
89
90
|
|
90
91
|
name = constant_name(node)
|
91
92
|
return if name.nil?
|
@@ -95,8 +96,6 @@ module RubyLsp
|
|
95
96
|
|
96
97
|
sig { params(node: Prism::CallNode).void }
|
97
98
|
def on_call_node_enter(node)
|
98
|
-
return unless self_receiver?(node)
|
99
|
-
|
100
99
|
if @path && File.basename(@path) == GEMFILE_NAME && node.name == :gem
|
101
100
|
generate_gem_hover(node)
|
102
101
|
return
|
@@ -107,10 +106,15 @@ module RubyLsp
|
|
107
106
|
message = node.message
|
108
107
|
return unless message
|
109
108
|
|
110
|
-
|
109
|
+
type = @type_inferrer.infer_receiver_type(@node_context)
|
110
|
+
return unless type
|
111
|
+
|
112
|
+
methods = @index.resolve_method(message, type)
|
111
113
|
return unless methods
|
112
114
|
|
113
|
-
|
115
|
+
title = "#{message}#{T.must(methods.first).decorated_parameters}"
|
116
|
+
|
117
|
+
categorized_markdown_from_index_entries(title, methods).each do |category, content|
|
114
118
|
@response_builder.push(content, category: category)
|
115
119
|
end
|
116
120
|
end
|
@@ -149,7 +153,10 @@ module RubyLsp
|
|
149
153
|
|
150
154
|
sig { params(name: String).void }
|
151
155
|
def handle_instance_variable_hover(name)
|
152
|
-
|
156
|
+
type = @type_inferrer.infer_receiver_type(@node_context)
|
157
|
+
return unless type
|
158
|
+
|
159
|
+
entries = @index.resolve_instance_variable(name, type)
|
153
160
|
return unless entries
|
154
161
|
|
155
162
|
categorized_markdown_from_index_entries(name, entries).each do |category, content|
|
@@ -21,6 +21,7 @@ module RubyLsp
|
|
21
21
|
@response_builder = response_builder
|
22
22
|
@global_state = global_state
|
23
23
|
@index = T.let(global_state.index, RubyIndexer::Index)
|
24
|
+
@type_inferrer = T.let(global_state.type_inferrer, TypeInferrer)
|
24
25
|
@node_context = node_context
|
25
26
|
dispatcher.register(self, :on_call_node_enter)
|
26
27
|
end
|
@@ -28,12 +29,14 @@ module RubyLsp
|
|
28
29
|
sig { params(node: Prism::CallNode).void }
|
29
30
|
def on_call_node_enter(node)
|
30
31
|
return if @typechecker_enabled
|
31
|
-
return unless self_receiver?(node)
|
32
32
|
|
33
33
|
message = node.message
|
34
34
|
return unless message
|
35
35
|
|
36
|
-
|
36
|
+
type = @type_inferrer.infer_receiver_type(@node_context)
|
37
|
+
return unless type
|
38
|
+
|
39
|
+
methods = @index.resolve_method(message, type)
|
37
40
|
return unless methods
|
38
41
|
|
39
42
|
target_method = methods.first
|
@@ -16,19 +16,24 @@ module RubyLsp
|
|
16
16
|
sig { returns(T.nilable(Prism::CallNode)) }
|
17
17
|
attr_reader :call_node
|
18
18
|
|
19
|
+
sig { returns(T.nilable(String)) }
|
20
|
+
attr_reader :surrounding_method
|
21
|
+
|
19
22
|
sig do
|
20
23
|
params(
|
21
24
|
node: T.nilable(Prism::Node),
|
22
25
|
parent: T.nilable(Prism::Node),
|
23
26
|
nesting: T::Array[String],
|
24
27
|
call_node: T.nilable(Prism::CallNode),
|
28
|
+
surrounding_method: T.nilable(String),
|
25
29
|
).void
|
26
30
|
end
|
27
|
-
def initialize(node, parent, nesting, call_node)
|
31
|
+
def initialize(node, parent, nesting, call_node, surrounding_method)
|
28
32
|
@node = node
|
29
33
|
@parent = parent
|
30
34
|
@nesting = nesting
|
31
35
|
@call_node = call_node
|
36
|
+
@surrounding_method = surrounding_method
|
32
37
|
end
|
33
38
|
|
34
39
|
sig { returns(String) }
|
@@ -69,7 +69,7 @@ module RubyLsp
|
|
69
69
|
|
70
70
|
# Find the closest statements node, so that we place the refactor in a valid position
|
71
71
|
node_context = @document
|
72
|
-
.locate(@document.
|
72
|
+
.locate(@document.parse_result.value, start_index, node_types: [Prism::StatementsNode, Prism::BlockNode])
|
73
73
|
|
74
74
|
closest_statements = node_context.node
|
75
75
|
parent_statements = node_context.parent
|
@@ -164,7 +164,7 @@ module RubyLsp
|
|
164
164
|
extracted_source = T.must(@document.source[start_index...end_index])
|
165
165
|
|
166
166
|
# Find the closest method declaration node, so that we place the refactor in a valid position
|
167
|
-
node_context = @document.locate(@document.
|
167
|
+
node_context = @document.locate(@document.parse_result.value, start_index, node_types: [Prism::DefNode])
|
168
168
|
closest_def = T.cast(node_context.node, Prism::DefNode)
|
169
169
|
return Error::InvalidTargetRange if closest_def.nil?
|
170
170
|
|
@@ -36,7 +36,7 @@ module RubyLsp
|
|
36
36
|
def provider
|
37
37
|
Interface::CompletionOptions.new(
|
38
38
|
resolve_provider: true,
|
39
|
-
trigger_characters: ["/", "\"", "'", ":", "@"],
|
39
|
+
trigger_characters: ["/", "\"", "'", ":", "@", "."],
|
40
40
|
completion_item: {
|
41
41
|
labelDetailsSupport: true,
|
42
42
|
},
|
@@ -48,20 +48,20 @@ module RubyLsp
|
|
48
48
|
params(
|
49
49
|
document: Document,
|
50
50
|
global_state: GlobalState,
|
51
|
-
|
51
|
+
params: T::Hash[Symbol, T.untyped],
|
52
52
|
typechecker_enabled: T::Boolean,
|
53
53
|
dispatcher: Prism::Dispatcher,
|
54
54
|
).void
|
55
55
|
end
|
56
|
-
def initialize(document, global_state,
|
56
|
+
def initialize(document, global_state, params, typechecker_enabled, dispatcher)
|
57
57
|
super()
|
58
58
|
@target = T.let(nil, T.nilable(Prism::Node))
|
59
59
|
@dispatcher = dispatcher
|
60
60
|
# Completion always receives the position immediately after the character that was just typed. Here we adjust it
|
61
61
|
# back by 1, so that we find the right node
|
62
|
-
char_position = document.create_scanner.find_char_position(position) - 1
|
62
|
+
char_position = document.create_scanner.find_char_position(params[:position]) - 1
|
63
63
|
node_context = document.locate(
|
64
|
-
document.
|
64
|
+
document.parse_result.value,
|
65
65
|
char_position,
|
66
66
|
node_types: [
|
67
67
|
Prism::CallNode,
|
@@ -87,6 +87,7 @@ module RubyLsp
|
|
87
87
|
typechecker_enabled,
|
88
88
|
dispatcher,
|
89
89
|
document.uri,
|
90
|
+
params.dig(:context, :triggerCharacter),
|
90
91
|
)
|
91
92
|
|
92
93
|
Addon.addons.each do |addon|
|