ruby-lsp 0.10.0 → 0.11.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +4 -4
- data/VERSION +1 -1
- data/exe/ruby-lsp-check +1 -1
- data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +35 -5
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +141 -5
- data/lib/ruby_indexer/lib/ruby_indexer/visitor.rb +66 -18
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +39 -16
- data/lib/ruby_indexer/test/configuration_test.rb +2 -0
- data/lib/ruby_indexer/test/constant_test.rb +213 -11
- data/lib/ruby_indexer/test/index_test.rb +20 -0
- data/lib/ruby_lsp/{extension.rb → addon.rb} +27 -25
- data/lib/ruby_lsp/check_docs.rb +7 -8
- data/lib/ruby_lsp/document.rb +35 -38
- data/lib/ruby_lsp/event_emitter.rb +239 -77
- data/lib/ruby_lsp/executor.rb +45 -55
- data/lib/ruby_lsp/internal.rb +2 -3
- data/lib/ruby_lsp/listener.rb +8 -7
- data/lib/ruby_lsp/parameter_scope.rb +33 -0
- data/lib/ruby_lsp/requests/base_request.rb +3 -3
- data/lib/ruby_lsp/requests/code_action_resolve.rb +14 -14
- data/lib/ruby_lsp/requests/code_lens.rb +39 -63
- data/lib/ruby_lsp/requests/completion.rb +54 -32
- data/lib/ruby_lsp/requests/definition.rb +30 -27
- data/lib/ruby_lsp/requests/diagnostics.rb +26 -3
- data/lib/ruby_lsp/requests/document_highlight.rb +18 -19
- data/lib/ruby_lsp/requests/document_link.rb +53 -10
- data/lib/ruby_lsp/requests/document_symbol.rb +82 -75
- data/lib/ruby_lsp/requests/folding_ranges.rb +199 -222
- data/lib/ruby_lsp/requests/formatting.rb +5 -6
- data/lib/ruby_lsp/requests/hover.rb +33 -22
- data/lib/ruby_lsp/requests/inlay_hints.rb +2 -3
- data/lib/ruby_lsp/requests/selection_ranges.rb +65 -40
- data/lib/ruby_lsp/requests/semantic_highlighting.rb +187 -145
- data/lib/ruby_lsp/requests/show_syntax_tree.rb +3 -4
- data/lib/ruby_lsp/requests/support/annotation.rb +18 -17
- data/lib/ruby_lsp/requests/support/common.rb +17 -26
- data/lib/ruby_lsp/requests/support/dependency_detector.rb +67 -42
- data/lib/ruby_lsp/requests/support/highlight_target.rb +64 -45
- data/lib/ruby_lsp/requests/support/rubocop_runner.rb +9 -4
- data/lib/ruby_lsp/requests/support/selection_range.rb +5 -4
- data/lib/ruby_lsp/requests/support/sorbet.rb +2 -57
- data/lib/ruby_lsp/requests/support/syntax_tree_formatting_runner.rb +7 -1
- data/lib/ruby_lsp/requests/workspace_symbol.rb +4 -1
- data/lib/ruby_lsp/server.rb +6 -44
- data/lib/ruby_lsp/setup_bundler.rb +22 -11
- data/lib/ruby_lsp/utils.rb +2 -12
- metadata +11 -30
@@ -14,8 +14,8 @@ module RubyIndexer
|
|
14
14
|
end
|
15
15
|
RUBY
|
16
16
|
|
17
|
-
assert_entry("FOO", Index::Entry::Constant, "/fake/path/foo.rb:0-0:0-
|
18
|
-
assert_entry("Bar::FOO", Index::Entry::Constant, "/fake/path/foo.rb:3-2:3-
|
17
|
+
assert_entry("FOO", Index::Entry::Constant, "/fake/path/foo.rb:0-0:0-7")
|
18
|
+
assert_entry("Bar::FOO", Index::Entry::Constant, "/fake/path/foo.rb:3-2:3-9")
|
19
19
|
end
|
20
20
|
|
21
21
|
def test_constant_or_writes
|
@@ -27,8 +27,8 @@ module RubyIndexer
|
|
27
27
|
end
|
28
28
|
RUBY
|
29
29
|
|
30
|
-
assert_entry("FOO", Index::Entry::Constant, "/fake/path/foo.rb:0-0:0-
|
31
|
-
assert_entry("Bar::FOO", Index::Entry::Constant, "/fake/path/foo.rb:3-2:3-
|
30
|
+
assert_entry("FOO", Index::Entry::Constant, "/fake/path/foo.rb:0-0:0-9")
|
31
|
+
assert_entry("Bar::FOO", Index::Entry::Constant, "/fake/path/foo.rb:3-2:3-11")
|
32
32
|
end
|
33
33
|
|
34
34
|
def test_constant_path_writes
|
@@ -45,10 +45,10 @@ module RubyIndexer
|
|
45
45
|
A::BAZ = 1
|
46
46
|
RUBY
|
47
47
|
|
48
|
-
assert_entry("A::FOO", Index::Entry::Constant, "/fake/path/foo.rb:1-2:1-
|
49
|
-
assert_entry("BAR", Index::Entry::Constant, "/fake/path/foo.rb:2-2:2-
|
50
|
-
assert_entry("A::B::FOO", Index::Entry::Constant, "/fake/path/foo.rb:5-4:5-
|
51
|
-
assert_entry("A::BAZ", Index::Entry::Constant, "/fake/path/foo.rb:9-0:9-
|
48
|
+
assert_entry("A::FOO", Index::Entry::Constant, "/fake/path/foo.rb:1-2:1-9")
|
49
|
+
assert_entry("BAR", Index::Entry::Constant, "/fake/path/foo.rb:2-2:2-11")
|
50
|
+
assert_entry("A::B::FOO", Index::Entry::Constant, "/fake/path/foo.rb:5-4:5-11")
|
51
|
+
assert_entry("A::BAZ", Index::Entry::Constant, "/fake/path/foo.rb:9-0:9-10")
|
52
52
|
end
|
53
53
|
|
54
54
|
def test_constant_path_or_writes
|
@@ -61,9 +61,9 @@ module RubyIndexer
|
|
61
61
|
A::BAZ ||= 1
|
62
62
|
RUBY
|
63
63
|
|
64
|
-
assert_entry("A::FOO", Index::Entry::Constant, "/fake/path/foo.rb:1-2:1-
|
65
|
-
assert_entry("BAR", Index::Entry::Constant, "/fake/path/foo.rb:2-2:2-
|
66
|
-
assert_entry("A::BAZ", Index::Entry::Constant, "/fake/path/foo.rb:5-0:5-
|
64
|
+
assert_entry("A::FOO", Index::Entry::Constant, "/fake/path/foo.rb:1-2:1-11")
|
65
|
+
assert_entry("BAR", Index::Entry::Constant, "/fake/path/foo.rb:2-2:2-13")
|
66
|
+
assert_entry("A::BAZ", Index::Entry::Constant, "/fake/path/foo.rb:5-0:5-12")
|
67
67
|
end
|
68
68
|
|
69
69
|
def test_comments_for_constants
|
@@ -104,5 +104,207 @@ module RubyIndexer
|
|
104
104
|
|
105
105
|
assert_no_entry
|
106
106
|
end
|
107
|
+
|
108
|
+
def test_private_constant_indexing
|
109
|
+
index(<<~RUBY)
|
110
|
+
class A
|
111
|
+
B = 1
|
112
|
+
private_constant(:B)
|
113
|
+
|
114
|
+
C = 2
|
115
|
+
private_constant("C")
|
116
|
+
|
117
|
+
D = 1
|
118
|
+
end
|
119
|
+
RUBY
|
120
|
+
|
121
|
+
b_const = @index["A::B"].first
|
122
|
+
assert_equal(:private, b_const.visibility)
|
123
|
+
|
124
|
+
c_const = @index["A::C"].first
|
125
|
+
assert_equal(:private, c_const.visibility)
|
126
|
+
|
127
|
+
d_const = @index["A::D"].first
|
128
|
+
assert_equal(:public, d_const.visibility)
|
129
|
+
end
|
130
|
+
|
131
|
+
def test_marking_constants_as_private_reopening_namespaces
|
132
|
+
index(<<~RUBY)
|
133
|
+
module A
|
134
|
+
module B
|
135
|
+
CONST_A = 1
|
136
|
+
private_constant(:CONST_A)
|
137
|
+
|
138
|
+
CONST_B = 2
|
139
|
+
CONST_C = 3
|
140
|
+
end
|
141
|
+
|
142
|
+
module B
|
143
|
+
private_constant(:CONST_B)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
module A
|
148
|
+
module B
|
149
|
+
private_constant(:CONST_C)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
RUBY
|
153
|
+
|
154
|
+
a_const = @index["A::B::CONST_A"].first
|
155
|
+
assert_equal(:private, a_const.visibility)
|
156
|
+
|
157
|
+
b_const = @index["A::B::CONST_B"].first
|
158
|
+
assert_equal(:private, b_const.visibility)
|
159
|
+
|
160
|
+
c_const = @index["A::B::CONST_C"].first
|
161
|
+
assert_equal(:private, c_const.visibility)
|
162
|
+
end
|
163
|
+
|
164
|
+
def test_marking_constants_as_private_with_receiver
|
165
|
+
index(<<~RUBY)
|
166
|
+
module A
|
167
|
+
module B
|
168
|
+
CONST_A = 1
|
169
|
+
CONST_B = 2
|
170
|
+
end
|
171
|
+
|
172
|
+
B.private_constant(:CONST_A)
|
173
|
+
end
|
174
|
+
|
175
|
+
A::B.private_constant(:CONST_B)
|
176
|
+
RUBY
|
177
|
+
|
178
|
+
a_const = @index["A::B::CONST_A"].first
|
179
|
+
assert_equal(:private, a_const.visibility)
|
180
|
+
|
181
|
+
b_const = @index["A::B::CONST_B"].first
|
182
|
+
assert_equal(:private, b_const.visibility)
|
183
|
+
end
|
184
|
+
|
185
|
+
def test_indexing_constant_aliases
|
186
|
+
index(<<~RUBY)
|
187
|
+
module A
|
188
|
+
module B
|
189
|
+
module C
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
FIRST = B::C
|
194
|
+
end
|
195
|
+
|
196
|
+
SECOND = A::FIRST
|
197
|
+
RUBY
|
198
|
+
|
199
|
+
unresolve_entry = @index["A::FIRST"].first
|
200
|
+
assert_instance_of(Index::Entry::UnresolvedAlias, unresolve_entry)
|
201
|
+
assert_equal(["A"], unresolve_entry.nesting)
|
202
|
+
assert_equal("B::C", unresolve_entry.target)
|
203
|
+
|
204
|
+
resolved_entry = @index.resolve("A::FIRST", []).first
|
205
|
+
assert_instance_of(Index::Entry::Alias, resolved_entry)
|
206
|
+
assert_equal("A::B::C", resolved_entry.target)
|
207
|
+
end
|
208
|
+
|
209
|
+
def test_aliasing_namespaces
|
210
|
+
index(<<~RUBY)
|
211
|
+
module A
|
212
|
+
module B
|
213
|
+
module C
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
ALIAS = B
|
218
|
+
end
|
219
|
+
|
220
|
+
module Other
|
221
|
+
ONE_MORE = A::ALIAS
|
222
|
+
end
|
223
|
+
RUBY
|
224
|
+
|
225
|
+
unresolve_entry = @index["A::ALIAS"].first
|
226
|
+
assert_instance_of(Index::Entry::UnresolvedAlias, unresolve_entry)
|
227
|
+
assert_equal(["A"], unresolve_entry.nesting)
|
228
|
+
assert_equal("B", unresolve_entry.target)
|
229
|
+
|
230
|
+
resolved_entry = @index.resolve("ALIAS", ["A"]).first
|
231
|
+
assert_instance_of(Index::Entry::Alias, resolved_entry)
|
232
|
+
assert_equal("A::B", resolved_entry.target)
|
233
|
+
|
234
|
+
resolved_entry = @index.resolve("ALIAS::C", ["A"]).first
|
235
|
+
assert_instance_of(Index::Entry::Module, resolved_entry)
|
236
|
+
assert_equal("A::B::C", resolved_entry.name)
|
237
|
+
|
238
|
+
unresolve_entry = @index["Other::ONE_MORE"].first
|
239
|
+
assert_instance_of(Index::Entry::UnresolvedAlias, unresolve_entry)
|
240
|
+
assert_equal(["Other"], unresolve_entry.nesting)
|
241
|
+
assert_equal("A::ALIAS", unresolve_entry.target)
|
242
|
+
|
243
|
+
resolved_entry = @index.resolve("Other::ONE_MORE::C", []).first
|
244
|
+
assert_instance_of(Index::Entry::Module, resolved_entry)
|
245
|
+
end
|
246
|
+
|
247
|
+
def test_indexing_same_line_constant_aliases
|
248
|
+
index(<<~RUBY)
|
249
|
+
module A
|
250
|
+
B = C = 1
|
251
|
+
D = E ||= 1
|
252
|
+
F = G::H &&= 1
|
253
|
+
I::J = K::L = M = 1
|
254
|
+
end
|
255
|
+
RUBY
|
256
|
+
|
257
|
+
# B and C
|
258
|
+
unresolve_entry = @index["A::B"].first
|
259
|
+
assert_instance_of(Index::Entry::UnresolvedAlias, unresolve_entry)
|
260
|
+
assert_equal(["A"], unresolve_entry.nesting)
|
261
|
+
assert_equal("C", unresolve_entry.target)
|
262
|
+
|
263
|
+
resolved_entry = @index.resolve("A::B", []).first
|
264
|
+
assert_instance_of(Index::Entry::Alias, resolved_entry)
|
265
|
+
assert_equal("A::C", resolved_entry.target)
|
266
|
+
|
267
|
+
constant = @index["A::C"].first
|
268
|
+
assert_instance_of(Index::Entry::Constant, constant)
|
269
|
+
|
270
|
+
# D and E
|
271
|
+
unresolve_entry = @index["A::D"].first
|
272
|
+
assert_instance_of(Index::Entry::UnresolvedAlias, unresolve_entry)
|
273
|
+
assert_equal(["A"], unresolve_entry.nesting)
|
274
|
+
assert_equal("E", unresolve_entry.target)
|
275
|
+
|
276
|
+
resolved_entry = @index.resolve("A::D", []).first
|
277
|
+
assert_instance_of(Index::Entry::Alias, resolved_entry)
|
278
|
+
assert_equal("A::E", resolved_entry.target)
|
279
|
+
|
280
|
+
# F and G::H
|
281
|
+
unresolve_entry = @index["A::F"].first
|
282
|
+
assert_instance_of(Index::Entry::UnresolvedAlias, unresolve_entry)
|
283
|
+
assert_equal(["A"], unresolve_entry.nesting)
|
284
|
+
assert_equal("G::H", unresolve_entry.target)
|
285
|
+
|
286
|
+
resolved_entry = @index.resolve("A::F", []).first
|
287
|
+
assert_instance_of(Index::Entry::Alias, resolved_entry)
|
288
|
+
assert_equal("A::G::H", resolved_entry.target)
|
289
|
+
|
290
|
+
# I::J, K::L and M
|
291
|
+
unresolve_entry = @index["A::I::J"].first
|
292
|
+
assert_instance_of(Index::Entry::UnresolvedAlias, unresolve_entry)
|
293
|
+
assert_equal(["A"], unresolve_entry.nesting)
|
294
|
+
assert_equal("K::L", unresolve_entry.target)
|
295
|
+
|
296
|
+
resolved_entry = @index.resolve("A::I::J", []).first
|
297
|
+
assert_instance_of(Index::Entry::Alias, resolved_entry)
|
298
|
+
assert_equal("A::K::L", resolved_entry.target)
|
299
|
+
|
300
|
+
# When we are resolving A::I::J, we invoke `resolve("K::L", ["A"])`, which recursively resolves A::K::L too.
|
301
|
+
# Therefore, both A::I::J and A::K::L point to A::M by the end of the previous resolve invocation
|
302
|
+
resolved_entry = @index["A::K::L"].first
|
303
|
+
assert_instance_of(Index::Entry::Alias, resolved_entry)
|
304
|
+
assert_equal("A::M", resolved_entry.target)
|
305
|
+
|
306
|
+
constant = @index["A::M"].first
|
307
|
+
assert_instance_of(Index::Entry::Constant, constant)
|
308
|
+
end
|
107
309
|
end
|
108
310
|
end
|
@@ -158,5 +158,25 @@ module RubyIndexer
|
|
158
158
|
results = @index.prefix_search("Ba", ["Foo"]).map { |entries| entries.map(&:name) }
|
159
159
|
assert_equal([["Foo::Bar", "Foo::Bar"], ["Foo::Baz"]], results)
|
160
160
|
end
|
161
|
+
|
162
|
+
def test_resolve_normalizes_top_level_names
|
163
|
+
@index.index_single(IndexablePath.new("/fake", "/fake/path/foo.rb"), <<~RUBY)
|
164
|
+
class Bar; end
|
165
|
+
|
166
|
+
module Foo
|
167
|
+
class Bar; end
|
168
|
+
end
|
169
|
+
RUBY
|
170
|
+
|
171
|
+
entries = @index.resolve("::Foo::Bar", [])
|
172
|
+
refute_nil(entries)
|
173
|
+
|
174
|
+
assert_equal("Foo::Bar", entries.first.name)
|
175
|
+
|
176
|
+
entries = @index.resolve("::Bar", ["Foo"])
|
177
|
+
refute_nil(entries)
|
178
|
+
|
179
|
+
assert_equal("Bar", entries.first.name)
|
180
|
+
end
|
161
181
|
end
|
162
182
|
end
|
@@ -2,24 +2,24 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
module RubyLsp
|
5
|
-
# To register an
|
5
|
+
# To register an addon, inherit from this class and implement both `name` and `activate`
|
6
6
|
#
|
7
7
|
# # Example
|
8
8
|
#
|
9
9
|
# ```ruby
|
10
10
|
# module MyGem
|
11
|
-
# class
|
11
|
+
# class MyAddon < Addon
|
12
12
|
# def activate
|
13
13
|
# # Perform any relevant initialization
|
14
14
|
# end
|
15
15
|
#
|
16
16
|
# def name
|
17
|
-
# "My
|
17
|
+
# "My addon name"
|
18
18
|
# end
|
19
19
|
# end
|
20
20
|
# end
|
21
21
|
# ```
|
22
|
-
class
|
22
|
+
class Addon
|
23
23
|
extend T::Sig
|
24
24
|
extend T::Helpers
|
25
25
|
|
@@ -28,37 +28,37 @@ module RubyLsp
|
|
28
28
|
class << self
|
29
29
|
extend T::Sig
|
30
30
|
|
31
|
-
# Automatically track and instantiate
|
32
|
-
sig { params(child_class: T.class_of(
|
31
|
+
# Automatically track and instantiate addon classes
|
32
|
+
sig { params(child_class: T.class_of(Addon)).void }
|
33
33
|
def inherited(child_class)
|
34
|
-
|
34
|
+
addons << child_class.new
|
35
35
|
super
|
36
36
|
end
|
37
37
|
|
38
|
-
sig { returns(T::Array[
|
39
|
-
def
|
40
|
-
@
|
38
|
+
sig { returns(T::Array[Addon]) }
|
39
|
+
def addons
|
40
|
+
@addons ||= T.let([], T.nilable(T::Array[Addon]))
|
41
41
|
end
|
42
42
|
|
43
|
-
# Discovers and loads all
|
44
|
-
sig { returns(T::Array[
|
45
|
-
def
|
46
|
-
# Require all
|
47
|
-
# `some_gem/lib/ruby_lsp/your_gem_name/
|
48
|
-
Gem.find_files("ruby_lsp/**/
|
49
|
-
require File.expand_path(
|
43
|
+
# Discovers and loads all addons. Returns the list of activated addons
|
44
|
+
sig { returns(T::Array[Addon]) }
|
45
|
+
def load_addons
|
46
|
+
# Require all addons entry points, which should be placed under
|
47
|
+
# `some_gem/lib/ruby_lsp/your_gem_name/addon.rb`
|
48
|
+
Gem.find_files("ruby_lsp/**/addon.rb").each do |addon|
|
49
|
+
require File.expand_path(addon)
|
50
50
|
rescue => e
|
51
51
|
warn(e.message)
|
52
52
|
warn(e.backtrace.to_s) # rubocop:disable Lint/RedundantStringCoercion
|
53
53
|
end
|
54
54
|
|
55
|
-
# Activate each one of the discovered
|
55
|
+
# Activate each one of the discovered addons. If any problems occur in the addons, we don't want to
|
56
56
|
# fail to boot the server
|
57
|
-
|
58
|
-
|
57
|
+
addons.each do |addon|
|
58
|
+
addon.activate
|
59
59
|
nil
|
60
60
|
rescue => e
|
61
|
-
|
61
|
+
addon.add_error(e)
|
62
62
|
end
|
63
63
|
end
|
64
64
|
end
|
@@ -92,17 +92,17 @@ module RubyLsp
|
|
92
92
|
@errors.filter_map(&:backtrace).join("\n\n")
|
93
93
|
end
|
94
94
|
|
95
|
-
# Each
|
95
|
+
# Each addon should implement `MyAddon#activate` and use to perform any sort of initialization, such as
|
96
96
|
# reading information into memory or even spawning a separate process
|
97
97
|
sig { abstract.void }
|
98
98
|
def activate; end
|
99
99
|
|
100
|
-
# Each
|
100
|
+
# Each addon should implement `MyAddon#deactivate` and use to perform any clean up, like shutting down a
|
101
101
|
# child process
|
102
102
|
sig { abstract.void }
|
103
103
|
def deactivate; end
|
104
104
|
|
105
|
-
#
|
105
|
+
# Addons should override the `name` method to return the addon name
|
106
106
|
sig { abstract.returns(String) }
|
107
107
|
def name; end
|
108
108
|
|
@@ -119,11 +119,13 @@ module RubyLsp
|
|
119
119
|
# Creates a new Hover listener. This method is invoked on every Hover request
|
120
120
|
sig do
|
121
121
|
overridable.params(
|
122
|
+
nesting: T::Array[String],
|
123
|
+
index: RubyIndexer::Index,
|
122
124
|
emitter: EventEmitter,
|
123
125
|
message_queue: Thread::Queue,
|
124
126
|
).returns(T.nilable(Listener[T.nilable(Interface::Hover)]))
|
125
127
|
end
|
126
|
-
def create_hover_listener(emitter, message_queue); end
|
128
|
+
def create_hover_listener(nesting, index, emitter, message_queue); end
|
127
129
|
|
128
130
|
# Creates a new DocumentSymbol listener. This method is invoked on every DocumentSymbol request
|
129
131
|
sig do
|
data/lib/ruby_lsp/check_docs.rb
CHANGED
@@ -5,9 +5,9 @@ require "ruby_lsp/internal"
|
|
5
5
|
require "objspace"
|
6
6
|
|
7
7
|
module RubyLsp
|
8
|
-
# This rake task checks that all requests or
|
9
|
-
# specify the absolute path for all files that must be required in order to discover all listeners and their
|
10
|
-
#
|
8
|
+
# This rake task checks that all requests or addons are fully documented. Add the rake task to your Rakefile and
|
9
|
+
# specify the absolute path for all files that must be required in order to discover all listeners and their related
|
10
|
+
# GIFs
|
11
11
|
#
|
12
12
|
# # Rakefile
|
13
13
|
# request_files = FileList.new("#{__dir__}/lib/ruby_lsp/requests/*.rb") do |fl|
|
@@ -53,8 +53,7 @@ module RubyLsp
|
|
53
53
|
# documented
|
54
54
|
features = ObjectSpace.each_object(Class).filter_map do |k|
|
55
55
|
klass = T.unsafe(k)
|
56
|
-
klass if klass <
|
57
|
-
(klass < RubyLsp::Listener && klass != RubyLsp::ExtensibleListener)
|
56
|
+
klass if klass < Requests::BaseRequest || (klass < Listener && klass != ExtensibleListener)
|
58
57
|
end
|
59
58
|
|
60
59
|
missing_docs = T.let(Hash.new { |h, k| h[k] = [] }, T::Hash[String, T::Array[String]])
|
@@ -82,14 +81,14 @@ module RubyLsp
|
|
82
81
|
T.must(missing_docs[class_name]) << "No documentation found"
|
83
82
|
elsif !%r{\(https://microsoft.github.io/language-server-protocol/specification#.*\)}.match?(documentation)
|
84
83
|
T.must(missing_docs[class_name]) << <<~DOCS
|
85
|
-
Missing specification link. Requests and
|
84
|
+
Missing specification link. Requests and addons should include a link to the LSP specification for the
|
86
85
|
related feature. For example:
|
87
86
|
|
88
87
|
[Inlay hint](https://microsoft.github.io/language-server-protocol/specification#textDocument_inlayHint)
|
89
88
|
DOCS
|
90
89
|
elsif !documentation.include?("# Example")
|
91
90
|
T.must(missing_docs[class_name]) << <<~DOCS
|
92
|
-
Missing example. Requests and
|
91
|
+
Missing example. Requests and addons should include a code example that explains what the feature does.
|
93
92
|
|
94
93
|
# # Example
|
95
94
|
# ```ruby
|
@@ -99,7 +98,7 @@ module RubyLsp
|
|
99
98
|
DOCS
|
100
99
|
elsif !/\[.* demo\]\(.*\.gif\)/.match?(documentation)
|
101
100
|
T.must(missing_docs[class_name]) << <<~DOCS
|
102
|
-
Missing demonstration GIF. Each request and
|
101
|
+
Missing demonstration GIF. Each request and addon must be documented with a GIF that shows the feature
|
103
102
|
working. For example:
|
104
103
|
|
105
104
|
# [Inlay hint demo](../../inlay_hint.gif)
|
data/lib/ruby_lsp/document.rb
CHANGED
@@ -9,8 +9,8 @@ module RubyLsp
|
|
9
9
|
RangeShape = T.type_alias { { start: PositionShape, end: PositionShape } }
|
10
10
|
EditShape = T.type_alias { { range: RangeShape, text: String } }
|
11
11
|
|
12
|
-
sig { returns(
|
13
|
-
attr_reader :
|
12
|
+
sig { returns(YARP::ParseResult) }
|
13
|
+
attr_reader :parse_result
|
14
14
|
|
15
15
|
sig { returns(String) }
|
16
16
|
attr_reader :source
|
@@ -28,11 +28,18 @@ module RubyLsp
|
|
28
28
|
@source = T.let(source, String)
|
29
29
|
@version = T.let(version, Integer)
|
30
30
|
@uri = T.let(uri, URI::Generic)
|
31
|
-
@
|
32
|
-
@
|
33
|
-
|
34
|
-
|
35
|
-
|
31
|
+
@needs_parsing = T.let(false, T::Boolean)
|
32
|
+
@parse_result = T.let(YARP.parse(@source), YARP::ParseResult)
|
33
|
+
end
|
34
|
+
|
35
|
+
sig { returns(YARP::ProgramNode) }
|
36
|
+
def tree
|
37
|
+
@parse_result.value
|
38
|
+
end
|
39
|
+
|
40
|
+
sig { returns(T::Array[YARP::Comment]) }
|
41
|
+
def comments
|
42
|
+
@parse_result.comments
|
36
43
|
end
|
37
44
|
|
38
45
|
sig { params(other: Document).returns(T::Boolean) }
|
@@ -80,29 +87,21 @@ module RubyLsp
|
|
80
87
|
end
|
81
88
|
|
82
89
|
@version = version
|
83
|
-
@
|
90
|
+
@needs_parsing = true
|
84
91
|
@cache.clear
|
85
92
|
end
|
86
93
|
|
87
94
|
sig { void }
|
88
95
|
def parse
|
89
|
-
return
|
96
|
+
return unless @needs_parsing
|
90
97
|
|
91
|
-
@
|
92
|
-
@
|
93
|
-
@syntax_error = false
|
94
|
-
rescue SyntaxTree::Parser::ParseError
|
95
|
-
@syntax_error = true
|
98
|
+
@needs_parsing = false
|
99
|
+
@parse_result = YARP.parse(@source)
|
96
100
|
end
|
97
101
|
|
98
102
|
sig { returns(T::Boolean) }
|
99
103
|
def syntax_error?
|
100
|
-
@
|
101
|
-
end
|
102
|
-
|
103
|
-
sig { returns(T::Boolean) }
|
104
|
-
def parsed?
|
105
|
-
!@tree.nil?
|
104
|
+
@parse_result.failure?
|
106
105
|
end
|
107
106
|
|
108
107
|
sig { returns(Scanner) }
|
@@ -113,27 +112,25 @@ module RubyLsp
|
|
113
112
|
sig do
|
114
113
|
params(
|
115
114
|
position: PositionShape,
|
116
|
-
node_types: T::Array[T.class_of(
|
117
|
-
).returns([T.nilable(
|
115
|
+
node_types: T::Array[T.class_of(YARP::Node)],
|
116
|
+
).returns([T.nilable(YARP::Node), T.nilable(YARP::Node), T::Array[String]])
|
118
117
|
end
|
119
118
|
def locate_node(position, node_types: [])
|
120
|
-
|
121
|
-
|
122
|
-
locate(T.must(@tree), create_scanner.find_char_position(position), node_types: node_types)
|
119
|
+
locate(@parse_result.value, create_scanner.find_char_position(position), node_types: node_types)
|
123
120
|
end
|
124
121
|
|
125
122
|
sig do
|
126
123
|
params(
|
127
|
-
node:
|
124
|
+
node: YARP::Node,
|
128
125
|
char_position: Integer,
|
129
|
-
node_types: T::Array[T.class_of(
|
130
|
-
).returns([T.nilable(
|
126
|
+
node_types: T::Array[T.class_of(YARP::Node)],
|
127
|
+
).returns([T.nilable(YARP::Node), T.nilable(YARP::Node), T::Array[String]])
|
131
128
|
end
|
132
129
|
def locate(node, char_position, node_types: [])
|
133
|
-
queue = T.let(node.child_nodes.compact, T::Array[T.nilable(
|
130
|
+
queue = T.let(node.child_nodes.compact, T::Array[T.nilable(YARP::Node)])
|
134
131
|
closest = node
|
135
|
-
parent = T.let(nil, T.nilable(
|
136
|
-
nesting = T.let([], T::Array[T.any(
|
132
|
+
parent = T.let(nil, T.nilable(YARP::Node))
|
133
|
+
nesting = T.let([], T::Array[T.any(YARP::ClassNode, YARP::ModuleNode)])
|
137
134
|
|
138
135
|
until queue.empty?
|
139
136
|
candidate = queue.shift
|
@@ -144,24 +141,24 @@ module RubyLsp
|
|
144
141
|
# Add the next child_nodes to the queue to be processed. The order here is important! We want to move in the
|
145
142
|
# same order as the visiting mechanism, which means searching the child nodes before moving on to the next
|
146
143
|
# sibling
|
147
|
-
queue.unshift(*candidate.child_nodes)
|
144
|
+
T.unsafe(queue).unshift(*candidate.child_nodes)
|
148
145
|
|
149
146
|
# Skip if the current node doesn't cover the desired position
|
150
147
|
loc = candidate.location
|
151
|
-
next unless (loc.
|
148
|
+
next unless (loc.start_offset...loc.end_offset).cover?(char_position)
|
152
149
|
|
153
150
|
# If the node's start character is already past the position, then we should've found the closest node
|
154
151
|
# already
|
155
|
-
break if char_position < loc.
|
152
|
+
break if char_position < loc.start_offset
|
156
153
|
|
157
154
|
# If the candidate starts after the end of the previous nesting level, then we've exited that nesting level and
|
158
155
|
# need to pop the stack
|
159
156
|
previous_level = nesting.last
|
160
|
-
nesting.pop if previous_level &&
|
157
|
+
nesting.pop if previous_level && loc.start_offset > previous_level.location.end_offset
|
161
158
|
|
162
159
|
# Keep track of the nesting where we found the target. This is used to determine the fully qualified name of the
|
163
160
|
# target when it is a constant
|
164
|
-
if candidate.is_a?(
|
161
|
+
if candidate.is_a?(YARP::ClassNode) || candidate.is_a?(YARP::ModuleNode)
|
165
162
|
nesting << candidate
|
166
163
|
end
|
167
164
|
|
@@ -170,13 +167,13 @@ module RubyLsp
|
|
170
167
|
|
171
168
|
# If the current node is narrower than or equal to the previous closest node, then it is more precise
|
172
169
|
closest_loc = closest.location
|
173
|
-
if loc.
|
170
|
+
if loc.end_offset - loc.start_offset <= closest_loc.end_offset - closest_loc.start_offset
|
174
171
|
parent = closest
|
175
172
|
closest = candidate
|
176
173
|
end
|
177
174
|
end
|
178
175
|
|
179
|
-
[closest, parent, nesting.map { |n| n.
|
176
|
+
[closest, parent, nesting.map { |n| n.constant_path.location.slice }]
|
180
177
|
end
|
181
178
|
|
182
179
|
class Scanner
|