ruby-lsp 0.18.4 → 0.19.0
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/core_ext/uri.rb +9 -4
- data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +6 -0
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +66 -8
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +56 -32
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +3 -2
- data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +18 -4
- data/lib/ruby_indexer/lib/ruby_indexer/reference_finder.rb +262 -0
- data/lib/ruby_indexer/ruby_indexer.rb +1 -0
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +11 -0
- data/lib/ruby_indexer/test/constant_test.rb +8 -0
- data/lib/ruby_indexer/test/enhancements_test.rb +2 -0
- data/lib/ruby_indexer/test/instance_variables_test.rb +12 -0
- data/lib/ruby_indexer/test/method_test.rb +10 -0
- data/lib/ruby_indexer/test/reference_finder_test.rb +86 -0
- data/lib/ruby_lsp/addon.rb +69 -7
- data/lib/ruby_lsp/erb_document.rb +3 -2
- data/lib/ruby_lsp/global_state.rb +8 -0
- data/lib/ruby_lsp/internal.rb +2 -0
- data/lib/ruby_lsp/listeners/completion.rb +1 -1
- data/lib/ruby_lsp/listeners/hover.rb +19 -0
- data/lib/ruby_lsp/requests/completion_resolve.rb +29 -0
- data/lib/ruby_lsp/requests/rename.rb +189 -0
- data/lib/ruby_lsp/requests/support/source_uri.rb +8 -1
- data/lib/ruby_lsp/server.rb +35 -8
- data/lib/ruby_lsp/static_docs.rb +15 -0
- data/lib/ruby_lsp/store.rb +12 -0
- data/lib/ruby_lsp/test_helper.rb +1 -1
- data/lib/ruby_lsp/type_inferrer.rb +6 -1
- data/static_docs/yield.md +81 -0
- metadata +18 -7
@@ -0,0 +1,262 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module RubyIndexer
|
5
|
+
class ReferenceFinder
|
6
|
+
extend T::Sig
|
7
|
+
|
8
|
+
class Reference
|
9
|
+
extend T::Sig
|
10
|
+
|
11
|
+
sig { returns(String) }
|
12
|
+
attr_reader :name
|
13
|
+
|
14
|
+
sig { returns(Prism::Location) }
|
15
|
+
attr_reader :location
|
16
|
+
|
17
|
+
sig { params(name: String, location: Prism::Location).void }
|
18
|
+
def initialize(name, location)
|
19
|
+
@name = name
|
20
|
+
@location = location
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
sig { returns(T::Array[Reference]) }
|
25
|
+
attr_reader :references
|
26
|
+
|
27
|
+
sig do
|
28
|
+
params(
|
29
|
+
fully_qualified_name: String,
|
30
|
+
index: RubyIndexer::Index,
|
31
|
+
dispatcher: Prism::Dispatcher,
|
32
|
+
).void
|
33
|
+
end
|
34
|
+
def initialize(fully_qualified_name, index, dispatcher)
|
35
|
+
@fully_qualified_name = fully_qualified_name
|
36
|
+
@index = index
|
37
|
+
@stack = T.let([], T::Array[String])
|
38
|
+
@references = T.let([], T::Array[Reference])
|
39
|
+
|
40
|
+
dispatcher.register(
|
41
|
+
self,
|
42
|
+
:on_class_node_enter,
|
43
|
+
:on_class_node_leave,
|
44
|
+
:on_module_node_enter,
|
45
|
+
:on_module_node_leave,
|
46
|
+
:on_singleton_class_node_enter,
|
47
|
+
:on_singleton_class_node_leave,
|
48
|
+
:on_def_node_enter,
|
49
|
+
:on_def_node_leave,
|
50
|
+
:on_multi_write_node_enter,
|
51
|
+
:on_constant_path_write_node_enter,
|
52
|
+
:on_constant_path_or_write_node_enter,
|
53
|
+
:on_constant_path_operator_write_node_enter,
|
54
|
+
:on_constant_path_and_write_node_enter,
|
55
|
+
:on_constant_or_write_node_enter,
|
56
|
+
:on_constant_path_node_enter,
|
57
|
+
:on_constant_read_node_enter,
|
58
|
+
:on_constant_write_node_enter,
|
59
|
+
:on_constant_or_write_node_enter,
|
60
|
+
:on_constant_and_write_node_enter,
|
61
|
+
:on_constant_operator_write_node_enter,
|
62
|
+
)
|
63
|
+
end
|
64
|
+
|
65
|
+
sig { params(node: Prism::ClassNode).void }
|
66
|
+
def on_class_node_enter(node)
|
67
|
+
constant_path = node.constant_path
|
68
|
+
name = constant_path.slice
|
69
|
+
nesting = actual_nesting(name)
|
70
|
+
|
71
|
+
if nesting.join("::") == @fully_qualified_name
|
72
|
+
@references << Reference.new(name, constant_path.location)
|
73
|
+
end
|
74
|
+
|
75
|
+
@stack << name
|
76
|
+
end
|
77
|
+
|
78
|
+
sig { params(node: Prism::ClassNode).void }
|
79
|
+
def on_class_node_leave(node)
|
80
|
+
@stack.pop
|
81
|
+
end
|
82
|
+
|
83
|
+
sig { params(node: Prism::ModuleNode).void }
|
84
|
+
def on_module_node_enter(node)
|
85
|
+
constant_path = node.constant_path
|
86
|
+
name = constant_path.slice
|
87
|
+
nesting = actual_nesting(name)
|
88
|
+
|
89
|
+
if nesting.join("::") == @fully_qualified_name
|
90
|
+
@references << Reference.new(name, constant_path.location)
|
91
|
+
end
|
92
|
+
|
93
|
+
@stack << name
|
94
|
+
end
|
95
|
+
|
96
|
+
sig { params(node: Prism::ModuleNode).void }
|
97
|
+
def on_module_node_leave(node)
|
98
|
+
@stack.pop
|
99
|
+
end
|
100
|
+
|
101
|
+
sig { params(node: Prism::SingletonClassNode).void }
|
102
|
+
def on_singleton_class_node_enter(node)
|
103
|
+
expression = node.expression
|
104
|
+
return unless expression.is_a?(Prism::SelfNode)
|
105
|
+
|
106
|
+
@stack << "<Class:#{@stack.last}>"
|
107
|
+
end
|
108
|
+
|
109
|
+
sig { params(node: Prism::SingletonClassNode).void }
|
110
|
+
def on_singleton_class_node_leave(node)
|
111
|
+
@stack.pop
|
112
|
+
end
|
113
|
+
|
114
|
+
sig { params(node: Prism::ConstantPathNode).void }
|
115
|
+
def on_constant_path_node_enter(node)
|
116
|
+
name = constant_name(node)
|
117
|
+
return unless name
|
118
|
+
|
119
|
+
collect_constant_references(name, node.location)
|
120
|
+
end
|
121
|
+
|
122
|
+
sig { params(node: Prism::ConstantReadNode).void }
|
123
|
+
def on_constant_read_node_enter(node)
|
124
|
+
name = constant_name(node)
|
125
|
+
return unless name
|
126
|
+
|
127
|
+
collect_constant_references(name, node.location)
|
128
|
+
end
|
129
|
+
|
130
|
+
sig { params(node: Prism::MultiWriteNode).void }
|
131
|
+
def on_multi_write_node_enter(node)
|
132
|
+
[*node.lefts, *node.rest, *node.rights].each do |target|
|
133
|
+
case target
|
134
|
+
when Prism::ConstantTargetNode, Prism::ConstantPathTargetNode
|
135
|
+
collect_constant_references(target.name.to_s, target.location)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
sig { params(node: Prism::ConstantPathWriteNode).void }
|
141
|
+
def on_constant_path_write_node_enter(node)
|
142
|
+
target = node.target
|
143
|
+
return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode)
|
144
|
+
|
145
|
+
name = constant_name(target)
|
146
|
+
return unless name
|
147
|
+
|
148
|
+
collect_constant_references(name, target.location)
|
149
|
+
end
|
150
|
+
|
151
|
+
sig { params(node: Prism::ConstantPathOrWriteNode).void }
|
152
|
+
def on_constant_path_or_write_node_enter(node)
|
153
|
+
target = node.target
|
154
|
+
return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode)
|
155
|
+
|
156
|
+
name = constant_name(target)
|
157
|
+
return unless name
|
158
|
+
|
159
|
+
collect_constant_references(name, target.location)
|
160
|
+
end
|
161
|
+
|
162
|
+
sig { params(node: Prism::ConstantPathOperatorWriteNode).void }
|
163
|
+
def on_constant_path_operator_write_node_enter(node)
|
164
|
+
target = node.target
|
165
|
+
return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode)
|
166
|
+
|
167
|
+
name = constant_name(target)
|
168
|
+
return unless name
|
169
|
+
|
170
|
+
collect_constant_references(name, target.location)
|
171
|
+
end
|
172
|
+
|
173
|
+
sig { params(node: Prism::ConstantPathAndWriteNode).void }
|
174
|
+
def on_constant_path_and_write_node_enter(node)
|
175
|
+
target = node.target
|
176
|
+
return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode)
|
177
|
+
|
178
|
+
name = constant_name(target)
|
179
|
+
return unless name
|
180
|
+
|
181
|
+
collect_constant_references(name, target.location)
|
182
|
+
end
|
183
|
+
|
184
|
+
sig { params(node: Prism::ConstantWriteNode).void }
|
185
|
+
def on_constant_write_node_enter(node)
|
186
|
+
collect_constant_references(node.name.to_s, node.name_loc)
|
187
|
+
end
|
188
|
+
|
189
|
+
sig { params(node: Prism::ConstantOrWriteNode).void }
|
190
|
+
def on_constant_or_write_node_enter(node)
|
191
|
+
collect_constant_references(node.name.to_s, node.name_loc)
|
192
|
+
end
|
193
|
+
|
194
|
+
sig { params(node: Prism::ConstantAndWriteNode).void }
|
195
|
+
def on_constant_and_write_node_enter(node)
|
196
|
+
collect_constant_references(node.name.to_s, node.name_loc)
|
197
|
+
end
|
198
|
+
|
199
|
+
sig { params(node: Prism::ConstantOperatorWriteNode).void }
|
200
|
+
def on_constant_operator_write_node_enter(node)
|
201
|
+
collect_constant_references(node.name.to_s, node.name_loc)
|
202
|
+
end
|
203
|
+
|
204
|
+
sig { params(node: Prism::DefNode).void }
|
205
|
+
def on_def_node_enter(node)
|
206
|
+
if node.receiver.is_a?(Prism::SelfNode)
|
207
|
+
@stack << "<Class:#{@stack.last}>"
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
sig { params(node: Prism::DefNode).void }
|
212
|
+
def on_def_node_leave(node)
|
213
|
+
if node.receiver.is_a?(Prism::SelfNode)
|
214
|
+
@stack.pop
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
private
|
219
|
+
|
220
|
+
sig { params(name: String).returns(T::Array[String]) }
|
221
|
+
def actual_nesting(name)
|
222
|
+
nesting = @stack + [name]
|
223
|
+
corrected_nesting = []
|
224
|
+
|
225
|
+
nesting.reverse_each do |name|
|
226
|
+
corrected_nesting.prepend(name.delete_prefix("::"))
|
227
|
+
|
228
|
+
break if name.start_with?("::")
|
229
|
+
end
|
230
|
+
|
231
|
+
corrected_nesting
|
232
|
+
end
|
233
|
+
|
234
|
+
sig { params(name: String, location: Prism::Location).void }
|
235
|
+
def collect_constant_references(name, location)
|
236
|
+
entries = @index.resolve(name, @stack)
|
237
|
+
return unless entries
|
238
|
+
|
239
|
+
entries.each do |entry|
|
240
|
+
next unless entry.name == @fully_qualified_name
|
241
|
+
|
242
|
+
@references << Reference.new(name, location)
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
sig do
|
247
|
+
params(
|
248
|
+
node: T.any(
|
249
|
+
Prism::ConstantPathNode,
|
250
|
+
Prism::ConstantReadNode,
|
251
|
+
Prism::ConstantPathTargetNode,
|
252
|
+
),
|
253
|
+
).returns(T.nilable(String))
|
254
|
+
end
|
255
|
+
def constant_name(node)
|
256
|
+
node.full_name
|
257
|
+
rescue Prism::ConstantPathNode::DynamicPartsInConstantPathError,
|
258
|
+
Prism::ConstantPathNode::MissingNodesInConstantPathError
|
259
|
+
nil
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
@@ -6,6 +6,7 @@ require "did_you_mean"
|
|
6
6
|
|
7
7
|
require "ruby_indexer/lib/ruby_indexer/indexable_path"
|
8
8
|
require "ruby_indexer/lib/ruby_indexer/declaration_listener"
|
9
|
+
require "ruby_indexer/lib/ruby_indexer/reference_finder"
|
9
10
|
require "ruby_indexer/lib/ruby_indexer/enhancement"
|
10
11
|
require "ruby_indexer/lib/ruby_indexer/index"
|
11
12
|
require "ruby_indexer/lib/ruby_indexer/entry"
|
@@ -159,6 +159,17 @@ module RubyIndexer
|
|
159
159
|
assert_entry("Foo::Bar", Entry::Module, "/fake/path/foo.rb:4-2:5-5")
|
160
160
|
end
|
161
161
|
|
162
|
+
def test_nested_modules_and_classes_with_multibyte_characters
|
163
|
+
index(<<~RUBY)
|
164
|
+
module A動物
|
165
|
+
class Bねこ; end
|
166
|
+
end
|
167
|
+
RUBY
|
168
|
+
|
169
|
+
assert_entry("A動物", Entry::Module, "/fake/path/foo.rb:0-0:2-3")
|
170
|
+
assert_entry("A動物::Bねこ", Entry::Class, "/fake/path/foo.rb:1-2:1-16")
|
171
|
+
end
|
172
|
+
|
162
173
|
def test_nested_modules_and_classes
|
163
174
|
index(<<~RUBY)
|
164
175
|
module Foo
|
@@ -21,6 +21,14 @@ module RubyIndexer
|
|
21
21
|
assert_entry("BAR", Entry::Constant, "/fake/path/foo.rb:6-0:6-7")
|
22
22
|
end
|
23
23
|
|
24
|
+
def test_constant_with_multibyte_characters
|
25
|
+
index(<<~RUBY)
|
26
|
+
CONST_💎 = "Ruby"
|
27
|
+
RUBY
|
28
|
+
|
29
|
+
assert_entry("CONST_💎", Entry::Constant, "/fake/path/foo.rb:0-0:0-16")
|
30
|
+
end
|
31
|
+
|
24
32
|
def test_constant_or_writes
|
25
33
|
index(<<~RUBY)
|
26
34
|
FOO ||= 1
|
@@ -39,6 +39,7 @@ module RubyIndexer
|
|
39
39
|
location,
|
40
40
|
location,
|
41
41
|
nil,
|
42
|
+
index.configuration.encoding,
|
42
43
|
[Entry::Signature.new([Entry::RequiredParameter.new(name: :a)])],
|
43
44
|
Entry::Visibility::PUBLIC,
|
44
45
|
owner,
|
@@ -121,6 +122,7 @@ module RubyIndexer
|
|
121
122
|
location,
|
122
123
|
location,
|
123
124
|
nil,
|
125
|
+
index.configuration.encoding,
|
124
126
|
[],
|
125
127
|
Entry::Visibility::PUBLIC,
|
126
128
|
owner,
|
@@ -25,6 +25,18 @@ module RubyIndexer
|
|
25
25
|
assert_equal("Foo::Bar", owner.name)
|
26
26
|
end
|
27
27
|
|
28
|
+
def test_instance_variable_with_multibyte_characters
|
29
|
+
index(<<~RUBY)
|
30
|
+
class Foo
|
31
|
+
def initialize
|
32
|
+
@あ = 1
|
33
|
+
end
|
34
|
+
end
|
35
|
+
RUBY
|
36
|
+
|
37
|
+
assert_entry("@あ", Entry::InstanceVariable, "/fake/path/foo.rb:2-4:2-6")
|
38
|
+
end
|
39
|
+
|
28
40
|
def test_instance_variable_and_write
|
29
41
|
index(<<~RUBY)
|
30
42
|
module Foo
|
@@ -27,6 +27,16 @@ module RubyIndexer
|
|
27
27
|
assert_entry("bar", Entry::Method, "/fake/path/foo.rb:1-2:2-5")
|
28
28
|
end
|
29
29
|
|
30
|
+
def test_method_with_multibyte_characters
|
31
|
+
index(<<~RUBY)
|
32
|
+
class Foo
|
33
|
+
def こんにちは; end
|
34
|
+
end
|
35
|
+
RUBY
|
36
|
+
|
37
|
+
assert_entry("こんにちは", Entry::Method, "/fake/path/foo.rb:1-2:1-16")
|
38
|
+
end
|
39
|
+
|
30
40
|
def test_singleton_method_using_self_receiver
|
31
41
|
index(<<~RUBY)
|
32
42
|
class Foo
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "test_helper"
|
5
|
+
|
6
|
+
module RubyIndexer
|
7
|
+
class ReferenceFinderTest < Minitest::Test
|
8
|
+
def test_finds_constant_references
|
9
|
+
refs = find_references("Foo::Bar", <<~RUBY)
|
10
|
+
module Foo
|
11
|
+
class Bar
|
12
|
+
end
|
13
|
+
|
14
|
+
Bar
|
15
|
+
end
|
16
|
+
|
17
|
+
Foo::Bar
|
18
|
+
RUBY
|
19
|
+
|
20
|
+
assert_equal("Bar", refs[0].name)
|
21
|
+
assert_equal(2, refs[0].location.start_line)
|
22
|
+
|
23
|
+
assert_equal("Bar", refs[1].name)
|
24
|
+
assert_equal(5, refs[1].location.start_line)
|
25
|
+
|
26
|
+
assert_equal("Foo::Bar", refs[2].name)
|
27
|
+
assert_equal(8, refs[2].location.start_line)
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_finds_constant_references_inside_singleton_contexts
|
31
|
+
refs = find_references("Foo::<Class:Foo>::Bar", <<~RUBY)
|
32
|
+
class Foo
|
33
|
+
class << self
|
34
|
+
class Bar
|
35
|
+
end
|
36
|
+
|
37
|
+
Bar
|
38
|
+
end
|
39
|
+
end
|
40
|
+
RUBY
|
41
|
+
|
42
|
+
assert_equal("Bar", refs[0].name)
|
43
|
+
assert_equal(3, refs[0].location.start_line)
|
44
|
+
|
45
|
+
assert_equal("Bar", refs[1].name)
|
46
|
+
assert_equal(6, refs[1].location.start_line)
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_finds_top_level_constant_references
|
50
|
+
refs = find_references("Bar", <<~RUBY)
|
51
|
+
class Bar
|
52
|
+
end
|
53
|
+
|
54
|
+
class Foo
|
55
|
+
::Bar
|
56
|
+
|
57
|
+
class << self
|
58
|
+
::Bar
|
59
|
+
end
|
60
|
+
end
|
61
|
+
RUBY
|
62
|
+
|
63
|
+
assert_equal("Bar", refs[0].name)
|
64
|
+
assert_equal(1, refs[0].location.start_line)
|
65
|
+
|
66
|
+
assert_equal("::Bar", refs[1].name)
|
67
|
+
assert_equal(5, refs[1].location.start_line)
|
68
|
+
|
69
|
+
assert_equal("::Bar", refs[2].name)
|
70
|
+
assert_equal(8, refs[2].location.start_line)
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def find_references(fully_qualified_name, source)
|
76
|
+
file_path = "/fake.rb"
|
77
|
+
index = Index.new
|
78
|
+
index.index_single(IndexablePath.new(nil, file_path), source)
|
79
|
+
parse_result = Prism.parse(source)
|
80
|
+
dispatcher = Prism::Dispatcher.new
|
81
|
+
finder = ReferenceFinder.new(fully_qualified_name, index, dispatcher)
|
82
|
+
dispatcher.visit(parse_result.value)
|
83
|
+
finder.references.uniq(&:location)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
data/lib/ruby_lsp/addon.rb
CHANGED
@@ -32,6 +32,8 @@ module RubyLsp
|
|
32
32
|
|
33
33
|
AddonNotFoundError = Class.new(StandardError)
|
34
34
|
|
35
|
+
class IncompatibleApiError < StandardError; end
|
36
|
+
|
35
37
|
class << self
|
36
38
|
extend T::Sig
|
37
39
|
|
@@ -53,13 +55,28 @@ module RubyLsp
|
|
53
55
|
|
54
56
|
# Discovers and loads all add-ons. Returns a list of errors when trying to require add-ons
|
55
57
|
sig do
|
56
|
-
params(
|
58
|
+
params(
|
59
|
+
global_state: GlobalState,
|
60
|
+
outgoing_queue: Thread::Queue,
|
61
|
+
include_project_addons: T::Boolean,
|
62
|
+
).returns(T::Array[StandardError])
|
57
63
|
end
|
58
|
-
def load_addons(global_state, outgoing_queue)
|
64
|
+
def load_addons(global_state, outgoing_queue, include_project_addons: true)
|
59
65
|
# Require all add-ons entry points, which should be placed under
|
60
|
-
# `some_gem/lib/ruby_lsp/your_gem_name/addon.rb`
|
61
|
-
|
62
|
-
|
66
|
+
# `some_gem/lib/ruby_lsp/your_gem_name/addon.rb` or in the workspace under
|
67
|
+
# `your_project/ruby_lsp/project_name/addon.rb`
|
68
|
+
addon_files = Gem.find_files("ruby_lsp/**/addon.rb")
|
69
|
+
|
70
|
+
if include_project_addons
|
71
|
+
addon_files.concat(Dir.glob(File.join(global_state.workspace_path, "**", "ruby_lsp/**/addon.rb")))
|
72
|
+
end
|
73
|
+
|
74
|
+
errors = addon_files.filter_map do |addon_path|
|
75
|
+
# Avoid requiring this file twice. This may happen if you're working on the Ruby LSP itself and at the same
|
76
|
+
# time have `ruby-lsp` installed as a vendored gem
|
77
|
+
next if File.basename(File.dirname(addon_path)) == "ruby_lsp"
|
78
|
+
|
79
|
+
require File.expand_path(addon_path)
|
63
80
|
nil
|
64
81
|
rescue => e
|
65
82
|
e
|
@@ -80,13 +97,53 @@ module RubyLsp
|
|
80
97
|
errors
|
81
98
|
end
|
82
99
|
|
83
|
-
|
84
|
-
|
100
|
+
# Get a reference to another add-on object by name and version. If an add-on exports an API that can be used by
|
101
|
+
# other add-ons, this is the way to get access to that API.
|
102
|
+
#
|
103
|
+
# Important: if the add-on is not found, AddonNotFoundError will be raised. If the add-on is found, but its
|
104
|
+
# current version does not satisfy the given version constraint, then IncompatibleApiError will be raised. It is
|
105
|
+
# the responsibility of the add-ons using this API to handle these errors appropriately.
|
106
|
+
sig { params(addon_name: String, version_constraints: String).returns(Addon) }
|
107
|
+
def get(addon_name, *version_constraints)
|
108
|
+
if version_constraints.empty?
|
109
|
+
raise IncompatibleApiError, "Must specify version constraints when accessing other add-ons"
|
110
|
+
end
|
111
|
+
|
85
112
|
addon = addons.find { |addon| addon.name == addon_name }
|
86
113
|
raise AddonNotFoundError, "Could not find add-on '#{addon_name}'" unless addon
|
87
114
|
|
115
|
+
version_object = Gem::Version.new(addon.version)
|
116
|
+
|
117
|
+
unless version_constraints.all? { |constraint| Gem::Requirement.new(constraint).satisfied_by?(version_object) }
|
118
|
+
raise IncompatibleApiError,
|
119
|
+
"Constraints #{version_constraints.inspect} is incompatible with #{addon_name} version #{addon.version}"
|
120
|
+
end
|
121
|
+
|
88
122
|
addon
|
89
123
|
end
|
124
|
+
|
125
|
+
# Depend on a specific version of the Ruby LSP. This method should only be used if the add-on is distributed in a
|
126
|
+
# gem that does not have a runtime dependency on the ruby-lsp gem. This method should be invoked at the top of the
|
127
|
+
# `addon.rb` file before defining any classes or requiring any files. For example:
|
128
|
+
#
|
129
|
+
# ```ruby
|
130
|
+
# RubyLsp::Addon.depend_on_ruby_lsp!(">= 0.18.0")
|
131
|
+
#
|
132
|
+
# module MyGem
|
133
|
+
# class MyAddon < RubyLsp::Addon
|
134
|
+
# # ...
|
135
|
+
# end
|
136
|
+
# end
|
137
|
+
# ```
|
138
|
+
sig { params(version_constraints: String).void }
|
139
|
+
def depend_on_ruby_lsp!(*version_constraints)
|
140
|
+
version_object = Gem::Version.new(RubyLsp::VERSION)
|
141
|
+
|
142
|
+
unless version_constraints.all? { |constraint| Gem::Requirement.new(constraint).satisfied_by?(version_object) }
|
143
|
+
raise IncompatibleApiError,
|
144
|
+
"Add-on is not compatible with this version of the Ruby LSP. Skipping its activation"
|
145
|
+
end
|
146
|
+
end
|
90
147
|
end
|
91
148
|
|
92
149
|
sig { void }
|
@@ -132,6 +189,11 @@ module RubyLsp
|
|
132
189
|
sig { abstract.returns(String) }
|
133
190
|
def name; end
|
134
191
|
|
192
|
+
# Add-ons should override the `version` method to return a semantic version string representing the add-on's
|
193
|
+
# version. This is used for compatibility checks
|
194
|
+
sig { abstract.returns(String) }
|
195
|
+
def version; end
|
196
|
+
|
135
197
|
# Creates a new CodeLens listener. This method is invoked on every CodeLens request
|
136
198
|
sig do
|
137
199
|
overridable.params(
|
@@ -27,8 +27,9 @@ module RubyLsp
|
|
27
27
|
scanner = ERBScanner.new(@source)
|
28
28
|
scanner.scan
|
29
29
|
@host_language_source = scanner.host_language
|
30
|
-
#
|
31
|
-
|
30
|
+
# Use partial script to avoid syntax errors in ERB files where keywords may be used without the full context in
|
31
|
+
# which they will be evaluated
|
32
|
+
@parse_result = Prism.parse(scanner.ruby, partial_script: true)
|
32
33
|
true
|
33
34
|
end
|
34
35
|
|
@@ -23,6 +23,9 @@ module RubyLsp
|
|
23
23
|
sig { returns(T::Boolean) }
|
24
24
|
attr_reader :supports_watching_files, :experimental_features, :supports_request_delegation
|
25
25
|
|
26
|
+
sig { returns(T::Array[String]) }
|
27
|
+
attr_reader :supported_resource_operations
|
28
|
+
|
26
29
|
sig { returns(TypeInferrer) }
|
27
30
|
attr_reader :type_inferrer
|
28
31
|
|
@@ -42,6 +45,7 @@ module RubyLsp
|
|
42
45
|
@type_inferrer = T.let(TypeInferrer.new(@index), TypeInferrer)
|
43
46
|
@addon_settings = T.let({}, T::Hash[String, T.untyped])
|
44
47
|
@supports_request_delegation = T.let(false, T::Boolean)
|
48
|
+
@supported_resource_operations = T.let([], T::Array[String])
|
45
49
|
end
|
46
50
|
|
47
51
|
sig { params(addon_name: String).returns(T.nilable(T::Hash[Symbol, T.untyped])) }
|
@@ -117,6 +121,7 @@ module RubyLsp
|
|
117
121
|
else
|
118
122
|
Encoding::UTF_32
|
119
123
|
end
|
124
|
+
@index.configuration.encoding = @encoding
|
120
125
|
|
121
126
|
file_watching_caps = options.dig(:capabilities, :workspace, :didChangeWatchedFiles)
|
122
127
|
if file_watching_caps&.dig(:dynamicRegistration) && file_watching_caps&.dig(:relativePatternSupport)
|
@@ -132,6 +137,9 @@ module RubyLsp
|
|
132
137
|
end
|
133
138
|
|
134
139
|
@supports_request_delegation = options.dig(:capabilities, :experimental, :requestDelegation) || false
|
140
|
+
supported_resource_operations = options.dig(:capabilities, :workspace, :workspaceEdit, :resourceOperations)
|
141
|
+
@supported_resource_operations = supported_resource_operations if supported_resource_operations
|
142
|
+
|
135
143
|
notifications
|
136
144
|
end
|
137
145
|
|
data/lib/ruby_lsp/internal.rb
CHANGED
@@ -26,6 +26,7 @@ require "ruby_lsp/base_server"
|
|
26
26
|
require "ruby_indexer/ruby_indexer"
|
27
27
|
require "core_ext/uri"
|
28
28
|
require "ruby_lsp/utils"
|
29
|
+
require "ruby_lsp/static_docs"
|
29
30
|
require "ruby_lsp/scope"
|
30
31
|
require "ruby_lsp/global_state"
|
31
32
|
require "ruby_lsp/server"
|
@@ -80,3 +81,4 @@ require "ruby_lsp/requests/show_syntax_tree"
|
|
80
81
|
require "ruby_lsp/requests/signature_help"
|
81
82
|
require "ruby_lsp/requests/type_hierarchy_supertypes"
|
82
83
|
require "ruby_lsp/requests/workspace_symbol"
|
84
|
+
require "ruby_lsp/requests/rename"
|
@@ -24,6 +24,7 @@ module RubyLsp
|
|
24
24
|
Prism::InterpolatedStringNode,
|
25
25
|
Prism::SuperNode,
|
26
26
|
Prism::ForwardingSuperNode,
|
27
|
+
Prism::YieldNode,
|
27
28
|
],
|
28
29
|
T::Array[T.class_of(Prism::Node)],
|
29
30
|
)
|
@@ -71,6 +72,7 @@ module RubyLsp
|
|
71
72
|
:on_forwarding_super_node_enter,
|
72
73
|
:on_string_node_enter,
|
73
74
|
:on_interpolated_string_node_enter,
|
75
|
+
:on_yield_node_enter,
|
74
76
|
)
|
75
77
|
end
|
76
78
|
|
@@ -166,6 +168,11 @@ module RubyLsp
|
|
166
168
|
handle_super_node_hover
|
167
169
|
end
|
168
170
|
|
171
|
+
sig { params(node: Prism::YieldNode).void }
|
172
|
+
def on_yield_node_enter(node)
|
173
|
+
handle_keyword_documentation(node.keyword)
|
174
|
+
end
|
175
|
+
|
169
176
|
private
|
170
177
|
|
171
178
|
sig { params(node: T.any(Prism::InterpolatedStringNode, Prism::StringNode)).void }
|
@@ -193,6 +200,18 @@ module RubyLsp
|
|
193
200
|
end
|
194
201
|
end
|
195
202
|
|
203
|
+
sig { params(keyword: String).void }
|
204
|
+
def handle_keyword_documentation(keyword)
|
205
|
+
content = KEYWORD_DOCS[keyword]
|
206
|
+
return unless content
|
207
|
+
|
208
|
+
doc_path = File.join(STATIC_DOCS_PATH, "#{keyword}.md")
|
209
|
+
|
210
|
+
@response_builder.push("```ruby\n#{keyword}\n```", category: :title)
|
211
|
+
@response_builder.push("[Read more](#{doc_path})", category: :links)
|
212
|
+
@response_builder.push(content, category: :documentation)
|
213
|
+
end
|
214
|
+
|
196
215
|
sig { void }
|
197
216
|
def handle_super_node_hover
|
198
217
|
# Sorbet can handle super hover on typed true or higher
|