ruby-lsp 0.23.7 → 0.23.9
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-launcher +2 -2
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +4 -18
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +90 -6
- 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/index_test.rb +69 -0
- data/lib/ruby_indexer/test/reference_finder_test.rb +27 -2
- data/lib/ruby_lsp/internal.rb +2 -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/on_type_formatting.rb +1 -1
- 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 +53 -13
- data/lib/ruby_lsp/setup_bundler.rb +1 -2
- 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
|
data/exe/ruby-lsp-launcher
CHANGED
@@ -51,7 +51,7 @@ end
|
|
51
51
|
|
52
52
|
begin
|
53
53
|
# Wait until the composed Bundle is finished
|
54
|
-
Process.
|
54
|
+
_, status = Process.wait2(pid)
|
55
55
|
rescue Errno::ECHILD
|
56
56
|
# In theory, the child process can finish before we even get to the wait call, but that is not an error
|
57
57
|
end
|
@@ -105,7 +105,7 @@ end
|
|
105
105
|
# flow, we are not booting the LSP yet, just checking if the bundle is valid before rebooting
|
106
106
|
if reboot
|
107
107
|
# Use the exit status to signal to the server if composing the bundle succeeded
|
108
|
-
exit(install_error || setup_error ? 1 : 0)
|
108
|
+
exit(install_error || setup_error ? 1 : status&.exitstatus || 0)
|
109
109
|
end
|
110
110
|
|
111
111
|
# Now that the bundle is set up, we can begin actually launching the server. Note that `Bundler.setup` will have already
|
@@ -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:
|
@@ -120,8 +159,16 @@ module RubyIndexer
|
|
120
159
|
)]))
|
121
160
|
end
|
122
161
|
def first_unqualified_const(name)
|
162
|
+
# Look for an exact match first
|
123
163
|
_name, entries = @entries.find do |const_name, _entries|
|
124
|
-
const_name.end_with?(name)
|
164
|
+
const_name == name || const_name.end_with?("::#{name}")
|
165
|
+
end
|
166
|
+
|
167
|
+
# If an exact match is not found, then try to find a constant that ends with the name
|
168
|
+
unless entries
|
169
|
+
_name, entries = @entries.find do |const_name, _entries|
|
170
|
+
const_name.end_with?(name)
|
171
|
+
end
|
125
172
|
end
|
126
173
|
|
127
174
|
T.cast(
|
@@ -593,7 +640,7 @@ module RubyIndexer
|
|
593
640
|
entries = self[variable_name]&.grep(Entry::ClassVariable)
|
594
641
|
return unless entries&.any?
|
595
642
|
|
596
|
-
ancestors =
|
643
|
+
ancestors = linearized_attached_ancestors(owner_name)
|
597
644
|
return if ancestors.empty?
|
598
645
|
|
599
646
|
entries.select { |e| ancestors.include?(e.owner&.name) }
|
@@ -601,12 +648,33 @@ module RubyIndexer
|
|
601
648
|
|
602
649
|
# Returns a list of possible candidates for completion of instance variables for a given owner name. The name must
|
603
650
|
# include the `@` prefix
|
604
|
-
sig
|
651
|
+
sig do
|
652
|
+
params(name: String, owner_name: String).returns(T::Array[T.any(Entry::InstanceVariable, Entry::ClassVariable)])
|
653
|
+
end
|
605
654
|
def instance_variable_completion_candidates(name, owner_name)
|
606
|
-
entries = T.cast(prefix_search(name).flatten, T::Array[Entry::InstanceVariable])
|
655
|
+
entries = T.cast(prefix_search(name).flatten, T::Array[T.any(Entry::InstanceVariable, Entry::ClassVariable)])
|
656
|
+
# Avoid wasting time linearizing ancestors if we didn't find anything
|
657
|
+
return entries if entries.empty?
|
658
|
+
|
607
659
|
ancestors = linearized_ancestors_of(owner_name)
|
608
660
|
|
609
|
-
|
661
|
+
instance_variables, class_variables = entries.partition { |e| e.is_a?(Entry::InstanceVariable) }
|
662
|
+
variables = instance_variables.select { |e| ancestors.any?(e.owner&.name) }
|
663
|
+
|
664
|
+
# Class variables are only owned by the attached class in our representation. If the owner is in a singleton
|
665
|
+
# context, we have to search for ancestors of the attached class
|
666
|
+
if class_variables.any?
|
667
|
+
name_parts = owner_name.split("::")
|
668
|
+
|
669
|
+
if name_parts.last&.start_with?("<Class:")
|
670
|
+
attached_name = T.must(name_parts[0..-2]).join("::")
|
671
|
+
attached_ancestors = linearized_ancestors_of(attached_name)
|
672
|
+
variables.concat(class_variables.select { |e| attached_ancestors.any?(e.owner&.name) })
|
673
|
+
else
|
674
|
+
variables.concat(class_variables.select { |e| ancestors.any?(e.owner&.name) })
|
675
|
+
end
|
676
|
+
end
|
677
|
+
|
610
678
|
variables.uniq!(&:name)
|
611
679
|
variables
|
612
680
|
end
|
@@ -614,8 +682,10 @@ module RubyIndexer
|
|
614
682
|
sig { params(name: String, owner_name: String).returns(T::Array[Entry::ClassVariable]) }
|
615
683
|
def class_variable_completion_candidates(name, owner_name)
|
616
684
|
entries = T.cast(prefix_search(name).flatten, T::Array[Entry::ClassVariable])
|
617
|
-
ancestors
|
685
|
+
# Avoid wasting time linearizing ancestors if we didn't find anything
|
686
|
+
return entries if entries.empty?
|
618
687
|
|
688
|
+
ancestors = linearized_attached_ancestors(owner_name)
|
619
689
|
variables = entries.select { |e| ancestors.any?(e.owner&.name) }
|
620
690
|
variables.uniq!(&:name)
|
621
691
|
variables
|
@@ -717,6 +787,20 @@ module RubyIndexer
|
|
717
787
|
|
718
788
|
private
|
719
789
|
|
790
|
+
# Always returns the linearized ancestors for the attached class, regardless of whether `name` refers to a singleton
|
791
|
+
# or attached namespace
|
792
|
+
sig { params(name: String).returns(T::Array[String]) }
|
793
|
+
def linearized_attached_ancestors(name)
|
794
|
+
name_parts = name.split("::")
|
795
|
+
|
796
|
+
if name_parts.last&.start_with?("<Class:")
|
797
|
+
attached_name = T.must(name_parts[0..-2]).join("::")
|
798
|
+
linearized_ancestors_of(attached_name)
|
799
|
+
else
|
800
|
+
linearized_ancestors_of(name)
|
801
|
+
end
|
802
|
+
end
|
803
|
+
|
720
804
|
# Runs the registered included hooks
|
721
805
|
sig { params(fully_qualified_name: String, nesting: T::Array[String]).void }
|
722
806
|
def run_included_hooks(fully_qualified_name, nesting)
|
@@ -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
|
@@ -1561,6 +1561,23 @@ module RubyIndexer
|
|
1561
1561
|
assert_equal("Foo::Bar", entry.name)
|
1562
1562
|
end
|
1563
1563
|
|
1564
|
+
def test_first_unqualified_const_prefers_exact_matches
|
1565
|
+
index(<<~RUBY)
|
1566
|
+
module Foo
|
1567
|
+
class ParseResultType
|
1568
|
+
end
|
1569
|
+
end
|
1570
|
+
|
1571
|
+
module Namespace
|
1572
|
+
class Type
|
1573
|
+
end
|
1574
|
+
end
|
1575
|
+
RUBY
|
1576
|
+
|
1577
|
+
entry = T.must(@index.first_unqualified_const("Type")&.first)
|
1578
|
+
assert_equal("Namespace::Type", entry.name)
|
1579
|
+
end
|
1580
|
+
|
1564
1581
|
def test_completion_does_not_duplicate_overridden_methods
|
1565
1582
|
index(<<~RUBY)
|
1566
1583
|
class Foo
|
@@ -2092,5 +2109,57 @@ module RubyIndexer
|
|
2092
2109
|
refute_nil(entry, "Expected indexer to be able to handle unsaved URIs")
|
2093
2110
|
assert_equal("I added this comment!", entry.comments)
|
2094
2111
|
end
|
2112
|
+
|
2113
|
+
def test_instance_variable_completion_returns_class_variables_too
|
2114
|
+
index(<<~RUBY)
|
2115
|
+
class Parent
|
2116
|
+
@@abc = 123
|
2117
|
+
end
|
2118
|
+
|
2119
|
+
class Child < Parent
|
2120
|
+
@@adf = 123
|
2121
|
+
|
2122
|
+
def self.do
|
2123
|
+
end
|
2124
|
+
end
|
2125
|
+
RUBY
|
2126
|
+
|
2127
|
+
abc, adf = @index.instance_variable_completion_candidates("@", "Child::<Class:Child>")
|
2128
|
+
|
2129
|
+
refute_nil(abc)
|
2130
|
+
refute_nil(adf)
|
2131
|
+
|
2132
|
+
assert_equal("@@abc", abc.name)
|
2133
|
+
assert_equal("@@adf", adf.name)
|
2134
|
+
end
|
2135
|
+
|
2136
|
+
def test_class_variable_completion_from_singleton_context
|
2137
|
+
index(<<~RUBY)
|
2138
|
+
class Foo
|
2139
|
+
@@hello = 123
|
2140
|
+
|
2141
|
+
def self.do
|
2142
|
+
end
|
2143
|
+
end
|
2144
|
+
RUBY
|
2145
|
+
|
2146
|
+
candidates = @index.class_variable_completion_candidates("@@", "Foo::<Class:Foo>")
|
2147
|
+
refute_empty(candidates)
|
2148
|
+
|
2149
|
+
assert_equal("@@hello", candidates.first&.name)
|
2150
|
+
end
|
2151
|
+
|
2152
|
+
def test_resolve_class_variable_in_singleton_context
|
2153
|
+
index(<<~RUBY)
|
2154
|
+
class Foo
|
2155
|
+
@@hello = 123
|
2156
|
+
end
|
2157
|
+
RUBY
|
2158
|
+
|
2159
|
+
candidates = @index.resolve_class_variable("@@hello", "Foo::<Class:Foo>")
|
2160
|
+
refute_empty(candidates)
|
2161
|
+
|
2162
|
+
assert_equal("@@hello", candidates.first&.name)
|
2163
|
+
end
|
2095
2164
|
end
|
2096
2165
|
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
|
@@ -170,7 +170,7 @@ module RubyLsp
|
|
170
170
|
|
171
171
|
sig { params(line: Integer, character: Integer).void }
|
172
172
|
def move_cursor_to(line, character)
|
173
|
-
return unless
|
173
|
+
return unless /Visual Studio Code|Cursor/.match?(@client_name)
|
174
174
|
|
175
175
|
position = Interface::Position.new(
|
176
176
|
line: line,
|
@@ -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
|
@@ -1036,6 +1050,10 @@ module RubyLsp
|
|
1036
1050
|
when Constant::FileChangeType::DELETED
|
1037
1051
|
index.delete(uri)
|
1038
1052
|
end
|
1053
|
+
rescue Errno::ENOENT
|
1054
|
+
# If a file is created and then delete immediately afterwards, we will process the created notification before we
|
1055
|
+
# receive the deleted one, but the file no longer exists. This may happen when running a test suite that creates
|
1056
|
+
# and deletes files automatically.
|
1039
1057
|
end
|
1040
1058
|
|
1041
1059
|
sig { params(uri: URI::Generic).void }
|
@@ -1127,7 +1145,12 @@ module RubyLsp
|
|
1127
1145
|
|
1128
1146
|
sig { params(message: T::Hash[Symbol, T.untyped]).void }
|
1129
1147
|
def workspace_dependencies(message)
|
1130
|
-
|
1148
|
+
unless @global_state.top_level_bundle
|
1149
|
+
send_message(Result.new(id: message[:id], response: []))
|
1150
|
+
return
|
1151
|
+
end
|
1152
|
+
|
1153
|
+
response = begin
|
1131
1154
|
Bundler.with_original_env do
|
1132
1155
|
definition = Bundler.definition
|
1133
1156
|
dep_keys = definition.locked_deps.keys.to_set
|
@@ -1141,7 +1164,7 @@ module RubyLsp
|
|
1141
1164
|
}
|
1142
1165
|
end
|
1143
1166
|
end
|
1144
|
-
|
1167
|
+
rescue Bundler::GemNotFound
|
1145
1168
|
[]
|
1146
1169
|
end
|
1147
1170
|
|
@@ -1286,25 +1309,40 @@ module RubyLsp
|
|
1286
1309
|
addon.handle_window_show_message_response(result[:title])
|
1287
1310
|
end
|
1288
1311
|
|
1289
|
-
|
1312
|
+
# NOTE: all servers methods are void because they can produce several messages for the client. The only reason this
|
1313
|
+
# method returns the created thread is to that we can join it in tests and avoid flakiness. The implementation is
|
1314
|
+
# not supposed to rely on the return of this method
|
1315
|
+
sig { params(message: T::Hash[Symbol, T.untyped]).returns(T.nilable(Thread)) }
|
1290
1316
|
def compose_bundle(message)
|
1291
1317
|
already_composed_path = File.join(@global_state.workspace_path, ".ruby-lsp", "bundle_is_composed")
|
1292
|
-
command = "#{Gem.ruby} #{File.expand_path("../../exe/ruby-lsp-launcher", __dir__)} #{@global_state.workspace_uri}"
|
1293
1318
|
id = message[:id]
|
1294
1319
|
|
1295
1320
|
begin
|
1296
|
-
Bundler
|
1321
|
+
Bundler.with_original_env do
|
1322
|
+
Bundler::LockfileParser.new(Bundler.default_lockfile.read)
|
1323
|
+
end
|
1297
1324
|
rescue Bundler::LockfileError => e
|
1298
1325
|
send_message(Error.new(id: id, code: BUNDLE_COMPOSE_FAILED_CODE, message: e.message))
|
1299
1326
|
return
|
1327
|
+
rescue Bundler::GemfileNotFound, Errno::ENOENT
|
1328
|
+
# We still compose the bundle if there's no Gemfile or if the lockfile got deleted
|
1300
1329
|
end
|
1301
1330
|
|
1302
1331
|
# We compose the bundle in a thread so that the LSP continues to work while we're checking for its validity. Once
|
1303
1332
|
# we return the response back to the editor, then the restart is triggered
|
1304
1333
|
Thread.new do
|
1305
1334
|
send_log_message("Recomposing the bundle ahead of restart")
|
1306
|
-
|
1307
|
-
|
1335
|
+
|
1336
|
+
_stdout, stderr, status = Bundler.with_unbundled_env do
|
1337
|
+
Open3.capture3(
|
1338
|
+
Gem.ruby,
|
1339
|
+
"-I",
|
1340
|
+
File.dirname(T.must(__dir__)),
|
1341
|
+
File.expand_path("../../exe/ruby-lsp-launcher", __dir__),
|
1342
|
+
@global_state.workspace_uri.to_s,
|
1343
|
+
chdir: @global_state.workspace_path,
|
1344
|
+
)
|
1345
|
+
end
|
1308
1346
|
|
1309
1347
|
if status&.exitstatus == 0
|
1310
1348
|
# Create a signal for the restart that it can skip composing the bundle and launch directly
|
@@ -1313,7 +1351,9 @@ module RubyLsp
|
|
1313
1351
|
else
|
1314
1352
|
# This special error code makes the extension avoid restarting in case we already know that the composed
|
1315
1353
|
# bundle is not valid
|
1316
|
-
send_message(
|
1354
|
+
send_message(
|
1355
|
+
Error.new(id: id, code: BUNDLE_COMPOSE_FAILED_CODE, message: "Failed to compose bundle\n#{stderr}"),
|
1356
|
+
)
|
1317
1357
|
end
|
1318
1358
|
end
|
1319
1359
|
end
|
@@ -241,8 +241,7 @@ module RubyLsp
|
|
241
241
|
|
242
242
|
# If either the Gemfile or the lockfile have been modified during the process of setting up the bundle, retry
|
243
243
|
# composing the bundle from scratch
|
244
|
-
|
245
|
-
if @gemfile && @lockfile
|
244
|
+
if @gemfile&.exist? && @lockfile&.exist?
|
246
245
|
current_gemfile_hash = Digest::SHA256.hexdigest(@gemfile.read)
|
247
246
|
current_lockfile_hash = Digest::SHA256.hexdigest(@lockfile.read)
|
248
247
|
|
@@ -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
|