ruby-lsp 0.23.8 → 0.23.10
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/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/global_state.rb +5 -0
- 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 +5 -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 +40 -10
- 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: 3a0d78055f2f6bf861e581a5b5a2d1321d3a8452b23d638becb6e64da925d842
|
4
|
+
data.tar.gz: '0397aa5a123ed08757606db1be3690b50cba847a372f52da25fb64a6fb5816ce'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a14d101646bd32467724aeedb60892c5c06e775a57d749623cc1e76dbc42ee6d82eca10e2fa1149900365eff59ee57c20300bb043a3fdcf1330961b6bbd94918
|
7
|
+
data.tar.gz: 23b339c5854620771e7b505381dad64cf3e8d89ed2850a19c7a299e72d2363202e1e74327c05d27d8fb7fae508abb7141e5a858319b2841c8c914233e28e076e
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.23.
|
1
|
+
0.23.10
|
@@ -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
|
@@ -32,6 +32,9 @@ module RubyLsp
|
|
32
32
|
sig { returns(URI::Generic) }
|
33
33
|
attr_reader :workspace_uri
|
34
34
|
|
35
|
+
sig { returns(T.nilable(String)) }
|
36
|
+
attr_reader :telemetry_machine_id
|
37
|
+
|
35
38
|
sig { void }
|
36
39
|
def initialize
|
37
40
|
@workspace_uri = T.let(URI::Generic.from_path(path: Dir.pwd), URI::Generic)
|
@@ -57,6 +60,7 @@ module RubyLsp
|
|
57
60
|
@client_capabilities = T.let(ClientCapabilities.new, ClientCapabilities)
|
58
61
|
@enabled_feature_flags = T.let({}, T::Hash[Symbol, T::Boolean])
|
59
62
|
@mutex = T.let(Mutex.new, Mutex)
|
63
|
+
@telemetry_machine_id = T.let(nil, T.nilable(String))
|
60
64
|
end
|
61
65
|
|
62
66
|
sig { type_parameters(:T).params(block: T.proc.returns(T.type_parameter(:T))).returns(T.type_parameter(:T)) }
|
@@ -175,6 +179,7 @@ module RubyLsp
|
|
175
179
|
enabled_flags = options.dig(:initializationOptions, :enabledFeatureFlags)
|
176
180
|
@enabled_feature_flags = enabled_flags if enabled_flags
|
177
181
|
|
182
|
+
@telemetry_machine_id = options.dig(:initializationOptions, :telemetryMachineId)
|
178
183
|
notifications
|
179
184
|
end
|
180
185
|
|
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
|
@@ -471,6 +471,9 @@ module RubyLsp
|
|
471
471
|
path_node_to_complete,
|
472
472
|
)
|
473
473
|
end
|
474
|
+
rescue Errno::EPERM
|
475
|
+
# If the user writes a relative require pointing to a path that the editor has no permissions to read, then glob
|
476
|
+
# might fail with EPERM
|
474
477
|
end
|
475
478
|
|
476
479
|
sig { params(node: Prism::CallNode, name: String).void }
|
@@ -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
|
@@ -1018,7 +1032,14 @@ module RubyLsp
|
|
1018
1032
|
end
|
1019
1033
|
end
|
1020
1034
|
|
1021
|
-
Addon.file_watcher_addons.each
|
1035
|
+
Addon.file_watcher_addons.each do |addon|
|
1036
|
+
T.unsafe(addon).workspace_did_change_watched_files(changes)
|
1037
|
+
rescue => e
|
1038
|
+
send_log_message(
|
1039
|
+
"Error in #{addon.name} add-on while processing watched file notifications: #{e.full_message}",
|
1040
|
+
type: Constant::MessageType::ERROR,
|
1041
|
+
)
|
1042
|
+
end
|
1022
1043
|
end
|
1023
1044
|
|
1024
1045
|
sig { params(index: RubyIndexer::Index, file_path: String, change_type: Integer).void }
|
@@ -1030,9 +1051,13 @@ module RubyLsp
|
|
1030
1051
|
|
1031
1052
|
case change_type
|
1032
1053
|
when Constant::FileChangeType::CREATED
|
1033
|
-
|
1054
|
+
# If we receive a late created notification for a file that has already been claimed by the client, we want to
|
1055
|
+
# handle change for that URI so that the require path tree is updated
|
1056
|
+
@store.key?(uri) ? index.handle_change(uri, content) : index.index_single(uri, content)
|
1034
1057
|
when Constant::FileChangeType::CHANGED
|
1035
|
-
|
1058
|
+
# We only handle changes on file watched notifications if the client is not the one managing this URI.
|
1059
|
+
# Otherwise, these changes are handled when running the combined requests
|
1060
|
+
index.handle_change(uri, content) unless @store.key?(uri)
|
1036
1061
|
when Constant::FileChangeType::DELETED
|
1037
1062
|
index.delete(uri)
|
1038
1063
|
end
|
@@ -1131,7 +1156,12 @@ module RubyLsp
|
|
1131
1156
|
|
1132
1157
|
sig { params(message: T::Hash[Symbol, T.untyped]).void }
|
1133
1158
|
def workspace_dependencies(message)
|
1134
|
-
|
1159
|
+
unless @global_state.top_level_bundle
|
1160
|
+
send_message(Result.new(id: message[:id], response: []))
|
1161
|
+
return
|
1162
|
+
end
|
1163
|
+
|
1164
|
+
response = begin
|
1135
1165
|
Bundler.with_original_env do
|
1136
1166
|
definition = Bundler.definition
|
1137
1167
|
dep_keys = definition.locked_deps.keys.to_set
|
@@ -1145,7 +1175,7 @@ module RubyLsp
|
|
1145
1175
|
}
|
1146
1176
|
end
|
1147
1177
|
end
|
1148
|
-
|
1178
|
+
rescue Bundler::GemNotFound
|
1149
1179
|
[]
|
1150
1180
|
end
|
1151
1181
|
|
@@ -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.10
|
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-10 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: language_server-protocol
|