ruby-lsp 0.11.1 → 0.12.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/exe/ruby-lsp +2 -1
- data/exe/ruby-lsp-doctor +16 -0
- data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +5 -1
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +205 -0
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +28 -108
- data/lib/ruby_indexer/lib/ruby_indexer/prefix_tree.rb +6 -6
- data/lib/ruby_indexer/lib/ruby_indexer/visitor.rb +160 -64
- data/lib/ruby_indexer/ruby_indexer.rb +1 -0
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +49 -16
- data/lib/ruby_indexer/test/constant_test.rb +111 -30
- data/lib/ruby_indexer/test/index_test.rb +15 -0
- data/lib/ruby_indexer/test/method_test.rb +73 -0
- data/lib/ruby_indexer/test/test_case.rb +5 -1
- data/lib/ruby_lsp/addon.rb +8 -8
- data/lib/ruby_lsp/document.rb +14 -14
- data/lib/ruby_lsp/executor.rb +89 -53
- data/lib/ruby_lsp/internal.rb +8 -2
- data/lib/ruby_lsp/listener.rb +6 -6
- data/lib/ruby_lsp/requests/base_request.rb +1 -9
- data/lib/ruby_lsp/requests/code_action_resolve.rb +3 -3
- data/lib/ruby_lsp/requests/code_lens.rb +30 -30
- data/lib/ruby_lsp/requests/completion.rb +83 -32
- data/lib/ruby_lsp/requests/definition.rb +21 -15
- data/lib/ruby_lsp/requests/diagnostics.rb +1 -1
- data/lib/ruby_lsp/requests/document_highlight.rb +508 -31
- data/lib/ruby_lsp/requests/document_link.rb +24 -17
- data/lib/ruby_lsp/requests/document_symbol.rb +42 -42
- data/lib/ruby_lsp/requests/folding_ranges.rb +84 -82
- data/lib/ruby_lsp/requests/hover.rb +22 -17
- data/lib/ruby_lsp/requests/inlay_hints.rb +6 -6
- data/lib/ruby_lsp/requests/selection_ranges.rb +13 -105
- data/lib/ruby_lsp/requests/semantic_highlighting.rb +92 -92
- data/lib/ruby_lsp/requests/support/annotation.rb +3 -3
- data/lib/ruby_lsp/requests/support/common.rb +5 -5
- data/lib/ruby_lsp/requests/support/dependency_detector.rb +12 -4
- data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +15 -6
- data/lib/ruby_lsp/requests/support/semantic_token_encoder.rb +10 -7
- data/lib/ruby_lsp/requests/support/sorbet.rb +28 -28
- data/lib/ruby_lsp/requests/workspace_symbol.rb +4 -4
- data/lib/ruby_lsp/requests.rb +0 -1
- data/lib/ruby_lsp/setup_bundler.rb +8 -5
- metadata +19 -17
- data/lib/ruby_lsp/event_emitter.rb +0 -351
- data/lib/ruby_lsp/requests/support/highlight_target.rb +0 -118
@@ -2,65 +2,139 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
module RubyIndexer
|
5
|
-
class IndexVisitor <
|
5
|
+
class IndexVisitor < Prism::Visitor
|
6
6
|
extend T::Sig
|
7
7
|
|
8
|
-
sig { params(index: Index, parse_result:
|
8
|
+
sig { params(index: Index, parse_result: Prism::ParseResult, file_path: String).void }
|
9
9
|
def initialize(index, parse_result, file_path)
|
10
10
|
@index = index
|
11
|
-
@parse_result = parse_result
|
12
11
|
@file_path = file_path
|
13
12
|
@stack = T.let([], T::Array[String])
|
14
13
|
@comments_by_line = T.let(
|
15
14
|
parse_result.comments.to_h do |c|
|
16
15
|
[c.location.start_line, c]
|
17
16
|
end,
|
18
|
-
T::Hash[Integer,
|
17
|
+
T::Hash[Integer, Prism::Comment],
|
19
18
|
)
|
20
19
|
|
21
20
|
super()
|
22
21
|
end
|
23
22
|
|
24
|
-
sig { void }
|
25
|
-
def
|
26
|
-
|
27
|
-
end
|
28
|
-
|
29
|
-
sig { params(node:
|
30
|
-
def
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
handle_private_constant(node) if message == "private_constant"
|
23
|
+
sig { override.params(node: Prism::ClassNode).void }
|
24
|
+
def visit_class_node(node)
|
25
|
+
add_class_entry(node)
|
26
|
+
end
|
27
|
+
|
28
|
+
sig { override.params(node: Prism::ModuleNode).void }
|
29
|
+
def visit_module_node(node)
|
30
|
+
add_module_entry(node)
|
31
|
+
end
|
32
|
+
|
33
|
+
sig { override.params(node: Prism::MultiWriteNode).void }
|
34
|
+
def visit_multi_write_node(node)
|
35
|
+
value = node.value
|
36
|
+
values = value.is_a?(Prism::ArrayNode) && value.opening_loc ? value.elements : []
|
37
|
+
|
38
|
+
node.targets.each_with_index do |target, i|
|
39
|
+
current_value = values[i]
|
40
|
+
# The moment we find a splat on the right hand side of the assignment, we can no longer figure out which value
|
41
|
+
# gets assigned to what
|
42
|
+
values.clear if current_value.is_a?(Prism::SplatNode)
|
43
|
+
|
44
|
+
case target
|
45
|
+
when Prism::ConstantTargetNode
|
46
|
+
add_constant(target, fully_qualify_name(target.name.to_s), current_value)
|
47
|
+
when Prism::ConstantPathTargetNode
|
48
|
+
add_constant(target, fully_qualify_name(target.slice), current_value)
|
49
|
+
end
|
52
50
|
end
|
53
51
|
end
|
54
52
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
53
|
+
sig { override.params(node: Prism::ConstantPathWriteNode).void }
|
54
|
+
def visit_constant_path_write_node(node)
|
55
|
+
# ignore variable constants like `var::FOO` or `self.class::FOO`
|
56
|
+
target = node.target
|
57
|
+
return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode)
|
58
|
+
|
59
|
+
name = fully_qualify_name(target.location.slice)
|
60
|
+
add_constant(node, name)
|
61
|
+
end
|
62
|
+
|
63
|
+
sig { override.params(node: Prism::ConstantPathOrWriteNode).void }
|
64
|
+
def visit_constant_path_or_write_node(node)
|
65
|
+
# ignore variable constants like `var::FOO` or `self.class::FOO`
|
66
|
+
target = node.target
|
67
|
+
return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode)
|
68
|
+
|
69
|
+
name = fully_qualify_name(target.location.slice)
|
70
|
+
add_constant(node, name)
|
71
|
+
end
|
72
|
+
|
73
|
+
sig { override.params(node: Prism::ConstantPathOperatorWriteNode).void }
|
74
|
+
def visit_constant_path_operator_write_node(node)
|
75
|
+
# ignore variable constants like `var::FOO` or `self.class::FOO`
|
76
|
+
target = node.target
|
77
|
+
return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode)
|
78
|
+
|
79
|
+
name = fully_qualify_name(target.location.slice)
|
80
|
+
add_constant(node, name)
|
81
|
+
end
|
82
|
+
|
83
|
+
sig { override.params(node: Prism::ConstantPathAndWriteNode).void }
|
84
|
+
def visit_constant_path_and_write_node(node)
|
85
|
+
# ignore variable constants like `var::FOO` or `self.class::FOO`
|
86
|
+
target = node.target
|
87
|
+
return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode)
|
88
|
+
|
89
|
+
name = fully_qualify_name(target.location.slice)
|
90
|
+
add_constant(node, name)
|
91
|
+
end
|
92
|
+
|
93
|
+
sig { override.params(node: Prism::ConstantWriteNode).void }
|
94
|
+
def visit_constant_write_node(node)
|
95
|
+
name = fully_qualify_name(node.name.to_s)
|
96
|
+
add_constant(node, name)
|
97
|
+
end
|
98
|
+
|
99
|
+
sig { override.params(node: Prism::ConstantOrWriteNode).void }
|
100
|
+
def visit_constant_or_write_node(node)
|
101
|
+
name = fully_qualify_name(node.name.to_s)
|
102
|
+
add_constant(node, name)
|
103
|
+
end
|
104
|
+
|
105
|
+
sig { override.params(node: Prism::ConstantAndWriteNode).void }
|
106
|
+
def visit_constant_and_write_node(node)
|
107
|
+
name = fully_qualify_name(node.name.to_s)
|
108
|
+
add_constant(node, name)
|
109
|
+
end
|
110
|
+
|
111
|
+
sig { override.params(node: Prism::ConstantOperatorWriteNode).void }
|
112
|
+
def visit_constant_operator_write_node(node)
|
113
|
+
name = fully_qualify_name(node.name.to_s)
|
114
|
+
add_constant(node, name)
|
115
|
+
end
|
116
|
+
|
117
|
+
sig { override.params(node: Prism::CallNode).void }
|
118
|
+
def visit_call_node(node)
|
119
|
+
message = node.message
|
120
|
+
handle_private_constant(node) if message == "private_constant"
|
121
|
+
end
|
122
|
+
|
123
|
+
sig { override.params(node: Prism::DefNode).void }
|
124
|
+
def visit_def_node(node)
|
125
|
+
method_name = node.name.to_s
|
126
|
+
comments = collect_comments(node)
|
127
|
+
case node.receiver
|
128
|
+
when nil
|
129
|
+
@index << Entry::InstanceMethod.new(method_name, @file_path, node.location, comments, node.parameters)
|
130
|
+
when Prism::SelfNode
|
131
|
+
@index << Entry::SingletonMethod.new(method_name, @file_path, node.location, comments, node.parameters)
|
132
|
+
end
|
59
133
|
end
|
60
134
|
|
61
135
|
private
|
62
136
|
|
63
|
-
sig { params(node:
|
137
|
+
sig { params(node: Prism::CallNode).void }
|
64
138
|
def handle_private_constant(node)
|
65
139
|
arguments = node.arguments&.arguments
|
66
140
|
return unless arguments
|
@@ -68,9 +142,9 @@ module RubyIndexer
|
|
68
142
|
first_argument = arguments.first
|
69
143
|
|
70
144
|
name = case first_argument
|
71
|
-
when
|
145
|
+
when Prism::StringNode
|
72
146
|
first_argument.content
|
73
|
-
when
|
147
|
+
when Prism::SymbolNode
|
74
148
|
first_argument.value
|
75
149
|
end
|
76
150
|
|
@@ -88,56 +162,78 @@ module RubyIndexer
|
|
88
162
|
sig do
|
89
163
|
params(
|
90
164
|
node: T.any(
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
165
|
+
Prism::ConstantWriteNode,
|
166
|
+
Prism::ConstantOrWriteNode,
|
167
|
+
Prism::ConstantAndWriteNode,
|
168
|
+
Prism::ConstantOperatorWriteNode,
|
169
|
+
Prism::ConstantPathWriteNode,
|
170
|
+
Prism::ConstantPathOrWriteNode,
|
171
|
+
Prism::ConstantPathOperatorWriteNode,
|
172
|
+
Prism::ConstantPathAndWriteNode,
|
173
|
+
Prism::ConstantTargetNode,
|
174
|
+
Prism::ConstantPathTargetNode,
|
97
175
|
),
|
98
176
|
name: String,
|
177
|
+
value: T.nilable(Prism::Node),
|
99
178
|
).void
|
100
179
|
end
|
101
|
-
def add_constant(node, name)
|
102
|
-
value = node.value
|
180
|
+
def add_constant(node, name, value = nil)
|
181
|
+
value = node.value unless node.is_a?(Prism::ConstantTargetNode) || node.is_a?(Prism::ConstantPathTargetNode)
|
103
182
|
comments = collect_comments(node)
|
104
183
|
|
105
184
|
@index << case value
|
106
|
-
when
|
107
|
-
|
108
|
-
when
|
109
|
-
|
185
|
+
when Prism::ConstantReadNode, Prism::ConstantPathNode
|
186
|
+
Entry::UnresolvedAlias.new(value.slice, @stack.dup, name, @file_path, node.location, comments)
|
187
|
+
when Prism::ConstantWriteNode, Prism::ConstantAndWriteNode, Prism::ConstantOrWriteNode,
|
188
|
+
Prism::ConstantOperatorWriteNode
|
110
189
|
|
111
190
|
# If the right hand side is another constant assignment, we need to visit it because that constant has to be
|
112
191
|
# indexed too
|
113
192
|
visit(value)
|
114
|
-
|
115
|
-
when
|
116
|
-
|
193
|
+
Entry::UnresolvedAlias.new(value.name.to_s, @stack.dup, name, @file_path, node.location, comments)
|
194
|
+
when Prism::ConstantPathWriteNode, Prism::ConstantPathOrWriteNode, Prism::ConstantPathOperatorWriteNode,
|
195
|
+
Prism::ConstantPathAndWriteNode
|
117
196
|
|
118
197
|
visit(value)
|
119
|
-
|
198
|
+
Entry::UnresolvedAlias.new(value.target.slice, @stack.dup, name, @file_path, node.location, comments)
|
120
199
|
else
|
121
|
-
|
200
|
+
Entry::Constant.new(name, @file_path, node.location, comments)
|
122
201
|
end
|
123
202
|
end
|
124
203
|
|
125
|
-
sig { params(node:
|
126
|
-
def
|
204
|
+
sig { params(node: Prism::ModuleNode).void }
|
205
|
+
def add_module_entry(node)
|
127
206
|
name = node.constant_path.location.slice
|
128
|
-
|
129
|
-
unless /^[A-Z:]/.match?(name)
|
130
|
-
return visit_child_nodes(node)
|
131
|
-
end
|
207
|
+
return visit_child_nodes(node) unless /^[A-Z:]/.match?(name)
|
132
208
|
|
133
209
|
comments = collect_comments(node)
|
134
|
-
|
210
|
+
|
211
|
+
@index << Entry::Module.new(fully_qualify_name(name), @file_path, node.location, comments)
|
135
212
|
@stack << name
|
136
213
|
visit_child_nodes(node)
|
137
214
|
@stack.pop
|
138
215
|
end
|
139
216
|
|
140
|
-
sig { params(node:
|
217
|
+
sig { params(node: Prism::ClassNode).void }
|
218
|
+
def add_class_entry(node)
|
219
|
+
name = node.constant_path.location.slice
|
220
|
+
return visit_child_nodes(node) unless /^[A-Z:]/.match?(name)
|
221
|
+
|
222
|
+
comments = collect_comments(node)
|
223
|
+
|
224
|
+
superclass = node.superclass
|
225
|
+
parent_class = case superclass
|
226
|
+
when Prism::ConstantReadNode, Prism::ConstantPathNode
|
227
|
+
superclass.slice
|
228
|
+
end
|
229
|
+
|
230
|
+
@index << Entry::Class.new(fully_qualify_name(name), @file_path, node.location, comments, parent_class)
|
231
|
+
@stack << name
|
232
|
+
visit(node.body)
|
233
|
+
@stack.pop
|
234
|
+
end
|
235
|
+
|
236
|
+
sig { params(node: Prism::Node).returns(T::Array[String]) }
|
141
237
|
def collect_comments(node)
|
142
238
|
comments = []
|
143
239
|
|
@@ -7,6 +7,7 @@ require "did_you_mean"
|
|
7
7
|
require "ruby_indexer/lib/ruby_indexer/indexable_path"
|
8
8
|
require "ruby_indexer/lib/ruby_indexer/visitor"
|
9
9
|
require "ruby_indexer/lib/ruby_indexer/index"
|
10
|
+
require "ruby_indexer/lib/ruby_indexer/entry"
|
10
11
|
require "ruby_indexer/lib/ruby_indexer/configuration"
|
11
12
|
require "ruby_indexer/lib/ruby_indexer/prefix_tree"
|
12
13
|
|
@@ -11,7 +11,7 @@ module RubyIndexer
|
|
11
11
|
end
|
12
12
|
RUBY
|
13
13
|
|
14
|
-
assert_entry("Foo",
|
14
|
+
assert_entry("Foo", Entry::Class, "/fake/path/foo.rb:0-0:1-3")
|
15
15
|
end
|
16
16
|
|
17
17
|
def test_class_with_statements
|
@@ -21,7 +21,7 @@ module RubyIndexer
|
|
21
21
|
end
|
22
22
|
RUBY
|
23
23
|
|
24
|
-
assert_entry("Foo",
|
24
|
+
assert_entry("Foo", Entry::Class, "/fake/path/foo.rb:0-0:2-3")
|
25
25
|
end
|
26
26
|
|
27
27
|
def test_colon_colon_class
|
@@ -30,7 +30,7 @@ module RubyIndexer
|
|
30
30
|
end
|
31
31
|
RUBY
|
32
32
|
|
33
|
-
assert_entry("Foo",
|
33
|
+
assert_entry("Foo", Entry::Class, "/fake/path/foo.rb:0-0:1-3")
|
34
34
|
end
|
35
35
|
|
36
36
|
def test_colon_colon_class_inside_class
|
@@ -41,8 +41,8 @@ module RubyIndexer
|
|
41
41
|
end
|
42
42
|
RUBY
|
43
43
|
|
44
|
-
assert_entry("Bar",
|
45
|
-
assert_entry("Foo",
|
44
|
+
assert_entry("Bar", Entry::Class, "/fake/path/foo.rb:0-0:3-3")
|
45
|
+
assert_entry("Foo", Entry::Class, "/fake/path/foo.rb:1-2:2-5")
|
46
46
|
end
|
47
47
|
|
48
48
|
def test_namespaced_class
|
@@ -51,7 +51,7 @@ module RubyIndexer
|
|
51
51
|
end
|
52
52
|
RUBY
|
53
53
|
|
54
|
-
assert_entry("Foo::Bar",
|
54
|
+
assert_entry("Foo::Bar", Entry::Class, "/fake/path/foo.rb:0-0:1-3")
|
55
55
|
end
|
56
56
|
|
57
57
|
def test_dynamically_namespaced_class
|
@@ -69,7 +69,7 @@ module RubyIndexer
|
|
69
69
|
end
|
70
70
|
RUBY
|
71
71
|
|
72
|
-
assert_entry("Foo",
|
72
|
+
assert_entry("Foo", Entry::Module, "/fake/path/foo.rb:0-0:1-3")
|
73
73
|
end
|
74
74
|
|
75
75
|
def test_module_with_statements
|
@@ -79,7 +79,7 @@ module RubyIndexer
|
|
79
79
|
end
|
80
80
|
RUBY
|
81
81
|
|
82
|
-
assert_entry("Foo",
|
82
|
+
assert_entry("Foo", Entry::Module, "/fake/path/foo.rb:0-0:2-3")
|
83
83
|
end
|
84
84
|
|
85
85
|
def test_colon_colon_module
|
@@ -88,7 +88,7 @@ module RubyIndexer
|
|
88
88
|
end
|
89
89
|
RUBY
|
90
90
|
|
91
|
-
assert_entry("Foo",
|
91
|
+
assert_entry("Foo", Entry::Module, "/fake/path/foo.rb:0-0:1-3")
|
92
92
|
end
|
93
93
|
|
94
94
|
def test_namespaced_module
|
@@ -97,7 +97,7 @@ module RubyIndexer
|
|
97
97
|
end
|
98
98
|
RUBY
|
99
99
|
|
100
|
-
assert_entry("Foo::Bar",
|
100
|
+
assert_entry("Foo::Bar", Entry::Module, "/fake/path/foo.rb:0-0:1-3")
|
101
101
|
end
|
102
102
|
|
103
103
|
def test_dynamically_namespaced_module
|
@@ -124,11 +124,11 @@ module RubyIndexer
|
|
124
124
|
end
|
125
125
|
RUBY
|
126
126
|
|
127
|
-
assert_entry("Foo",
|
128
|
-
assert_entry("Foo::Bar",
|
129
|
-
assert_entry("Foo::Baz",
|
130
|
-
assert_entry("Foo::Baz::Qux",
|
131
|
-
assert_entry("Foo::Baz::Qux::Something",
|
127
|
+
assert_entry("Foo", Entry::Module, "/fake/path/foo.rb:0-0:10-3")
|
128
|
+
assert_entry("Foo::Bar", Entry::Class, "/fake/path/foo.rb:1-2:2-5")
|
129
|
+
assert_entry("Foo::Baz", Entry::Module, "/fake/path/foo.rb:4-2:9-5")
|
130
|
+
assert_entry("Foo::Baz::Qux", Entry::Class, "/fake/path/foo.rb:5-4:8-7")
|
131
|
+
assert_entry("Foo::Baz::Qux::Something", Entry::Class, "/fake/path/foo.rb:6-6:7-9")
|
132
132
|
end
|
133
133
|
|
134
134
|
def test_deleting_from_index_based_on_file_path
|
@@ -137,7 +137,7 @@ module RubyIndexer
|
|
137
137
|
end
|
138
138
|
RUBY
|
139
139
|
|
140
|
-
assert_entry("Foo",
|
140
|
+
assert_entry("Foo", Entry::Class, "/fake/path/foo.rb:0-0:1-3")
|
141
141
|
|
142
142
|
@index.delete(IndexablePath.new(nil, "/fake/path/foo.rb"))
|
143
143
|
refute_entry("Foo")
|
@@ -239,5 +239,38 @@ module RubyIndexer
|
|
239
239
|
d_const = @index["A::D"].first
|
240
240
|
assert_equal(:public, d_const.visibility)
|
241
241
|
end
|
242
|
+
|
243
|
+
def test_keeping_track_of_super_classes
|
244
|
+
index(<<~RUBY)
|
245
|
+
class Foo < Bar
|
246
|
+
end
|
247
|
+
|
248
|
+
class Baz
|
249
|
+
end
|
250
|
+
|
251
|
+
module Something
|
252
|
+
class Baz
|
253
|
+
end
|
254
|
+
|
255
|
+
class Qux < ::Baz
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
class FinalThing < Something::Baz
|
260
|
+
end
|
261
|
+
RUBY
|
262
|
+
|
263
|
+
foo = T.must(@index["Foo"].first)
|
264
|
+
assert_equal("Bar", foo.parent_class)
|
265
|
+
|
266
|
+
baz = T.must(@index["Baz"].first)
|
267
|
+
assert_nil(baz.parent_class)
|
268
|
+
|
269
|
+
qux = T.must(@index["Something::Qux"].first)
|
270
|
+
assert_equal("::Baz", qux.parent_class)
|
271
|
+
|
272
|
+
final_thing = T.must(@index["FinalThing"].first)
|
273
|
+
assert_equal("Something::Baz", final_thing.parent_class)
|
274
|
+
end
|
242
275
|
end
|
243
276
|
end
|