ruby-lsp 0.23.8 → 0.23.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +4 -18
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +39 -0
- data/lib/ruby_indexer/lib/ruby_indexer/location.rb +12 -0
- data/lib/ruby_indexer/lib/ruby_indexer/reference_finder.rb +28 -63
- data/lib/ruby_indexer/test/reference_finder_test.rb +27 -2
- data/lib/ruby_lsp/internal.rb +1 -0
- data/lib/ruby_lsp/listeners/code_lens.rb +1 -1
- data/lib/ruby_lsp/listeners/completion.rb +2 -2
- data/lib/ruby_lsp/listeners/definition.rb +2 -2
- data/lib/ruby_lsp/listeners/document_link.rb +10 -1
- data/lib/ruby_lsp/listeners/hover.rb +2 -2
- data/lib/ruby_lsp/requests/code_action_resolve.rb +88 -0
- data/lib/ruby_lsp/requests/code_actions.rb +56 -0
- data/lib/ruby_lsp/requests/references.rb +2 -1
- data/lib/ruby_lsp/requests/rename.rb +2 -2
- data/lib/ruby_lsp/requests/support/common.rb +1 -4
- data/lib/ruby_lsp/ruby_document.rb +8 -3
- data/lib/ruby_lsp/server.rb +26 -7
- data/lib/ruby_lsp/type_inferrer.rb +1 -16
- data/lib/ruby_lsp/utils.rb +11 -3
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 149e7978a6f85c9349fa3922283a151f2e2129d12eb14e29d665ad35a3b909e7
|
4
|
+
data.tar.gz: a931c7b049810fe304eaa93ae4a7a8bdd499951fa9f3ca21b6da9df91cc24e12
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6fae90263d8e037d3e58a0f20de4e6eaa671d739f28fa1af90a013a9d0ade3a43fa118b5fe15c3e2a3de025a9c3e4a53dc4584426678619896b98d38cca2dcbc
|
7
|
+
data.tar.gz: c634542b0ec26e9df418222b8eab459f42d3f65e42c196c6a3769139073c94f98bfb0da8cb61b18f055629aa9fbf58d6516d13a40b453f27fbc4a6f1514d3d9d
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.23.
|
1
|
+
0.23.9
|
@@ -91,7 +91,7 @@ module RubyIndexer
|
|
91
91
|
def on_class_node_enter(node)
|
92
92
|
constant_path = node.constant_path
|
93
93
|
superclass = node.superclass
|
94
|
-
nesting = actual_nesting(constant_path.slice)
|
94
|
+
nesting = Index.actual_nesting(@stack, constant_path.slice)
|
95
95
|
|
96
96
|
parent_class = case superclass
|
97
97
|
when Prism::ConstantReadNode, Prism::ConstantPathNode
|
@@ -144,7 +144,7 @@ module RubyIndexer
|
|
144
144
|
if current_owner
|
145
145
|
expression = node.expression
|
146
146
|
name = (expression.is_a?(Prism::SelfNode) ? "<Class:#{last_name_in_stack}>" : "<Class:#{expression.slice}>")
|
147
|
-
real_nesting = actual_nesting(name)
|
147
|
+
real_nesting = Index.actual_nesting(@stack, name)
|
148
148
|
|
149
149
|
existing_entries = T.cast(@index[real_nesting.join("::")], T.nilable(T::Array[Entry::SingletonClass]))
|
150
150
|
|
@@ -516,7 +516,7 @@ module RubyIndexer
|
|
516
516
|
name_loc = Location.from_prism_location(name_location, @code_units_cache)
|
517
517
|
|
518
518
|
entry = Entry::Module.new(
|
519
|
-
actual_nesting(name),
|
519
|
+
Index.actual_nesting(@stack, name),
|
520
520
|
@uri,
|
521
521
|
location,
|
522
522
|
name_loc,
|
@@ -536,7 +536,7 @@ module RubyIndexer
|
|
536
536
|
).void
|
537
537
|
end
|
538
538
|
def add_class(name_or_nesting, full_location, name_location, parent_class_name: nil, comments: nil)
|
539
|
-
nesting = name_or_nesting.is_a?(Array) ? name_or_nesting : actual_nesting(name_or_nesting)
|
539
|
+
nesting = name_or_nesting.is_a?(Array) ? name_or_nesting : Index.actual_nesting(@stack, name_or_nesting)
|
540
540
|
entry = Entry::Class.new(
|
541
541
|
nesting,
|
542
542
|
@uri,
|
@@ -1104,20 +1104,6 @@ module RubyIndexer
|
|
1104
1104
|
end
|
1105
1105
|
end
|
1106
1106
|
|
1107
|
-
sig { params(name: String).returns(T::Array[String]) }
|
1108
|
-
def actual_nesting(name)
|
1109
|
-
nesting = @stack + [name]
|
1110
|
-
corrected_nesting = []
|
1111
|
-
|
1112
|
-
nesting.reverse_each do |name|
|
1113
|
-
corrected_nesting.prepend(name.delete_prefix("::"))
|
1114
|
-
|
1115
|
-
break if name.start_with?("::")
|
1116
|
-
end
|
1117
|
-
|
1118
|
-
corrected_nesting
|
1119
|
-
end
|
1120
|
-
|
1121
1107
|
sig { params(short_name: String, entry: Entry::Namespace).void }
|
1122
1108
|
def advance_namespace_stack(short_name, entry)
|
1123
1109
|
@visibility_stack.push(VisibilityScope.public_scope)
|
@@ -15,6 +15,45 @@ module RubyIndexer
|
|
15
15
|
sig { returns(Configuration) }
|
16
16
|
attr_reader :configuration
|
17
17
|
|
18
|
+
class << self
|
19
|
+
extend T::Sig
|
20
|
+
|
21
|
+
# Returns the real nesting of a constant name taking into account top level
|
22
|
+
# references that may be included anywhere in the name or nesting where that
|
23
|
+
# constant was found
|
24
|
+
sig { params(stack: T::Array[String], name: String).returns(T::Array[String]) }
|
25
|
+
def actual_nesting(stack, name)
|
26
|
+
nesting = stack + [name]
|
27
|
+
corrected_nesting = []
|
28
|
+
|
29
|
+
nesting.reverse_each do |name|
|
30
|
+
corrected_nesting.prepend(name.delete_prefix("::"))
|
31
|
+
|
32
|
+
break if name.start_with?("::")
|
33
|
+
end
|
34
|
+
|
35
|
+
corrected_nesting
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns the unresolved name for a constant reference including all parts of a constant path, or `nil` if the
|
39
|
+
# constant contains dynamic or incomplete parts
|
40
|
+
sig do
|
41
|
+
params(
|
42
|
+
node: T.any(
|
43
|
+
Prism::ConstantPathNode,
|
44
|
+
Prism::ConstantReadNode,
|
45
|
+
Prism::ConstantPathTargetNode,
|
46
|
+
),
|
47
|
+
).returns(T.nilable(String))
|
48
|
+
end
|
49
|
+
def constant_name(node)
|
50
|
+
node.full_name
|
51
|
+
rescue Prism::ConstantPathNode::DynamicPartsInConstantPathError,
|
52
|
+
Prism::ConstantPathNode::MissingNodesInConstantPathError
|
53
|
+
nil
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
18
57
|
sig { void }
|
19
58
|
def initialize
|
20
59
|
# Holds all entries in the index using the following format:
|
@@ -44,5 +44,17 @@ module RubyIndexer
|
|
44
44
|
@start_column = start_column
|
45
45
|
@end_column = end_column
|
46
46
|
end
|
47
|
+
|
48
|
+
sig do
|
49
|
+
params(
|
50
|
+
other: T.any(Location, Prism::Location),
|
51
|
+
).returns(T::Boolean)
|
52
|
+
end
|
53
|
+
def ==(other)
|
54
|
+
start_line == other.start_line &&
|
55
|
+
end_line == other.end_line &&
|
56
|
+
start_column == other.start_column &&
|
57
|
+
end_column == other.end_column
|
58
|
+
end
|
47
59
|
end
|
48
60
|
end
|
@@ -75,12 +75,14 @@ module RubyIndexer
|
|
75
75
|
target: Target,
|
76
76
|
index: RubyIndexer::Index,
|
77
77
|
dispatcher: Prism::Dispatcher,
|
78
|
+
uri: URI::Generic,
|
78
79
|
include_declarations: T::Boolean,
|
79
80
|
).void
|
80
81
|
end
|
81
|
-
def initialize(target, index, dispatcher, include_declarations: true)
|
82
|
+
def initialize(target, index, dispatcher, uri, include_declarations: true)
|
82
83
|
@target = target
|
83
84
|
@index = index
|
85
|
+
@uri = uri
|
84
86
|
@include_declarations = include_declarations
|
85
87
|
@stack = T.let([], T::Array[String])
|
86
88
|
@references = T.let([], T::Array[Reference])
|
@@ -126,15 +128,7 @@ module RubyIndexer
|
|
126
128
|
|
127
129
|
sig { params(node: Prism::ClassNode).void }
|
128
130
|
def on_class_node_enter(node)
|
129
|
-
|
130
|
-
name = constant_path.slice
|
131
|
-
nesting = actual_nesting(name)
|
132
|
-
|
133
|
-
if @target.is_a?(ConstTarget) && nesting.join("::") == @target.fully_qualified_name
|
134
|
-
@references << Reference.new(name, constant_path.location, declaration: true)
|
135
|
-
end
|
136
|
-
|
137
|
-
@stack << name
|
131
|
+
@stack << node.constant_path.slice
|
138
132
|
end
|
139
133
|
|
140
134
|
sig { params(node: Prism::ClassNode).void }
|
@@ -144,15 +138,7 @@ module RubyIndexer
|
|
144
138
|
|
145
139
|
sig { params(node: Prism::ModuleNode).void }
|
146
140
|
def on_module_node_enter(node)
|
147
|
-
|
148
|
-
name = constant_path.slice
|
149
|
-
nesting = actual_nesting(name)
|
150
|
-
|
151
|
-
if @target.is_a?(ConstTarget) && nesting.join("::") == @target.fully_qualified_name
|
152
|
-
@references << Reference.new(name, constant_path.location, declaration: true)
|
153
|
-
end
|
154
|
-
|
155
|
-
@stack << name
|
141
|
+
@stack << node.constant_path.slice
|
156
142
|
end
|
157
143
|
|
158
144
|
sig { params(node: Prism::ModuleNode).void }
|
@@ -175,7 +161,7 @@ module RubyIndexer
|
|
175
161
|
|
176
162
|
sig { params(node: Prism::ConstantPathNode).void }
|
177
163
|
def on_constant_path_node_enter(node)
|
178
|
-
name = constant_name(node)
|
164
|
+
name = Index.constant_name(node)
|
179
165
|
return unless name
|
180
166
|
|
181
167
|
collect_constant_references(name, node.location)
|
@@ -183,7 +169,7 @@ module RubyIndexer
|
|
183
169
|
|
184
170
|
sig { params(node: Prism::ConstantReadNode).void }
|
185
171
|
def on_constant_read_node_enter(node)
|
186
|
-
name = constant_name(node)
|
172
|
+
name = Index.constant_name(node)
|
187
173
|
return unless name
|
188
174
|
|
189
175
|
collect_constant_references(name, node.location)
|
@@ -204,7 +190,7 @@ module RubyIndexer
|
|
204
190
|
target = node.target
|
205
191
|
return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode)
|
206
192
|
|
207
|
-
name = constant_name(target)
|
193
|
+
name = Index.constant_name(target)
|
208
194
|
return unless name
|
209
195
|
|
210
196
|
collect_constant_references(name, target.location)
|
@@ -215,7 +201,7 @@ module RubyIndexer
|
|
215
201
|
target = node.target
|
216
202
|
return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode)
|
217
203
|
|
218
|
-
name = constant_name(target)
|
204
|
+
name = Index.constant_name(target)
|
219
205
|
return unless name
|
220
206
|
|
221
207
|
collect_constant_references(name, target.location)
|
@@ -226,7 +212,7 @@ module RubyIndexer
|
|
226
212
|
target = node.target
|
227
213
|
return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode)
|
228
214
|
|
229
|
-
name = constant_name(target)
|
215
|
+
name = Index.constant_name(target)
|
230
216
|
return unless name
|
231
217
|
|
232
218
|
collect_constant_references(name, target.location)
|
@@ -237,7 +223,7 @@ module RubyIndexer
|
|
237
223
|
target = node.target
|
238
224
|
return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode)
|
239
225
|
|
240
|
-
name = constant_name(target)
|
226
|
+
name = Index.constant_name(target)
|
241
227
|
return unless name
|
242
228
|
|
243
229
|
collect_constant_references(name, target.location)
|
@@ -320,20 +306,6 @@ module RubyIndexer
|
|
320
306
|
|
321
307
|
private
|
322
308
|
|
323
|
-
sig { params(name: String).returns(T::Array[String]) }
|
324
|
-
def actual_nesting(name)
|
325
|
-
nesting = @stack + [name]
|
326
|
-
corrected_nesting = []
|
327
|
-
|
328
|
-
nesting.reverse_each do |name|
|
329
|
-
corrected_nesting.prepend(name.delete_prefix("::"))
|
330
|
-
|
331
|
-
break if name.start_with?("::")
|
332
|
-
end
|
333
|
-
|
334
|
-
corrected_nesting
|
335
|
-
end
|
336
|
-
|
337
309
|
sig { params(name: String, location: Prism::Location).void }
|
338
310
|
def collect_constant_references(name, location)
|
339
311
|
return unless @target.is_a?(ConstTarget)
|
@@ -341,17 +313,26 @@ module RubyIndexer
|
|
341
313
|
entries = @index.resolve(name, @stack)
|
342
314
|
return unless entries
|
343
315
|
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
316
|
+
# Filter down to all constant declarations that match the expected name and type
|
317
|
+
matching_entries = entries.select do |e|
|
318
|
+
[
|
319
|
+
Entry::Namespace,
|
320
|
+
Entry::Constant,
|
321
|
+
Entry::ConstantAlias,
|
322
|
+
Entry::UnresolvedConstantAlias,
|
323
|
+
].any? { |klass| e.is_a?(klass) } &&
|
324
|
+
e.name == @target.fully_qualified_name
|
325
|
+
end
|
348
326
|
|
349
|
-
|
350
|
-
# when we find the constant node defining the namespace, then we have to check if it wasn't already added
|
351
|
-
next if previous_reference&.location == location
|
327
|
+
return if matching_entries.empty?
|
352
328
|
|
353
|
-
|
329
|
+
# If any of the matching entries have the same location as the constant and were
|
330
|
+
# defined in the same file, then it is that constant's declaration
|
331
|
+
declaration = matching_entries.any? do |e|
|
332
|
+
e.uri == @uri && e.name_location == location
|
354
333
|
end
|
334
|
+
|
335
|
+
@references << Reference.new(name, location, declaration: declaration)
|
355
336
|
end
|
356
337
|
|
357
338
|
sig { params(name: String, location: Prism::Location, declaration: T::Boolean).void }
|
@@ -360,21 +341,5 @@ module RubyIndexer
|
|
360
341
|
|
361
342
|
@references << Reference.new(name, location, declaration: declaration)
|
362
343
|
end
|
363
|
-
|
364
|
-
sig do
|
365
|
-
params(
|
366
|
-
node: T.any(
|
367
|
-
Prism::ConstantPathNode,
|
368
|
-
Prism::ConstantReadNode,
|
369
|
-
Prism::ConstantPathTargetNode,
|
370
|
-
),
|
371
|
-
).returns(T.nilable(String))
|
372
|
-
end
|
373
|
-
def constant_name(node)
|
374
|
-
node.full_name
|
375
|
-
rescue Prism::ConstantPathNode::DynamicPartsInConstantPathError,
|
376
|
-
Prism::ConstantPathNode::MissingNodesInConstantPathError
|
377
|
-
nil
|
378
|
-
end
|
379
344
|
end
|
380
345
|
end
|
@@ -274,6 +274,30 @@ module RubyIndexer
|
|
274
274
|
assert_equal(8, refs[1].location.start_line)
|
275
275
|
end
|
276
276
|
|
277
|
+
def test_accounts_for_reopened_classes
|
278
|
+
refs = find_const_references("Foo", <<~RUBY)
|
279
|
+
class Foo
|
280
|
+
end
|
281
|
+
class Foo
|
282
|
+
class Bar
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
Foo.new
|
287
|
+
RUBY
|
288
|
+
|
289
|
+
assert_equal(3, refs.size)
|
290
|
+
|
291
|
+
assert_equal("Foo", refs[0].name)
|
292
|
+
assert_equal(1, refs[0].location.start_line)
|
293
|
+
|
294
|
+
assert_equal("Foo", refs[1].name)
|
295
|
+
assert_equal(3, refs[1].location.start_line)
|
296
|
+
|
297
|
+
assert_equal("Foo", refs[2].name)
|
298
|
+
assert_equal(8, refs[2].location.start_line)
|
299
|
+
end
|
300
|
+
|
277
301
|
private
|
278
302
|
|
279
303
|
def find_const_references(const_name, source)
|
@@ -293,11 +317,12 @@ module RubyIndexer
|
|
293
317
|
|
294
318
|
def find_references(target, source)
|
295
319
|
file_path = "/fake.rb"
|
320
|
+
uri = URI::Generic.from_path(path: file_path)
|
296
321
|
index = Index.new
|
297
|
-
index.index_single(
|
322
|
+
index.index_single(uri, source)
|
298
323
|
parse_result = Prism.parse(source)
|
299
324
|
dispatcher = Prism::Dispatcher.new
|
300
|
-
finder = ReferenceFinder.new(target, index, dispatcher)
|
325
|
+
finder = ReferenceFinder.new(target, index, dispatcher, uri)
|
301
326
|
dispatcher.visit(parse_result.value)
|
302
327
|
finder.references
|
303
328
|
end
|
data/lib/ruby_lsp/internal.rb
CHANGED
@@ -113,7 +113,7 @@ module RubyLsp
|
|
113
113
|
# no sigil, Sorbet will still provide completion for constants
|
114
114
|
return if @sorbet_level != RubyDocument::SorbetLevel::Ignore
|
115
115
|
|
116
|
-
name = constant_name(node)
|
116
|
+
name = RubyIndexer::Index.constant_name(node)
|
117
117
|
return if name.nil?
|
118
118
|
|
119
119
|
range = range_from_location(node.location)
|
@@ -162,7 +162,7 @@ module RubyLsp
|
|
162
162
|
if (receiver.is_a?(Prism::ConstantReadNode) || receiver.is_a?(Prism::ConstantPathNode)) &&
|
163
163
|
node.call_operator == "::"
|
164
164
|
|
165
|
-
name = constant_name(receiver)
|
165
|
+
name = RubyIndexer::Index.constant_name(receiver)
|
166
166
|
|
167
167
|
if name
|
168
168
|
start_loc = node.location
|
@@ -118,7 +118,7 @@ module RubyLsp
|
|
118
118
|
|
119
119
|
sig { params(node: Prism::ConstantPathNode).void }
|
120
120
|
def on_constant_path_node_enter(node)
|
121
|
-
name = constant_name(node)
|
121
|
+
name = RubyIndexer::Index.constant_name(node)
|
122
122
|
return if name.nil?
|
123
123
|
|
124
124
|
find_in_index(name)
|
@@ -126,7 +126,7 @@ module RubyLsp
|
|
126
126
|
|
127
127
|
sig { params(node: Prism::ConstantReadNode).void }
|
128
128
|
def on_constant_read_node_enter(node)
|
129
|
-
name = constant_name(node)
|
129
|
+
name = RubyIndexer::Index.constant_name(node)
|
130
130
|
return if name.nil?
|
131
131
|
|
132
132
|
find_in_index(name)
|
@@ -124,7 +124,16 @@ module RubyLsp
|
|
124
124
|
match = comment.location.slice.match(%r{source://.*#\d+$})
|
125
125
|
return unless match
|
126
126
|
|
127
|
-
uri = T.cast(
|
127
|
+
uri = T.cast(
|
128
|
+
begin
|
129
|
+
URI(T.must(match[0]))
|
130
|
+
rescue URI::Error
|
131
|
+
nil
|
132
|
+
end,
|
133
|
+
T.nilable(URI::Source),
|
134
|
+
)
|
135
|
+
return unless uri
|
136
|
+
|
128
137
|
gem_version = resolve_version(uri)
|
129
138
|
return if gem_version.nil?
|
130
139
|
|
@@ -114,7 +114,7 @@ module RubyLsp
|
|
114
114
|
def on_constant_read_node_enter(node)
|
115
115
|
return if @sorbet_level != RubyDocument::SorbetLevel::Ignore
|
116
116
|
|
117
|
-
name = constant_name(node)
|
117
|
+
name = RubyIndexer::Index.constant_name(node)
|
118
118
|
return if name.nil?
|
119
119
|
|
120
120
|
generate_hover(name, node.location)
|
@@ -131,7 +131,7 @@ module RubyLsp
|
|
131
131
|
def on_constant_path_node_enter(node)
|
132
132
|
return if @sorbet_level != RubyDocument::SorbetLevel::Ignore
|
133
133
|
|
134
|
-
name = constant_name(node)
|
134
|
+
name = RubyIndexer::Index.constant_name(node)
|
135
135
|
return if name.nil?
|
136
136
|
|
137
137
|
generate_hover(name, node.location)
|
@@ -42,6 +42,10 @@ module RubyLsp
|
|
42
42
|
refactor_method
|
43
43
|
when CodeActions::TOGGLE_BLOCK_STYLE_TITLE
|
44
44
|
switch_block_style
|
45
|
+
when CodeActions::CREATE_ATTRIBUTE_READER,
|
46
|
+
CodeActions::CREATE_ATTRIBUTE_WRITER,
|
47
|
+
CodeActions::CREATE_ATTRIBUTE_ACCESSOR
|
48
|
+
create_attribute_accessor
|
45
49
|
else
|
46
50
|
Error::UnknownCodeAction
|
47
51
|
end
|
@@ -325,6 +329,90 @@ module RubyLsp
|
|
325
329
|
|
326
330
|
indentation ? body_content.gsub(";", "\n") : "#{body_content.gsub("\n", ";")} "
|
327
331
|
end
|
332
|
+
|
333
|
+
sig { returns(T.any(Interface::CodeAction, Error)) }
|
334
|
+
def create_attribute_accessor
|
335
|
+
source_range = @code_action.dig(:data, :range)
|
336
|
+
|
337
|
+
node = if source_range[:start] != source_range[:end]
|
338
|
+
@document.locate_first_within_range(
|
339
|
+
@code_action.dig(:data, :range),
|
340
|
+
node_types: CodeActions::INSTANCE_VARIABLE_NODES,
|
341
|
+
)
|
342
|
+
end
|
343
|
+
|
344
|
+
if node.nil?
|
345
|
+
node_context = @document.locate_node(
|
346
|
+
source_range[:start],
|
347
|
+
node_types: CodeActions::INSTANCE_VARIABLE_NODES,
|
348
|
+
)
|
349
|
+
node = node_context.node
|
350
|
+
|
351
|
+
return Error::EmptySelection unless CodeActions::INSTANCE_VARIABLE_NODES.include?(node.class)
|
352
|
+
end
|
353
|
+
|
354
|
+
node = T.cast(
|
355
|
+
node,
|
356
|
+
T.any(
|
357
|
+
Prism::InstanceVariableAndWriteNode,
|
358
|
+
Prism::InstanceVariableOperatorWriteNode,
|
359
|
+
Prism::InstanceVariableOrWriteNode,
|
360
|
+
Prism::InstanceVariableReadNode,
|
361
|
+
Prism::InstanceVariableTargetNode,
|
362
|
+
Prism::InstanceVariableWriteNode,
|
363
|
+
),
|
364
|
+
)
|
365
|
+
|
366
|
+
node_context = @document.locate_node(
|
367
|
+
{
|
368
|
+
line: node.location.start_line,
|
369
|
+
character: node.location.start_character_column,
|
370
|
+
},
|
371
|
+
node_types: [
|
372
|
+
Prism::ClassNode,
|
373
|
+
Prism::ModuleNode,
|
374
|
+
Prism::SingletonClassNode,
|
375
|
+
],
|
376
|
+
)
|
377
|
+
closest_node = node_context.node
|
378
|
+
return Error::InvalidTargetRange if closest_node.nil?
|
379
|
+
|
380
|
+
attribute_name = node.name[1..]
|
381
|
+
indentation = " " * (closest_node.location.start_column + 2)
|
382
|
+
attribute_accessor_source = T.must(
|
383
|
+
case @code_action[:title]
|
384
|
+
when CodeActions::CREATE_ATTRIBUTE_READER
|
385
|
+
"#{indentation}attr_reader :#{attribute_name}\n\n"
|
386
|
+
when CodeActions::CREATE_ATTRIBUTE_WRITER
|
387
|
+
"#{indentation}attr_writer :#{attribute_name}\n\n"
|
388
|
+
when CodeActions::CREATE_ATTRIBUTE_ACCESSOR
|
389
|
+
"#{indentation}attr_accessor :#{attribute_name}\n\n"
|
390
|
+
end,
|
391
|
+
)
|
392
|
+
|
393
|
+
target_start_line = closest_node.location.start_line
|
394
|
+
target_range = {
|
395
|
+
start: { line: target_start_line, character: 0 },
|
396
|
+
end: { line: target_start_line, character: 0 },
|
397
|
+
}
|
398
|
+
|
399
|
+
Interface::CodeAction.new(
|
400
|
+
title: @code_action[:title],
|
401
|
+
edit: Interface::WorkspaceEdit.new(
|
402
|
+
document_changes: [
|
403
|
+
Interface::TextDocumentEdit.new(
|
404
|
+
text_document: Interface::OptionalVersionedTextDocumentIdentifier.new(
|
405
|
+
uri: @code_action.dig(:data, :uri),
|
406
|
+
version: nil,
|
407
|
+
),
|
408
|
+
edits: [
|
409
|
+
create_text_edit(target_range, attribute_accessor_source),
|
410
|
+
],
|
411
|
+
),
|
412
|
+
],
|
413
|
+
),
|
414
|
+
)
|
415
|
+
end
|
328
416
|
end
|
329
417
|
end
|
330
418
|
end
|
@@ -12,6 +12,21 @@ module RubyLsp
|
|
12
12
|
EXTRACT_TO_VARIABLE_TITLE = "Refactor: Extract Variable"
|
13
13
|
EXTRACT_TO_METHOD_TITLE = "Refactor: Extract Method"
|
14
14
|
TOGGLE_BLOCK_STYLE_TITLE = "Refactor: Toggle block style"
|
15
|
+
CREATE_ATTRIBUTE_READER = "Create Attribute Reader"
|
16
|
+
CREATE_ATTRIBUTE_WRITER = "Create Attribute Writer"
|
17
|
+
CREATE_ATTRIBUTE_ACCESSOR = "Create Attribute Accessor"
|
18
|
+
|
19
|
+
INSTANCE_VARIABLE_NODES = T.let(
|
20
|
+
[
|
21
|
+
Prism::InstanceVariableAndWriteNode,
|
22
|
+
Prism::InstanceVariableOperatorWriteNode,
|
23
|
+
Prism::InstanceVariableOrWriteNode,
|
24
|
+
Prism::InstanceVariableReadNode,
|
25
|
+
Prism::InstanceVariableTargetNode,
|
26
|
+
Prism::InstanceVariableWriteNode,
|
27
|
+
],
|
28
|
+
T::Array[T.class_of(Prism::Node)],
|
29
|
+
)
|
15
30
|
|
16
31
|
class << self
|
17
32
|
extend T::Sig
|
@@ -66,9 +81,50 @@ module RubyLsp
|
|
66
81
|
data: { range: @range, uri: @uri.to_s },
|
67
82
|
)
|
68
83
|
end
|
84
|
+
code_actions.concat(attribute_actions)
|
69
85
|
|
70
86
|
code_actions
|
71
87
|
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
sig { returns(T::Array[Interface::CodeAction]) }
|
92
|
+
def attribute_actions
|
93
|
+
return [] unless @document.is_a?(RubyDocument)
|
94
|
+
|
95
|
+
node = if @range.dig(:start) != @range.dig(:end)
|
96
|
+
@document.locate_first_within_range(
|
97
|
+
@range,
|
98
|
+
node_types: INSTANCE_VARIABLE_NODES,
|
99
|
+
)
|
100
|
+
end
|
101
|
+
|
102
|
+
if node.nil?
|
103
|
+
node_context = @document.locate_node(
|
104
|
+
@range[:start],
|
105
|
+
node_types: CodeActions::INSTANCE_VARIABLE_NODES,
|
106
|
+
)
|
107
|
+
return [] unless INSTANCE_VARIABLE_NODES.include?(node_context.node.class)
|
108
|
+
end
|
109
|
+
|
110
|
+
[
|
111
|
+
Interface::CodeAction.new(
|
112
|
+
title: CREATE_ATTRIBUTE_READER,
|
113
|
+
kind: Constant::CodeActionKind::EMPTY,
|
114
|
+
data: { range: @range, uri: @uri.to_s },
|
115
|
+
),
|
116
|
+
Interface::CodeAction.new(
|
117
|
+
title: CREATE_ATTRIBUTE_WRITER,
|
118
|
+
kind: Constant::CodeActionKind::EMPTY,
|
119
|
+
data: { range: @range, uri: @uri.to_s },
|
120
|
+
),
|
121
|
+
Interface::CodeAction.new(
|
122
|
+
title: CREATE_ATTRIBUTE_ACCESSOR,
|
123
|
+
kind: Constant::CodeActionKind::EMPTY,
|
124
|
+
data: { range: @range, uri: @uri.to_s },
|
125
|
+
),
|
126
|
+
]
|
127
|
+
end
|
72
128
|
end
|
73
129
|
end
|
74
130
|
end
|
@@ -124,7 +124,7 @@ module RubyLsp
|
|
124
124
|
def create_reference_target(target_node, node_context)
|
125
125
|
case target_node
|
126
126
|
when Prism::ConstantReadNode, Prism::ConstantPathNode, Prism::ConstantPathTargetNode
|
127
|
-
name = constant_name(target_node)
|
127
|
+
name = RubyIndexer::Index.constant_name(target_node)
|
128
128
|
return unless name
|
129
129
|
|
130
130
|
entries = @global_state.index.resolve(name, node_context.nesting)
|
@@ -158,6 +158,7 @@ module RubyLsp
|
|
158
158
|
target,
|
159
159
|
@global_state.index,
|
160
160
|
dispatcher,
|
161
|
+
uri,
|
161
162
|
include_declarations: @params.dig(:context, :includeDeclaration) || true,
|
162
163
|
)
|
163
164
|
dispatcher.visit(parse_result.value)
|
@@ -65,7 +65,7 @@ module RubyLsp
|
|
65
65
|
T.any(Prism::ConstantReadNode, Prism::ConstantPathNode, Prism::ConstantPathTargetNode),
|
66
66
|
)
|
67
67
|
|
68
|
-
name = constant_name(target)
|
68
|
+
name = RubyIndexer::Index.constant_name(target)
|
69
69
|
return unless name
|
70
70
|
|
71
71
|
entries = @global_state.index.resolve(name, node_context.nesting)
|
@@ -179,7 +179,7 @@ module RubyLsp
|
|
179
179
|
end
|
180
180
|
def collect_changes(target, parse_result, name, uri)
|
181
181
|
dispatcher = Prism::Dispatcher.new
|
182
|
-
finder = RubyIndexer::ReferenceFinder.new(target, @global_state.index, dispatcher)
|
182
|
+
finder = RubyIndexer::ReferenceFinder.new(target, @global_state.index, dispatcher, uri)
|
183
183
|
dispatcher.visit(parse_result.value)
|
184
184
|
|
185
185
|
finder.references.map do |reference|
|
@@ -145,10 +145,7 @@ module RubyLsp
|
|
145
145
|
).returns(T.nilable(String))
|
146
146
|
end
|
147
147
|
def constant_name(node)
|
148
|
-
node
|
149
|
-
rescue Prism::ConstantPathNode::DynamicPartsInConstantPathError,
|
150
|
-
Prism::ConstantPathNode::MissingNodesInConstantPathError
|
151
|
-
nil
|
148
|
+
RubyIndexer::Index.constant_name(node)
|
152
149
|
end
|
153
150
|
|
154
151
|
sig { params(node: T.any(Prism::ModuleNode, Prism::ClassNode)).returns(T.nilable(String)) }
|
@@ -258,11 +258,18 @@ module RubyLsp
|
|
258
258
|
end
|
259
259
|
|
260
260
|
sig { returns(T::Boolean) }
|
261
|
-
def
|
261
|
+
def should_index?
|
262
262
|
# This method controls when we should index documents. If there's no recent edit and the document has just been
|
263
263
|
# opened, we need to index it
|
264
264
|
return true unless @last_edit
|
265
265
|
|
266
|
+
last_edit_may_change_declarations?
|
267
|
+
end
|
268
|
+
|
269
|
+
private
|
270
|
+
|
271
|
+
sig { returns(T::Boolean) }
|
272
|
+
def last_edit_may_change_declarations?
|
266
273
|
case @last_edit
|
267
274
|
when Delete
|
268
275
|
# Not optimized yet. It's not trivial to identify that a declaration has been removed since the source is no
|
@@ -275,8 +282,6 @@ module RubyLsp
|
|
275
282
|
end
|
276
283
|
end
|
277
284
|
|
278
|
-
private
|
279
|
-
|
280
285
|
sig { params(position: T::Hash[Symbol, Integer]).returns(T::Boolean) }
|
281
286
|
def position_may_impact_declarations?(position)
|
282
287
|
node_context = locate_node(position)
|
data/lib/ruby_lsp/server.rb
CHANGED
@@ -13,7 +13,7 @@ module RubyLsp
|
|
13
13
|
def process_message(message)
|
14
14
|
case message[:method]
|
15
15
|
when "initialize"
|
16
|
-
send_log_message("Initializing Ruby LSP v#{VERSION}
|
16
|
+
send_log_message("Initializing Ruby LSP v#{VERSION} https://github.com/Shopify/ruby-lsp/releases/tag/v#{VERSION}....")
|
17
17
|
run_initialize(message)
|
18
18
|
when "initialized"
|
19
19
|
send_log_message("Finished initializing Ruby LSP!") unless @test_mode
|
@@ -301,10 +301,19 @@ module RubyLsp
|
|
301
301
|
|
302
302
|
# Not every client supports dynamic registration or file watching
|
303
303
|
if @global_state.client_capabilities.supports_watching_files
|
304
|
-
send_message(Request.register_watched_files(@current_request_id, "**/*.rb"))
|
305
304
|
send_message(Request.register_watched_files(
|
306
305
|
@current_request_id,
|
307
|
-
|
306
|
+
"**/*.rb",
|
307
|
+
registration_id: "workspace-watcher",
|
308
|
+
))
|
309
|
+
|
310
|
+
send_message(Request.register_watched_files(
|
311
|
+
@current_request_id,
|
312
|
+
Interface::RelativePattern.new(
|
313
|
+
base_uri: @global_state.workspace_uri.to_s,
|
314
|
+
pattern: "{.rubocop.yml,.rubocop}",
|
315
|
+
),
|
316
|
+
registration_id: "rubocop-watcher",
|
308
317
|
))
|
309
318
|
end
|
310
319
|
|
@@ -473,11 +482,11 @@ module RubyLsp
|
|
473
482
|
code_lens = Requests::CodeLens.new(@global_state, uri, dispatcher)
|
474
483
|
inlay_hint = Requests::InlayHints.new(document, T.must(@store.features_configuration.dig(:inlayHint)), dispatcher)
|
475
484
|
|
476
|
-
if document.is_a?(RubyDocument) && document.
|
485
|
+
if document.is_a?(RubyDocument) && document.should_index?
|
477
486
|
# Re-index the file as it is modified. This mode of indexing updates entries only. Require path trees are only
|
478
487
|
# updated on save
|
479
488
|
@global_state.synchronize do
|
480
|
-
send_log_message("
|
489
|
+
send_log_message("Determined that document should be indexed: #{uri}")
|
481
490
|
|
482
491
|
@global_state.index.handle_change(uri) do |index|
|
483
492
|
index.delete(uri, skip_require_paths_tree: true)
|
@@ -999,6 +1008,11 @@ module RubyLsp
|
|
999
1008
|
sig { params(message: T::Hash[Symbol, T.untyped]).void }
|
1000
1009
|
def workspace_did_change_watched_files(message)
|
1001
1010
|
changes = message.dig(:params, :changes)
|
1011
|
+
# We allow add-ons to register for watching files and we have no restrictions for what they register for. If the
|
1012
|
+
# same pattern is registered more than once, the LSP will receive duplicate change notifications. Receiving them
|
1013
|
+
# is fine, but we shouldn't process the same file changes more than once
|
1014
|
+
changes.uniq!
|
1015
|
+
|
1002
1016
|
index = @global_state.index
|
1003
1017
|
changes.each do |change|
|
1004
1018
|
# File change events include folders, but we're only interested in files
|
@@ -1131,7 +1145,12 @@ module RubyLsp
|
|
1131
1145
|
|
1132
1146
|
sig { params(message: T::Hash[Symbol, T.untyped]).void }
|
1133
1147
|
def workspace_dependencies(message)
|
1134
|
-
|
1148
|
+
unless @global_state.top_level_bundle
|
1149
|
+
send_message(Result.new(id: message[:id], response: []))
|
1150
|
+
return
|
1151
|
+
end
|
1152
|
+
|
1153
|
+
response = begin
|
1135
1154
|
Bundler.with_original_env do
|
1136
1155
|
definition = Bundler.definition
|
1137
1156
|
dep_keys = definition.locked_deps.keys.to_set
|
@@ -1145,7 +1164,7 @@ module RubyLsp
|
|
1145
1164
|
}
|
1146
1165
|
end
|
1147
1166
|
end
|
1148
|
-
|
1167
|
+
rescue Bundler::GemNotFound
|
1149
1168
|
[]
|
1150
1169
|
end
|
1151
1170
|
|
@@ -80,7 +80,7 @@ module RubyLsp
|
|
80
80
|
# When the receiver is a constant reference, we have to try to resolve it to figure out the right
|
81
81
|
# receiver. But since the invocation is directly on the constant, that's the singleton context of that
|
82
82
|
# class/module
|
83
|
-
receiver_name = constant_name(receiver)
|
83
|
+
receiver_name = RubyIndexer::Index.constant_name(receiver)
|
84
84
|
return unless receiver_name
|
85
85
|
|
86
86
|
resolved_receiver = @index.resolve(receiver_name, node_context.nesting)
|
@@ -147,21 +147,6 @@ module RubyLsp
|
|
147
147
|
Type.new("#{parts.join("::")}::<Class:#{parts.last}>")
|
148
148
|
end
|
149
149
|
|
150
|
-
sig do
|
151
|
-
params(
|
152
|
-
node: T.any(
|
153
|
-
Prism::ConstantPathNode,
|
154
|
-
Prism::ConstantReadNode,
|
155
|
-
),
|
156
|
-
).returns(T.nilable(String))
|
157
|
-
end
|
158
|
-
def constant_name(node)
|
159
|
-
node.full_name
|
160
|
-
rescue Prism::ConstantPathNode::DynamicPartsInConstantPathError,
|
161
|
-
Prism::ConstantPathNode::MissingNodesInConstantPathError
|
162
|
-
nil
|
163
|
-
end
|
164
|
-
|
165
150
|
sig { params(node_context: NodeContext).returns(T.nilable(Type)) }
|
166
151
|
def infer_receiver_for_class_variables(node_context)
|
167
152
|
nesting_parts = node_context.nesting.dup
|
data/lib/ruby_lsp/utils.rb
CHANGED
@@ -176,11 +176,19 @@ module RubyLsp
|
|
176
176
|
class << self
|
177
177
|
extend T::Sig
|
178
178
|
|
179
|
-
sig
|
179
|
+
sig do
|
180
|
+
params(
|
181
|
+
id: Integer,
|
182
|
+
pattern: T.any(Interface::RelativePattern, String),
|
183
|
+
kind: Integer,
|
184
|
+
registration_id: T.nilable(String),
|
185
|
+
).returns(Request)
|
186
|
+
end
|
180
187
|
def register_watched_files(
|
181
188
|
id,
|
182
189
|
pattern,
|
183
|
-
kind: Constant::WatchKind::CREATE | Constant::WatchKind::CHANGE | Constant::WatchKind::DELETE
|
190
|
+
kind: Constant::WatchKind::CREATE | Constant::WatchKind::CHANGE | Constant::WatchKind::DELETE,
|
191
|
+
registration_id: nil
|
184
192
|
)
|
185
193
|
new(
|
186
194
|
id: id,
|
@@ -188,7 +196,7 @@ module RubyLsp
|
|
188
196
|
params: Interface::RegistrationParams.new(
|
189
197
|
registrations: [
|
190
198
|
Interface::Registration.new(
|
191
|
-
id:
|
199
|
+
id: registration_id || SecureRandom.uuid,
|
192
200
|
method: "workspace/didChangeWatchedFiles",
|
193
201
|
register_options: Interface::DidChangeWatchedFilesRegistrationOptions.new(
|
194
202
|
watchers: [
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-lsp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.23.
|
4
|
+
version: 0.23.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shopify
|
8
8
|
bindir: exe
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-
|
10
|
+
date: 2025-02-06 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: language_server-protocol
|