ruby-lsp 0.17.7 → 0.17.9
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 +6 -1
- data/VERSION +1 -1
- data/exe/ruby-lsp +25 -0
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +16 -3
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +28 -0
- data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +14 -26
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +38 -0
- data/lib/ruby_indexer/test/index_test.rb +15 -0
- data/lib/ruby_lsp/base_server.rb +1 -1
- data/lib/ruby_lsp/document.rb +27 -4
- data/lib/ruby_lsp/global_state.rb +2 -1
- data/lib/ruby_lsp/listeners/completion.rb +118 -37
- data/lib/ruby_lsp/listeners/definition.rb +23 -11
- data/lib/ruby_lsp/listeners/hover.rb +21 -9
- data/lib/ruby_lsp/listeners/signature_help.rb +13 -6
- data/lib/ruby_lsp/requests/completion.rb +3 -3
- data/lib/ruby_lsp/requests/completion_resolve.rb +10 -3
- data/lib/ruby_lsp/requests/definition.rb +3 -3
- data/lib/ruby_lsp/requests/hover.rb +3 -3
- data/lib/ruby_lsp/requests/signature_help.rb +3 -3
- data/lib/ruby_lsp/requests/support/common.rb +11 -2
- data/lib/ruby_lsp/server.rb +76 -44
- data/lib/ruby_lsp/store.rb +10 -1
- data/lib/ruby_lsp/test_helper.rb +1 -1
- data/lib/ruby_lsp/type_inferrer.rb +48 -10
- data/lib/ruby_lsp/utils.rb +1 -0
- metadata +3 -5
- data/exe/ruby-lsp-doctor +0 -23
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f3deccbc9952a61d46392be2f050a4684b108e7cd3da0085a4e8997168dfc021
|
4
|
+
data.tar.gz: d1b9a804d9e67aa2d23b39f12c79c6e1d5ba940ec360170623f7e0870e7d6c78
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fe2e99145d0f268644a5d8feeff49146e635998ab8ff3eff18f47b1f555bb249b296966d0a431c930ab6ebca58032ed954a338f7c075300869545711bcfae50e
|
7
|
+
data.tar.gz: 36f253a202755a49e33b8d6c4906aa0acfebe9fe52772519684d433649d7fbadfee186274eba94027017535e55438333759477e2dbb87a7da7e9ead0bc98aaa7
|
data/README.md
CHANGED
@@ -78,7 +78,12 @@ default gems, except for
|
|
78
78
|
- Gems that only appear under the `:development` group
|
79
79
|
- All Ruby files under `test/**/*.rb`
|
80
80
|
|
81
|
-
|
81
|
+
This behaviour can be overridden and tuned. Learn how to configure it [for VS Code](vscode/README.md#Indexing-Configuration) or [for other editors](EDITORS.md#Indexing-Configuration).
|
82
|
+
|
83
|
+
Note that indexing-dependent behavior, such as definition, hover, completion or workspace symbol will be impacted by
|
84
|
+
the configuration changes.
|
85
|
+
|
86
|
+
The older approach of using a `.index.yml` file has been deprecated and will be removed in a future release.
|
82
87
|
|
83
88
|
```yaml
|
84
89
|
# Exclude files based on a given pattern. Often used to exclude test files or fixtures
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.17.
|
1
|
+
0.17.9
|
data/exe/ruby-lsp
CHANGED
@@ -36,6 +36,10 @@ parser = OptionParser.new do |opts|
|
|
36
36
|
options[:experimental] = true
|
37
37
|
end
|
38
38
|
|
39
|
+
opts.on("--doctor", "Run troubleshooting steps") do
|
40
|
+
options[:doctor] = true
|
41
|
+
end
|
42
|
+
|
39
43
|
opts.on("-h", "--help", "Print this help") do
|
40
44
|
puts opts.help
|
41
45
|
puts
|
@@ -107,4 +111,25 @@ if options[:time_index]
|
|
107
111
|
return
|
108
112
|
end
|
109
113
|
|
114
|
+
if options[:doctor]
|
115
|
+
if File.exist?(".index.yml")
|
116
|
+
begin
|
117
|
+
config = YAML.parse_file(".index.yml").to_ruby
|
118
|
+
rescue => e
|
119
|
+
abort("Error parsing config: #{e.message}")
|
120
|
+
end
|
121
|
+
RubyIndexer.configuration.apply_config(config)
|
122
|
+
end
|
123
|
+
|
124
|
+
index = RubyIndexer::Index.new
|
125
|
+
|
126
|
+
puts "Globbing for indexable files"
|
127
|
+
|
128
|
+
RubyIndexer.configuration.indexables.each do |indexable|
|
129
|
+
puts "indexing: #{indexable.full_path}"
|
130
|
+
index.index_single(indexable)
|
131
|
+
end
|
132
|
+
return
|
133
|
+
end
|
134
|
+
|
110
135
|
RubyLsp::Server.new.start
|
@@ -71,7 +71,7 @@ module RubyIndexer
|
|
71
71
|
|
72
72
|
superclass = node.superclass
|
73
73
|
|
74
|
-
nesting =
|
74
|
+
nesting = actual_nesting(name)
|
75
75
|
|
76
76
|
parent_class = case superclass
|
77
77
|
when Prism::ConstantReadNode, Prism::ConstantPathNode
|
@@ -119,8 +119,7 @@ module RubyIndexer
|
|
119
119
|
|
120
120
|
comments = collect_comments(node)
|
121
121
|
|
122
|
-
|
123
|
-
entry = Entry::Module.new(nesting, @file_path, node.location, constant_path.location, comments)
|
122
|
+
entry = Entry::Module.new(actual_nesting(name), @file_path, node.location, constant_path.location, comments)
|
124
123
|
|
125
124
|
@owner_stack << entry
|
126
125
|
@index.add(entry)
|
@@ -709,5 +708,19 @@ module RubyIndexer
|
|
709
708
|
:"(#{names_with_commas})"
|
710
709
|
end
|
711
710
|
end
|
711
|
+
|
712
|
+
sig { params(name: String).returns(T::Array[String]) }
|
713
|
+
def actual_nesting(name)
|
714
|
+
nesting = @stack + [name]
|
715
|
+
corrected_nesting = []
|
716
|
+
|
717
|
+
nesting.reverse_each do |name|
|
718
|
+
corrected_nesting.prepend(name.delete_prefix("::"))
|
719
|
+
|
720
|
+
break if name.start_with?("::")
|
721
|
+
end
|
722
|
+
|
723
|
+
corrected_nesting
|
724
|
+
end
|
712
725
|
end
|
713
726
|
end
|
@@ -84,6 +84,34 @@ module RubyIndexer
|
|
84
84
|
@require_paths_tree.search(query)
|
85
85
|
end
|
86
86
|
|
87
|
+
# Searches for a constant based on an unqualified name and returns the first possible match regardless of whether
|
88
|
+
# there are more possible matching entries
|
89
|
+
sig do
|
90
|
+
params(
|
91
|
+
name: String,
|
92
|
+
).returns(T.nilable(T::Array[T.any(
|
93
|
+
Entry::Namespace,
|
94
|
+
Entry::Alias,
|
95
|
+
Entry::UnresolvedAlias,
|
96
|
+
Entry::Constant,
|
97
|
+
)]))
|
98
|
+
end
|
99
|
+
def first_unqualified_const(name)
|
100
|
+
_name, entries = @entries.find do |const_name, _entries|
|
101
|
+
const_name.end_with?(name)
|
102
|
+
end
|
103
|
+
|
104
|
+
T.cast(
|
105
|
+
entries,
|
106
|
+
T.nilable(T::Array[T.any(
|
107
|
+
Entry::Namespace,
|
108
|
+
Entry::Alias,
|
109
|
+
Entry::UnresolvedAlias,
|
110
|
+
Entry::Constant,
|
111
|
+
)]),
|
112
|
+
)
|
113
|
+
end
|
114
|
+
|
87
115
|
# Searches entries in the index based on an exact prefix, intended for providing autocomplete. All possible matches
|
88
116
|
# to the prefix are returned. The return is an array of arrays, where each entry is the array of entries for a given
|
89
117
|
# name match. For example:
|
@@ -37,45 +37,33 @@ module RubyIndexer
|
|
37
37
|
sig { params(declaration: RBS::AST::Declarations::Base, pathname: Pathname).void }
|
38
38
|
def process_declaration(declaration, pathname)
|
39
39
|
case declaration
|
40
|
-
when RBS::AST::Declarations::Class
|
41
|
-
|
42
|
-
when RBS::AST::Declarations::Module
|
43
|
-
handle_module_declaration(declaration, pathname)
|
40
|
+
when RBS::AST::Declarations::Class, RBS::AST::Declarations::Module
|
41
|
+
handle_class_or_module_declaration(declaration, pathname)
|
44
42
|
else # rubocop:disable Style/EmptyElse
|
45
43
|
# Other kinds not yet handled
|
46
44
|
end
|
47
45
|
end
|
48
46
|
|
49
|
-
sig
|
50
|
-
|
51
|
-
nesting = [declaration.name.name.to_s]
|
52
|
-
file_path = pathname.to_s
|
53
|
-
location = to_ruby_indexer_location(declaration.location)
|
54
|
-
comments = Array(declaration.comment&.string)
|
55
|
-
parent_class = declaration.super_class&.name&.name&.to_s
|
56
|
-
class_entry = Entry::Class.new(nesting, file_path, location, location, comments, parent_class)
|
57
|
-
add_declaration_mixins_to_entry(declaration, class_entry)
|
58
|
-
@index.add(class_entry)
|
59
|
-
declaration.members.each do |member|
|
60
|
-
next unless member.is_a?(RBS::AST::Members::MethodDefinition)
|
61
|
-
|
62
|
-
handle_method(member, class_entry)
|
63
|
-
end
|
47
|
+
sig do
|
48
|
+
params(declaration: T.any(RBS::AST::Declarations::Class, RBS::AST::Declarations::Module), pathname: Pathname).void
|
64
49
|
end
|
65
|
-
|
66
|
-
sig { params(declaration: RBS::AST::Declarations::Module, pathname: Pathname).void }
|
67
|
-
def handle_module_declaration(declaration, pathname)
|
50
|
+
def handle_class_or_module_declaration(declaration, pathname)
|
68
51
|
nesting = [declaration.name.name.to_s]
|
69
52
|
file_path = pathname.to_s
|
70
53
|
location = to_ruby_indexer_location(declaration.location)
|
71
54
|
comments = Array(declaration.comment&.string)
|
72
|
-
|
73
|
-
|
74
|
-
|
55
|
+
entry = if declaration.is_a?(RBS::AST::Declarations::Class)
|
56
|
+
parent_class = declaration.super_class&.name&.name&.to_s
|
57
|
+
Entry::Class.new(nesting, file_path, location, location, comments, parent_class)
|
58
|
+
else
|
59
|
+
Entry::Module.new(nesting, file_path, location, location, comments)
|
60
|
+
end
|
61
|
+
add_declaration_mixins_to_entry(declaration, entry)
|
62
|
+
@index.add(entry)
|
75
63
|
declaration.members.each do |member|
|
76
64
|
next unless member.is_a?(RBS::AST::Members::MethodDefinition)
|
77
65
|
|
78
|
-
handle_method(member,
|
66
|
+
handle_method(member, entry)
|
79
67
|
end
|
80
68
|
end
|
81
69
|
|
@@ -546,5 +546,43 @@ module RubyIndexer
|
|
546
546
|
assert_equal(7, name_location.start_column)
|
547
547
|
assert_equal(10, name_location.end_column)
|
548
548
|
end
|
549
|
+
|
550
|
+
def test_indexing_namespaces_inside_top_level_references
|
551
|
+
index(<<~RUBY)
|
552
|
+
module ::Foo
|
553
|
+
class Bar
|
554
|
+
end
|
555
|
+
end
|
556
|
+
RUBY
|
557
|
+
|
558
|
+
# We want to explicitly verify that we didn't introduce the leading `::` by accident, but `Index#[]` deletes the
|
559
|
+
# prefix when we use `refute_entry`
|
560
|
+
entries = @index.instance_variable_get(:@entries)
|
561
|
+
refute(entries.key?("::Foo"))
|
562
|
+
refute(entries.key?("::Foo::Bar"))
|
563
|
+
assert_entry("Foo", Entry::Module, "/fake/path/foo.rb:0-0:3-3")
|
564
|
+
assert_entry("Foo::Bar", Entry::Class, "/fake/path/foo.rb:1-2:2-5")
|
565
|
+
end
|
566
|
+
|
567
|
+
def test_indexing_namespaces_inside_nested_top_level_references
|
568
|
+
index(<<~RUBY)
|
569
|
+
class Baz
|
570
|
+
module ::Foo
|
571
|
+
class Bar
|
572
|
+
end
|
573
|
+
|
574
|
+
class ::Qux
|
575
|
+
end
|
576
|
+
end
|
577
|
+
end
|
578
|
+
RUBY
|
579
|
+
|
580
|
+
refute_entry("Baz::Foo")
|
581
|
+
refute_entry("Baz::Foo::Bar")
|
582
|
+
assert_entry("Baz", Entry::Class, "/fake/path/foo.rb:0-0:8-3")
|
583
|
+
assert_entry("Foo", Entry::Module, "/fake/path/foo.rb:1-2:7-5")
|
584
|
+
assert_entry("Foo::Bar", Entry::Class, "/fake/path/foo.rb:2-4:3-7")
|
585
|
+
assert_entry("Qux", Entry::Class, "/fake/path/foo.rb:5-4:6-7")
|
586
|
+
end
|
549
587
|
end
|
550
588
|
end
|
@@ -1542,6 +1542,21 @@ module RubyIndexer
|
|
1542
1542
|
assert_empty(@index.method_completion_candidates("bar", "Foo"))
|
1543
1543
|
end
|
1544
1544
|
|
1545
|
+
def test_first_unqualified_const
|
1546
|
+
index(<<~RUBY)
|
1547
|
+
module Foo
|
1548
|
+
class Bar; end
|
1549
|
+
end
|
1550
|
+
|
1551
|
+
module Baz
|
1552
|
+
class Bar; end
|
1553
|
+
end
|
1554
|
+
RUBY
|
1555
|
+
|
1556
|
+
entry = T.must(@index.first_unqualified_const("Bar")&.first)
|
1557
|
+
assert_equal("Foo::Bar", entry.name)
|
1558
|
+
end
|
1559
|
+
|
1545
1560
|
def test_completion_does_not_duplicate_overridden_methods
|
1546
1561
|
index(<<~RUBY)
|
1547
1562
|
class Foo
|
data/lib/ruby_lsp/base_server.rb
CHANGED
@@ -53,7 +53,7 @@ module RubyLsp
|
|
53
53
|
|
54
54
|
# We don't want to try to parse documents on text synchronization notifications
|
55
55
|
@store.get(parsed_uri).parse unless method.start_with?("textDocument/did")
|
56
|
-
rescue
|
56
|
+
rescue Store::NonExistingDocumentError
|
57
57
|
# If we receive a request for a file that no longer exists, we don't want to fail
|
58
58
|
end
|
59
59
|
end
|
data/lib/ruby_lsp/document.rb
CHANGED
@@ -10,6 +10,16 @@ module RubyLsp
|
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
13
|
+
class SorbetLevel < T::Enum
|
14
|
+
enums do
|
15
|
+
None = new("none")
|
16
|
+
Ignore = new("ignore")
|
17
|
+
False = new("false")
|
18
|
+
True = new("true")
|
19
|
+
Strict = new("strict")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
13
23
|
extend T::Sig
|
14
24
|
extend T::Helpers
|
15
25
|
|
@@ -213,10 +223,23 @@ module RubyLsp
|
|
213
223
|
NodeContext.new(closest, parent, nesting_nodes, call_node)
|
214
224
|
end
|
215
225
|
|
216
|
-
sig { returns(
|
217
|
-
def
|
218
|
-
parse_result.magic_comments.
|
219
|
-
comment.key == "typed"
|
226
|
+
sig { returns(SorbetLevel) }
|
227
|
+
def sorbet_level
|
228
|
+
sigil = parse_result.magic_comments.find do |comment|
|
229
|
+
comment.key == "typed"
|
230
|
+
end&.value
|
231
|
+
|
232
|
+
case sigil
|
233
|
+
when "ignore"
|
234
|
+
SorbetLevel::Ignore
|
235
|
+
when "false"
|
236
|
+
SorbetLevel::False
|
237
|
+
when "true"
|
238
|
+
SorbetLevel::True
|
239
|
+
when "strict", "strong"
|
240
|
+
SorbetLevel::Strict
|
241
|
+
else
|
242
|
+
SorbetLevel::None
|
220
243
|
end
|
221
244
|
end
|
222
245
|
|
@@ -36,10 +36,10 @@ module RubyLsp
|
|
36
36
|
@test_library = T.let("minitest", String)
|
37
37
|
@has_type_checker = T.let(true, T::Boolean)
|
38
38
|
@index = T.let(RubyIndexer::Index.new, RubyIndexer::Index)
|
39
|
-
@type_inferrer = T.let(TypeInferrer.new(@index), TypeInferrer)
|
40
39
|
@supported_formatters = T.let({}, T::Hash[String, Requests::Support::Formatter])
|
41
40
|
@supports_watching_files = T.let(false, T::Boolean)
|
42
41
|
@experimental_features = T.let(false, T::Boolean)
|
42
|
+
@type_inferrer = T.let(TypeInferrer.new(@index, @experimental_features), TypeInferrer)
|
43
43
|
end
|
44
44
|
|
45
45
|
sig { params(identifier: String, instance: Requests::Support::Formatter).void }
|
@@ -90,6 +90,7 @@ module RubyLsp
|
|
90
90
|
end
|
91
91
|
|
92
92
|
@experimental_features = options.dig(:initializationOptions, :experimentalFeaturesEnabled) || false
|
93
|
+
@type_inferrer.experimental_features = @experimental_features
|
93
94
|
end
|
94
95
|
|
95
96
|
sig { returns(String) }
|
@@ -7,12 +7,56 @@ module RubyLsp
|
|
7
7
|
extend T::Sig
|
8
8
|
include Requests::Support::Common
|
9
9
|
|
10
|
+
KEYWORDS = [
|
11
|
+
"alias",
|
12
|
+
"and",
|
13
|
+
"begin",
|
14
|
+
"BEGIN",
|
15
|
+
"break",
|
16
|
+
"case",
|
17
|
+
"class",
|
18
|
+
"def",
|
19
|
+
"defined?",
|
20
|
+
"do",
|
21
|
+
"else",
|
22
|
+
"elsif",
|
23
|
+
"end",
|
24
|
+
"END",
|
25
|
+
"ensure",
|
26
|
+
"false",
|
27
|
+
"for",
|
28
|
+
"if",
|
29
|
+
"in",
|
30
|
+
"module",
|
31
|
+
"next",
|
32
|
+
"nil",
|
33
|
+
"not",
|
34
|
+
"or",
|
35
|
+
"redo",
|
36
|
+
"rescue",
|
37
|
+
"retry",
|
38
|
+
"return",
|
39
|
+
"self",
|
40
|
+
"super",
|
41
|
+
"then",
|
42
|
+
"true",
|
43
|
+
"undef",
|
44
|
+
"unless",
|
45
|
+
"until",
|
46
|
+
"when",
|
47
|
+
"while",
|
48
|
+
"yield",
|
49
|
+
"__ENCODING__",
|
50
|
+
"__FILE__",
|
51
|
+
"__LINE__",
|
52
|
+
].freeze
|
53
|
+
|
10
54
|
sig do
|
11
55
|
params(
|
12
56
|
response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::CompletionItem],
|
13
57
|
global_state: GlobalState,
|
14
58
|
node_context: NodeContext,
|
15
|
-
|
59
|
+
sorbet_level: Document::SorbetLevel,
|
16
60
|
dispatcher: Prism::Dispatcher,
|
17
61
|
uri: URI::Generic,
|
18
62
|
trigger_character: T.nilable(String),
|
@@ -22,7 +66,7 @@ module RubyLsp
|
|
22
66
|
response_builder,
|
23
67
|
global_state,
|
24
68
|
node_context,
|
25
|
-
|
69
|
+
sorbet_level,
|
26
70
|
dispatcher,
|
27
71
|
uri,
|
28
72
|
trigger_character
|
@@ -32,7 +76,7 @@ module RubyLsp
|
|
32
76
|
@index = T.let(global_state.index, RubyIndexer::Index)
|
33
77
|
@type_inferrer = T.let(global_state.type_inferrer, TypeInferrer)
|
34
78
|
@node_context = node_context
|
35
|
-
@
|
79
|
+
@sorbet_level = sorbet_level
|
36
80
|
@uri = uri
|
37
81
|
@trigger_character = trigger_character
|
38
82
|
|
@@ -53,7 +97,9 @@ module RubyLsp
|
|
53
97
|
# Handle completion on regular constant references (e.g. `Bar`)
|
54
98
|
sig { params(node: Prism::ConstantReadNode).void }
|
55
99
|
def on_constant_read_node_enter(node)
|
56
|
-
|
100
|
+
# The only scenario where Sorbet doesn't provide constant completion is on ignored files. Even if the file has
|
101
|
+
# no sigil, Sorbet will still provide completion for constants
|
102
|
+
return if @sorbet_level != Document::SorbetLevel::Ignore
|
57
103
|
|
58
104
|
name = constant_name(node)
|
59
105
|
return if name.nil?
|
@@ -74,7 +120,9 @@ module RubyLsp
|
|
74
120
|
# Handle completion on namespaced constant references (e.g. `Foo::Bar`)
|
75
121
|
sig { params(node: Prism::ConstantPathNode).void }
|
76
122
|
def on_constant_path_node_enter(node)
|
77
|
-
|
123
|
+
# The only scenario where Sorbet doesn't provide constant completion is on ignored files. Even if the file has
|
124
|
+
# no sigil, Sorbet will still provide completion for constants
|
125
|
+
return if @sorbet_level != Document::SorbetLevel::Ignore
|
78
126
|
|
79
127
|
name = constant_name(node)
|
80
128
|
return if name.nil?
|
@@ -84,28 +132,32 @@ module RubyLsp
|
|
84
132
|
|
85
133
|
sig { params(node: Prism::CallNode).void }
|
86
134
|
def on_call_node_enter(node)
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
135
|
+
# The only scenario where Sorbet doesn't provide constant completion is on ignored files. Even if the file has
|
136
|
+
# no sigil, Sorbet will still provide completion for constants
|
137
|
+
if @sorbet_level == Document::SorbetLevel::Ignore
|
138
|
+
receiver = node.receiver
|
139
|
+
|
140
|
+
# When writing `Foo::`, the AST assigns a method call node (because you can use that syntax to invoke
|
141
|
+
# singleton methods). However, in addition to providing method completion, we also need to show possible
|
142
|
+
# constant completions
|
143
|
+
if (receiver.is_a?(Prism::ConstantReadNode) || receiver.is_a?(Prism::ConstantPathNode)) &&
|
144
|
+
node.call_operator == "::"
|
145
|
+
|
146
|
+
name = constant_name(receiver)
|
147
|
+
|
148
|
+
if name
|
149
|
+
start_loc = node.location
|
150
|
+
end_loc = T.must(node.call_operator_loc)
|
151
|
+
|
152
|
+
constant_path_completion(
|
153
|
+
"#{name}::",
|
154
|
+
Interface::Range.new(
|
155
|
+
start: Interface::Position.new(line: start_loc.start_line - 1, character: start_loc.start_column),
|
156
|
+
end: Interface::Position.new(line: end_loc.end_line - 1, character: end_loc.end_column),
|
157
|
+
),
|
158
|
+
)
|
159
|
+
return
|
160
|
+
end
|
109
161
|
end
|
110
162
|
end
|
111
163
|
|
@@ -118,7 +170,7 @@ module RubyLsp
|
|
118
170
|
when "require_relative"
|
119
171
|
complete_require_relative(node)
|
120
172
|
else
|
121
|
-
complete_methods(node, name)
|
173
|
+
complete_methods(node, name)
|
122
174
|
end
|
123
175
|
end
|
124
176
|
|
@@ -203,10 +255,14 @@ module RubyLsp
|
|
203
255
|
|
204
256
|
sig { params(name: String, location: Prism::Location).void }
|
205
257
|
def handle_instance_variable_completion(name, location)
|
258
|
+
# Sorbet enforces that all instance variables be declared on typed strict or higher, which means it will be able
|
259
|
+
# to provide all features for them
|
260
|
+
return if @sorbet_level == Document::SorbetLevel::Strict
|
261
|
+
|
206
262
|
type = @type_inferrer.infer_receiver_type(@node_context)
|
207
263
|
return unless type
|
208
264
|
|
209
|
-
@index.instance_variable_completion_candidates(name, type).each do |entry|
|
265
|
+
@index.instance_variable_completion_candidates(name, type.name).each do |entry|
|
210
266
|
variable_name = entry.name
|
211
267
|
|
212
268
|
label_details = Interface::CompletionItemLabelDetails.new(
|
@@ -277,7 +333,15 @@ module RubyLsp
|
|
277
333
|
|
278
334
|
sig { params(node: Prism::CallNode, name: String).void }
|
279
335
|
def complete_methods(node, name)
|
280
|
-
|
336
|
+
# If the node has a receiver, then we don't need to provide local nor keyword completions. Sorbet can provide
|
337
|
+
# local and keyword completion for any file with a Sorbet level of true or higher
|
338
|
+
if !sorbet_level_true_or_higher?(@sorbet_level) && !node.receiver
|
339
|
+
add_local_completions(node, name)
|
340
|
+
add_keyword_completions(node, name)
|
341
|
+
end
|
342
|
+
|
343
|
+
# Sorbet can provide completion for methods invoked on self on typed true or higher files
|
344
|
+
return if sorbet_level_true_or_higher?(@sorbet_level) && self_receiver?(node)
|
281
345
|
|
282
346
|
type = @type_inferrer.infer_receiver_type(@node_context)
|
283
347
|
return unless type
|
@@ -302,8 +366,11 @@ module RubyLsp
|
|
302
366
|
|
303
367
|
return unless range
|
304
368
|
|
305
|
-
|
369
|
+
guessed_type = type.name
|
370
|
+
|
371
|
+
@index.method_completion_candidates(method_name, type.name).each do |entry|
|
306
372
|
entry_name = entry.name
|
373
|
+
owner_name = entry.owner&.name
|
307
374
|
|
308
375
|
label_details = Interface::CompletionItemLabelDetails.new(
|
309
376
|
description: entry.file_name,
|
@@ -316,7 +383,8 @@ module RubyLsp
|
|
316
383
|
text_edit: Interface::TextEdit.new(range: range, new_text: entry_name),
|
317
384
|
kind: Constant::CompletionItemKind::METHOD,
|
318
385
|
data: {
|
319
|
-
owner_name:
|
386
|
+
owner_name: owner_name,
|
387
|
+
guessed_type: guessed_type,
|
320
388
|
},
|
321
389
|
)
|
322
390
|
end
|
@@ -326,11 +394,6 @@ module RubyLsp
|
|
326
394
|
|
327
395
|
sig { params(node: Prism::CallNode, name: String).void }
|
328
396
|
def add_local_completions(node, name)
|
329
|
-
return if @global_state.has_type_checker
|
330
|
-
|
331
|
-
# If the call node has a receiver, then it cannot possibly be a local variable
|
332
|
-
return if node.receiver
|
333
|
-
|
334
397
|
range = range_from_location(T.must(node.message_loc))
|
335
398
|
|
336
399
|
@node_context.locals_for_scope.each do |local|
|
@@ -349,6 +412,24 @@ module RubyLsp
|
|
349
412
|
end
|
350
413
|
end
|
351
414
|
|
415
|
+
sig { params(node: Prism::CallNode, name: String).void }
|
416
|
+
def add_keyword_completions(node, name)
|
417
|
+
range = range_from_location(T.must(node.message_loc))
|
418
|
+
|
419
|
+
KEYWORDS.each do |keyword|
|
420
|
+
next unless keyword.start_with?(name)
|
421
|
+
|
422
|
+
@response_builder << Interface::CompletionItem.new(
|
423
|
+
label: keyword,
|
424
|
+
text_edit: Interface::TextEdit.new(range: range, new_text: keyword),
|
425
|
+
kind: Constant::CompletionItemKind::KEYWORD,
|
426
|
+
data: {
|
427
|
+
skip_resolve: true,
|
428
|
+
},
|
429
|
+
)
|
430
|
+
end
|
431
|
+
end
|
432
|
+
|
352
433
|
sig { params(label: String, node: Prism::StringNode).returns(Interface::CompletionItem) }
|
353
434
|
def build_completion(label, node)
|
354
435
|
# We should use the content location as we only replace the content and not the delimiters of the string
|