ruby-lsp 0.23.5 → 0.23.7
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/README.md +2 -2
- data/VERSION +1 -1
- data/exe/ruby-lsp +3 -3
- data/exe/ruby-lsp-launcher +24 -7
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +11 -3
- data/lib/ruby_indexer/lib/ruby_indexer/reference_finder.rb +56 -0
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +19 -0
- data/lib/ruby_indexer/test/reference_finder_test.rb +63 -0
- data/lib/ruby_lsp/client_capabilities.rb +7 -1
- data/lib/ruby_lsp/global_state.rb +28 -3
- data/lib/ruby_lsp/listeners/document_highlight.rb +3 -2
- data/lib/ruby_lsp/listeners/document_link.rb +11 -2
- data/lib/ruby_lsp/load_sorbet.rb +3 -3
- data/lib/ruby_lsp/requests/code_actions.rb +1 -1
- data/lib/ruby_lsp/requests/diagnostics.rb +1 -1
- data/lib/ruby_lsp/requests/folding_ranges.rb +2 -6
- data/lib/ruby_lsp/requests/formatting.rb +2 -6
- data/lib/ruby_lsp/requests/on_type_formatting.rb +1 -1
- data/lib/ruby_lsp/requests/references.rb +26 -0
- data/lib/ruby_lsp/requests/semantic_highlighting.rb +1 -1
- data/lib/ruby_lsp/server.rb +49 -11
- data/lib/ruby_lsp/setup_bundler.rb +18 -0
- data/lib/ruby_lsp/utils.rb +9 -3
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 46b72d1b9ba142496f64035b6b1ad54d096c0a28ae52311612bd118bc49e2956
|
4
|
+
data.tar.gz: 525dc46dcfc71ee57da51955a4034ceace3bbdd1eca7942b542ab2cd32d42ad4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '06690b76d284ca6abc05fc0e836af32a54c0a3cedea1fd53071648330a3b3b6e6d1a59ef205127f54a88e8cd3ca07c3840f25f1293e37737d705b2ea41719cb1'
|
7
|
+
data.tar.gz: bd6104ef180f122d0bb9d6d751213c43f0d19d0f203deb50e5d94583ff0aaf85559a040ca0371fc24506ff11a0d2742c14f1277226cc34d3a09d02730d46aed7
|
data/README.md
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
|
5
5
|
[](https://github.com/Shopify/ruby-lsp/actions/workflows/ci.yml)
|
6
6
|
[](https://marketplace.visualstudio.com/items?itemName=Shopify.ruby-lsp)
|
7
|
-
[](https://join.slack.com/t/ruby-dx/shared_invite/zt-
|
7
|
+
[](https://join.slack.com/t/ruby-dx/shared_invite/zt-2yd77ayis-yAiVc1TX_kH0mHMBbi89dA)
|
8
8
|
|
9
9
|
# Ruby LSP
|
10
10
|
|
@@ -13,7 +13,7 @@ for Ruby, used to improve rich features in editors. It is a part of a wider goal
|
|
13
13
|
experience to Ruby developers using modern standards for cross-editor features, documentation and debugging.
|
14
14
|
|
15
15
|
Want to discuss Ruby developer experience? Consider joining the public
|
16
|
-
[Ruby DX Slack workspace](https://join.slack.com/t/ruby-dx/shared_invite/zt-
|
16
|
+
[Ruby DX Slack workspace](https://join.slack.com/t/ruby-dx/shared_invite/zt-2yd77ayis-yAiVc1TX_kH0mHMBbi89dA).
|
17
17
|
|
18
18
|
## Getting Started
|
19
19
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.23.
|
1
|
+
0.23.7
|
data/exe/ruby-lsp
CHANGED
@@ -64,9 +64,9 @@ if ENV["BUNDLE_GEMFILE"].nil?
|
|
64
64
|
# which gives us the opportunity to control which specs are activated and enter degraded mode if any gems failed to
|
65
65
|
# install rather than failing to boot the server completely
|
66
66
|
if options[:launcher]
|
67
|
-
|
68
|
-
|
69
|
-
exit exec(
|
67
|
+
flags = []
|
68
|
+
flags << "--debug" if options[:debug]
|
69
|
+
exit exec(Gem.ruby, File.expand_path("ruby-lsp-launcher", __dir__), *flags)
|
70
70
|
end
|
71
71
|
|
72
72
|
require_relative "../lib/ruby_lsp/setup_bundler"
|
data/exe/ruby-lsp-launcher
CHANGED
@@ -8,14 +8,24 @@
|
|
8
8
|
|
9
9
|
setup_error = nil
|
10
10
|
install_error = nil
|
11
|
+
reboot = false
|
11
12
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
13
|
+
workspace_uri = ARGV.first
|
14
|
+
|
15
|
+
raw_initialize = if workspace_uri && !workspace_uri.start_with?("--")
|
16
|
+
# If there's an argument without `--`, then it's the server asking to compose the bundle and passing to this
|
17
|
+
# executable the workspace URI. We can't require gems at this point, so we built a fake initialize request manually
|
18
|
+
reboot = true
|
19
|
+
"{\"params\":{\"workspaceFolders\":[{\"uri\":\"#{workspace_uri}\"}]}}"
|
20
|
+
else
|
21
|
+
# Read the initialize request before even starting the server. We need to do this to figure out the workspace URI.
|
22
|
+
# Editors are not required to spawn the language server process on the same directory as the workspace URI, so we need
|
23
|
+
# to ensure that we're setting up the bundle in the right place
|
24
|
+
$stdin.binmode
|
25
|
+
headers = $stdin.gets("\r\n\r\n")
|
26
|
+
content_length = headers[/Content-Length: (\d+)/i, 1].to_i
|
27
|
+
$stdin.read(content_length)
|
28
|
+
end
|
19
29
|
|
20
30
|
# Compose the Ruby LSP bundle in a forked process so that we can require gems without polluting the main process
|
21
31
|
# `$LOAD_PATH` and `Gem.loaded_specs`. Windows doesn't support forking, so we need a separate path to support it
|
@@ -91,6 +101,13 @@ rescue StandardError => e
|
|
91
101
|
$LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
|
92
102
|
end
|
93
103
|
|
104
|
+
# When performing a lockfile re-boot, this executable is invoked to set up the composed bundle ahead of time. In this
|
105
|
+
# flow, we are not booting the LSP yet, just checking if the bundle is valid before rebooting
|
106
|
+
if reboot
|
107
|
+
# Use the exit status to signal to the server if composing the bundle succeeded
|
108
|
+
exit(install_error || setup_error ? 1 : 0)
|
109
|
+
end
|
110
|
+
|
94
111
|
# Now that the bundle is set up, we can begin actually launching the server. Note that `Bundler.setup` will have already
|
95
112
|
# configured the load path using the version of the Ruby LSP present in the composed bundle. Do not push any Ruby LSP
|
96
113
|
# paths into the load path manually or we may end up requiring the wrong version of the gem
|
@@ -143,7 +143,7 @@ module RubyIndexer
|
|
143
143
|
|
144
144
|
if current_owner
|
145
145
|
expression = node.expression
|
146
|
-
name = (expression.is_a?(Prism::SelfNode) ? "<Class:#{
|
146
|
+
name = (expression.is_a?(Prism::SelfNode) ? "<Class:#{last_name_in_stack}>" : "<Class:#{expression.slice}>")
|
147
147
|
real_nesting = actual_nesting(name)
|
148
148
|
|
149
149
|
existing_entries = T.cast(@index[real_nesting.join("::")], T.nilable(T::Array[Entry::SingletonClass]))
|
@@ -376,7 +376,6 @@ module RubyIndexer
|
|
376
376
|
))
|
377
377
|
|
378
378
|
@owner_stack << singleton
|
379
|
-
@stack << "<Class:#{@stack.last}>"
|
380
379
|
end
|
381
380
|
end
|
382
381
|
|
@@ -386,7 +385,6 @@ module RubyIndexer
|
|
386
385
|
|
387
386
|
if node.receiver.is_a?(Prism::SelfNode)
|
388
387
|
@owner_stack.pop
|
389
|
-
@stack.pop
|
390
388
|
end
|
391
389
|
end
|
392
390
|
|
@@ -1127,5 +1125,15 @@ module RubyIndexer
|
|
1127
1125
|
@index.add(entry)
|
1128
1126
|
@stack << short_name
|
1129
1127
|
end
|
1128
|
+
|
1129
|
+
# Returns the last name in the stack not as we found it, but in terms of declared constants. For example, if the
|
1130
|
+
# last entry in the stack is a compact namespace like `Foo::Bar`, then the last name is `Bar`
|
1131
|
+
sig { returns(T.nilable(String)) }
|
1132
|
+
def last_name_in_stack
|
1133
|
+
name = @stack.last
|
1134
|
+
return unless name
|
1135
|
+
|
1136
|
+
name.split("::").last
|
1137
|
+
end
|
1130
1138
|
end
|
1131
1139
|
end
|
@@ -37,6 +37,19 @@ module RubyIndexer
|
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
40
|
+
class InstanceVariableTarget < Target
|
41
|
+
extend T::Sig
|
42
|
+
|
43
|
+
sig { returns(String) }
|
44
|
+
attr_reader :name
|
45
|
+
|
46
|
+
sig { params(name: String).void }
|
47
|
+
def initialize(name)
|
48
|
+
super()
|
49
|
+
@name = name
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
40
53
|
class Reference
|
41
54
|
extend T::Sig
|
42
55
|
|
@@ -94,6 +107,12 @@ module RubyIndexer
|
|
94
107
|
:on_constant_or_write_node_enter,
|
95
108
|
:on_constant_and_write_node_enter,
|
96
109
|
:on_constant_operator_write_node_enter,
|
110
|
+
:on_instance_variable_read_node_enter,
|
111
|
+
:on_instance_variable_write_node_enter,
|
112
|
+
:on_instance_variable_and_write_node_enter,
|
113
|
+
:on_instance_variable_operator_write_node_enter,
|
114
|
+
:on_instance_variable_or_write_node_enter,
|
115
|
+
:on_instance_variable_target_node_enter,
|
97
116
|
:on_call_node_enter,
|
98
117
|
)
|
99
118
|
end
|
@@ -262,6 +281,36 @@ module RubyIndexer
|
|
262
281
|
end
|
263
282
|
end
|
264
283
|
|
284
|
+
sig { params(node: Prism::InstanceVariableReadNode).void }
|
285
|
+
def on_instance_variable_read_node_enter(node)
|
286
|
+
collect_instance_variable_references(node.name.to_s, node.location, false)
|
287
|
+
end
|
288
|
+
|
289
|
+
sig { params(node: Prism::InstanceVariableWriteNode).void }
|
290
|
+
def on_instance_variable_write_node_enter(node)
|
291
|
+
collect_instance_variable_references(node.name.to_s, node.name_loc, true)
|
292
|
+
end
|
293
|
+
|
294
|
+
sig { params(node: Prism::InstanceVariableAndWriteNode).void }
|
295
|
+
def on_instance_variable_and_write_node_enter(node)
|
296
|
+
collect_instance_variable_references(node.name.to_s, node.name_loc, true)
|
297
|
+
end
|
298
|
+
|
299
|
+
sig { params(node: Prism::InstanceVariableOperatorWriteNode).void }
|
300
|
+
def on_instance_variable_operator_write_node_enter(node)
|
301
|
+
collect_instance_variable_references(node.name.to_s, node.name_loc, true)
|
302
|
+
end
|
303
|
+
|
304
|
+
sig { params(node: Prism::InstanceVariableOrWriteNode).void }
|
305
|
+
def on_instance_variable_or_write_node_enter(node)
|
306
|
+
collect_instance_variable_references(node.name.to_s, node.name_loc, true)
|
307
|
+
end
|
308
|
+
|
309
|
+
sig { params(node: Prism::InstanceVariableTargetNode).void }
|
310
|
+
def on_instance_variable_target_node_enter(node)
|
311
|
+
collect_instance_variable_references(node.name.to_s, node.location, true)
|
312
|
+
end
|
313
|
+
|
265
314
|
sig { params(node: Prism::CallNode).void }
|
266
315
|
def on_call_node_enter(node)
|
267
316
|
if @target.is_a?(MethodTarget) && (name = node.name.to_s) == @target.method_name
|
@@ -305,6 +354,13 @@ module RubyIndexer
|
|
305
354
|
end
|
306
355
|
end
|
307
356
|
|
357
|
+
sig { params(name: String, location: Prism::Location, declaration: T::Boolean).void }
|
358
|
+
def collect_instance_variable_references(name, location, declaration)
|
359
|
+
return unless @target.is_a?(InstanceVariableTarget) && name == @target.name
|
360
|
+
|
361
|
+
@references << Reference.new(name, location, declaration: declaration)
|
362
|
+
end
|
363
|
+
|
308
364
|
sig do
|
309
365
|
params(
|
310
366
|
node: T.any(
|
@@ -647,5 +647,24 @@ module RubyIndexer
|
|
647
647
|
entry = @index["Foo"].first
|
648
648
|
assert_empty(entry.comments)
|
649
649
|
end
|
650
|
+
|
651
|
+
def test_singleton_inside_compact_namespace
|
652
|
+
index(<<~RUBY)
|
653
|
+
module Foo::Bar
|
654
|
+
class << self
|
655
|
+
def baz; end
|
656
|
+
end
|
657
|
+
end
|
658
|
+
RUBY
|
659
|
+
|
660
|
+
# Verify we didn't index the incorrect name
|
661
|
+
assert_nil(@index["Foo::Bar::<Class:Foo::Bar>"])
|
662
|
+
|
663
|
+
# Verify we indexed the correct name
|
664
|
+
assert_entry("Foo::Bar::<Class:Bar>", Entry::SingletonClass, "/fake/path/foo.rb:1-2:3-5")
|
665
|
+
|
666
|
+
method = @index["baz"]&.first
|
667
|
+
assert_equal("Foo::Bar::<Class:Bar>", method.owner.name)
|
668
|
+
end
|
650
669
|
end
|
651
670
|
end
|
@@ -216,6 +216,64 @@ module RubyIndexer
|
|
216
216
|
assert_equal(11, refs[2].location.start_line)
|
217
217
|
end
|
218
218
|
|
219
|
+
def test_finds_instance_variable_read_references
|
220
|
+
refs = find_instance_variable_references("@foo", <<~RUBY)
|
221
|
+
class Foo
|
222
|
+
def foo
|
223
|
+
@foo
|
224
|
+
end
|
225
|
+
end
|
226
|
+
RUBY
|
227
|
+
assert_equal(1, refs.size)
|
228
|
+
|
229
|
+
assert_equal("@foo", refs[0].name)
|
230
|
+
assert_equal(3, refs[0].location.start_line)
|
231
|
+
end
|
232
|
+
|
233
|
+
def test_finds_instance_variable_write_references
|
234
|
+
refs = find_instance_variable_references("@foo", <<~RUBY)
|
235
|
+
class Foo
|
236
|
+
def write
|
237
|
+
@foo = 1
|
238
|
+
@foo &&= 2
|
239
|
+
@foo ||= 3
|
240
|
+
@foo += 4
|
241
|
+
@foo, @bar = []
|
242
|
+
end
|
243
|
+
end
|
244
|
+
RUBY
|
245
|
+
assert_equal(5, refs.size)
|
246
|
+
|
247
|
+
assert_equal(["@foo"], refs.map(&:name).uniq)
|
248
|
+
assert_equal(3, refs[0].location.start_line)
|
249
|
+
assert_equal(4, refs[1].location.start_line)
|
250
|
+
assert_equal(5, refs[2].location.start_line)
|
251
|
+
assert_equal(6, refs[3].location.start_line)
|
252
|
+
assert_equal(7, refs[4].location.start_line)
|
253
|
+
end
|
254
|
+
|
255
|
+
def test_finds_instance_variable_references_ignore_context
|
256
|
+
refs = find_instance_variable_references("@name", <<~RUBY)
|
257
|
+
class Foo
|
258
|
+
def name
|
259
|
+
@name = "foo"
|
260
|
+
end
|
261
|
+
end
|
262
|
+
class Bar
|
263
|
+
def name
|
264
|
+
@name = "bar"
|
265
|
+
end
|
266
|
+
end
|
267
|
+
RUBY
|
268
|
+
assert_equal(2, refs.size)
|
269
|
+
|
270
|
+
assert_equal("@name", refs[0].name)
|
271
|
+
assert_equal(3, refs[0].location.start_line)
|
272
|
+
|
273
|
+
assert_equal("@name", refs[1].name)
|
274
|
+
assert_equal(8, refs[1].location.start_line)
|
275
|
+
end
|
276
|
+
|
219
277
|
private
|
220
278
|
|
221
279
|
def find_const_references(const_name, source)
|
@@ -228,6 +286,11 @@ module RubyIndexer
|
|
228
286
|
find_references(target, source)
|
229
287
|
end
|
230
288
|
|
289
|
+
def find_instance_variable_references(instance_variable_name, source)
|
290
|
+
target = ReferenceFinder::InstanceVariableTarget.new(instance_variable_name)
|
291
|
+
find_references(target, source)
|
292
|
+
end
|
293
|
+
|
231
294
|
def find_references(target, source)
|
232
295
|
file_path = "/fake.rb"
|
233
296
|
index = Index.new
|
@@ -11,7 +11,8 @@ module RubyLsp
|
|
11
11
|
attr_reader :supports_watching_files,
|
12
12
|
:supports_request_delegation,
|
13
13
|
:window_show_message_supports_extra_properties,
|
14
|
-
:supports_progress
|
14
|
+
:supports_progress,
|
15
|
+
:supports_diagnostic_refresh
|
15
16
|
|
16
17
|
sig { void }
|
17
18
|
def initialize
|
@@ -32,6 +33,9 @@ module RubyLsp
|
|
32
33
|
|
33
34
|
# The editor supports displaying progress requests
|
34
35
|
@supports_progress = T.let(false, T::Boolean)
|
36
|
+
|
37
|
+
# The editor supports server initiated refresh for diagnostics
|
38
|
+
@supports_diagnostic_refresh = T.let(false, T::Boolean)
|
35
39
|
end
|
36
40
|
|
37
41
|
sig { params(capabilities: T::Hash[Symbol, T.untyped]).void }
|
@@ -57,6 +61,8 @@ module RubyLsp
|
|
57
61
|
|
58
62
|
progress = capabilities.dig(:window, :workDoneProgress)
|
59
63
|
@supports_progress = progress if progress
|
64
|
+
|
65
|
+
@supports_diagnostic_refresh = workspace_capabilities.dig(:diagnostics, :refreshSupport) || false
|
60
66
|
end
|
61
67
|
|
62
68
|
sig { returns(T::Boolean) }
|
@@ -94,6 +94,8 @@ module RubyLsp
|
|
94
94
|
@workspace_uri = URI(workspace_uri) if workspace_uri
|
95
95
|
|
96
96
|
specified_formatter = options.dig(:initializationOptions, :formatter)
|
97
|
+
rubocop_has_addon = defined?(::RuboCop::Version::STRING) &&
|
98
|
+
Gem::Requirement.new(">= 1.70.0").satisfied_by?(Gem::Version.new(::RuboCop::Version::STRING))
|
97
99
|
|
98
100
|
if specified_formatter
|
99
101
|
@formatter = specified_formatter
|
@@ -101,6 +103,12 @@ module RubyLsp
|
|
101
103
|
if specified_formatter != "auto"
|
102
104
|
notifications << Notification.window_log_message("Using formatter specified by user: #{@formatter}")
|
103
105
|
end
|
106
|
+
|
107
|
+
# If the user had originally configured to use `rubocop`, but their version doesn't provide the add-on yet,
|
108
|
+
# fallback to the internal integration
|
109
|
+
if specified_formatter == "rubocop" && !rubocop_has_addon
|
110
|
+
@formatter = "rubocop_internal"
|
111
|
+
end
|
104
112
|
end
|
105
113
|
|
106
114
|
if @formatter == "auto"
|
@@ -109,6 +117,23 @@ module RubyLsp
|
|
109
117
|
end
|
110
118
|
|
111
119
|
specified_linters = options.dig(:initializationOptions, :linters)
|
120
|
+
|
121
|
+
if specified_formatter == "rubocop" || specified_linters&.include?("rubocop")
|
122
|
+
notifications << Notification.window_log_message(<<~MESSAGE, type: Constant::MessageType::WARNING)
|
123
|
+
Formatter is configured to be `rubocop`. As of RuboCop v1.70.0, this identifier activates the add-on
|
124
|
+
implemented in the rubocop gem itself instead of the internal integration provided by the Ruby LSP.
|
125
|
+
|
126
|
+
If you wish to use the internal integration, please configure the formatter as `rubocop_internal`.
|
127
|
+
MESSAGE
|
128
|
+
end
|
129
|
+
|
130
|
+
# If the user had originally configured to use `rubocop`, but their version doesn't provide the add-on yet,
|
131
|
+
# fall back to the internal integration
|
132
|
+
if specified_linters&.include?("rubocop") && !rubocop_has_addon
|
133
|
+
specified_linters.delete("rubocop")
|
134
|
+
specified_linters << "rubocop_internal"
|
135
|
+
end
|
136
|
+
|
112
137
|
@linters = specified_linters || detect_linters(direct_dependencies, all_dependencies)
|
113
138
|
|
114
139
|
notifications << if specified_linters
|
@@ -185,13 +210,13 @@ module RubyLsp
|
|
185
210
|
sig { params(direct_dependencies: T::Array[String], all_dependencies: T::Array[String]).returns(String) }
|
186
211
|
def detect_formatter(direct_dependencies, all_dependencies)
|
187
212
|
# NOTE: Intentionally no $ at end, since we want to match rubocop-shopify, etc.
|
188
|
-
return "
|
213
|
+
return "rubocop_internal" if direct_dependencies.any?(/^rubocop/)
|
189
214
|
|
190
215
|
syntax_tree_is_direct_dependency = direct_dependencies.include?("syntax_tree")
|
191
216
|
return "syntax_tree" if syntax_tree_is_direct_dependency
|
192
217
|
|
193
218
|
rubocop_is_transitive_dependency = all_dependencies.include?("rubocop")
|
194
|
-
return "
|
219
|
+
return "rubocop_internal" if dot_rubocop_yml_present && rubocop_is_transitive_dependency
|
195
220
|
|
196
221
|
"none"
|
197
222
|
end
|
@@ -203,7 +228,7 @@ module RubyLsp
|
|
203
228
|
linters = []
|
204
229
|
|
205
230
|
if dependencies.any?(/^rubocop/) || (all_dependencies.include?("rubocop") && dot_rubocop_yml_present)
|
206
|
-
linters << "
|
231
|
+
linters << "rubocop_internal"
|
207
232
|
end
|
208
233
|
|
209
234
|
linters
|
@@ -120,7 +120,7 @@ module RubyLsp
|
|
120
120
|
[target, node_value(target)]
|
121
121
|
when Prism::ModuleNode, Prism::ClassNode, Prism::SingletonClassNode, Prism::DefNode, Prism::CaseNode,
|
122
122
|
Prism::WhileNode, Prism::UntilNode, Prism::ForNode, Prism::IfNode, Prism::UnlessNode
|
123
|
-
target
|
123
|
+
[target, nil]
|
124
124
|
end
|
125
125
|
|
126
126
|
@target = T.let(highlight_target, T.nilable(Prism::Node))
|
@@ -620,7 +620,8 @@ module RubyLsp
|
|
620
620
|
|
621
621
|
sig { params(keyword_loc: T.nilable(Prism::Location), end_loc: T.nilable(Prism::Location)).void }
|
622
622
|
def add_matching_end_highlights(keyword_loc, end_loc)
|
623
|
-
return unless keyword_loc && end_loc
|
623
|
+
return unless keyword_loc && end_loc
|
624
|
+
return unless end_loc.length.positive?
|
624
625
|
return unless covers_target_position?(keyword_loc) || covers_target_position?(end_loc)
|
625
626
|
|
626
627
|
add_highlight(Constant::DocumentHighlightKind::TEXT, keyword_loc)
|
@@ -128,7 +128,13 @@ module RubyLsp
|
|
128
128
|
gem_version = resolve_version(uri)
|
129
129
|
return if gem_version.nil?
|
130
130
|
|
131
|
-
|
131
|
+
path = uri.path
|
132
|
+
return unless path
|
133
|
+
|
134
|
+
gem_name = uri.gem_name
|
135
|
+
return unless gem_name
|
136
|
+
|
137
|
+
file_path = self.class.gem_paths.dig(gem_name, gem_version, CGI.unescape(path))
|
132
138
|
return if file_path.nil?
|
133
139
|
|
134
140
|
@response_builder << Interface::DocumentLink.new(
|
@@ -149,7 +155,10 @@ module RubyLsp
|
|
149
155
|
|
150
156
|
return @gem_version unless @gem_version.nil? || @gem_version.empty?
|
151
157
|
|
152
|
-
|
158
|
+
gem_name = uri.gem_name
|
159
|
+
return unless gem_name
|
160
|
+
|
161
|
+
GEM_TO_VERSION_MAP[gem_name]
|
153
162
|
end
|
154
163
|
end
|
155
164
|
end
|
data/lib/ruby_lsp/load_sorbet.rb
CHANGED
@@ -6,11 +6,11 @@ require "sorbet-runtime"
|
|
6
6
|
begin
|
7
7
|
T::Configuration.default_checked_level = :never
|
8
8
|
# Suppresses call validation errors
|
9
|
-
T::Configuration.call_validation_error_handler = ->(*) {}
|
9
|
+
T::Configuration.call_validation_error_handler = ->(*arg) {}
|
10
10
|
# Suppresses errors caused by T.cast, T.let, T.must, etc.
|
11
|
-
T::Configuration.inline_type_error_handler = ->(*) {}
|
11
|
+
T::Configuration.inline_type_error_handler = ->(*arg) {}
|
12
12
|
# Suppresses errors caused by incorrect parameter ordering
|
13
|
-
T::Configuration.sig_validation_error_handler = ->(*) {}
|
13
|
+
T::Configuration.sig_validation_error_handler = ->(*arg) {}
|
14
14
|
rescue
|
15
15
|
# Need this rescue so that if another gem has
|
16
16
|
# already set the checked level by the time we
|
@@ -19,7 +19,7 @@ module RubyLsp
|
|
19
19
|
sig { returns(Interface::CodeActionRegistrationOptions) }
|
20
20
|
def provider
|
21
21
|
Interface::CodeActionRegistrationOptions.new(
|
22
|
-
document_selector:
|
22
|
+
document_selector: nil,
|
23
23
|
resolve_provider: true,
|
24
24
|
)
|
25
25
|
end
|
@@ -15,7 +15,7 @@ module RubyLsp
|
|
15
15
|
sig { returns(Interface::DiagnosticRegistrationOptions) }
|
16
16
|
def provider
|
17
17
|
Interface::DiagnosticRegistrationOptions.new(
|
18
|
-
document_selector:
|
18
|
+
document_selector: nil,
|
19
19
|
inter_file_dependencies: false,
|
20
20
|
workspace_diagnostics: false,
|
21
21
|
)
|
@@ -13,13 +13,9 @@ module RubyLsp
|
|
13
13
|
class << self
|
14
14
|
extend T::Sig
|
15
15
|
|
16
|
-
sig { returns(
|
16
|
+
sig { returns(TrueClass) }
|
17
17
|
def provider
|
18
|
-
|
19
|
-
document_selector: [
|
20
|
-
Interface::DocumentFilter.new(language: "ruby"),
|
21
|
-
],
|
22
|
-
)
|
18
|
+
true
|
23
19
|
end
|
24
20
|
end
|
25
21
|
|
@@ -14,13 +14,9 @@ module RubyLsp
|
|
14
14
|
class << self
|
15
15
|
extend T::Sig
|
16
16
|
|
17
|
-
sig { returns(
|
17
|
+
sig { returns(TrueClass) }
|
18
18
|
def provider
|
19
|
-
|
20
|
-
document_selector: [
|
21
|
-
Interface::DocumentFilter.new(language: "ruby"),
|
22
|
-
],
|
23
|
-
)
|
19
|
+
true
|
24
20
|
end
|
25
21
|
end
|
26
22
|
|
@@ -14,7 +14,7 @@ module RubyLsp
|
|
14
14
|
sig { returns(Interface::DocumentOnTypeFormattingRegistrationOptions) }
|
15
15
|
def provider
|
16
16
|
Interface::DocumentOnTypeFormattingRegistrationOptions.new(
|
17
|
-
document_selector:
|
17
|
+
document_selector: nil,
|
18
18
|
first_trigger_character: "{",
|
19
19
|
more_trigger_character: ["\n", "|", "d"],
|
20
20
|
)
|
@@ -39,6 +39,12 @@ module RubyLsp
|
|
39
39
|
Prism::ConstantReadNode,
|
40
40
|
Prism::ConstantPathNode,
|
41
41
|
Prism::ConstantPathTargetNode,
|
42
|
+
Prism::InstanceVariableAndWriteNode,
|
43
|
+
Prism::InstanceVariableOperatorWriteNode,
|
44
|
+
Prism::InstanceVariableOrWriteNode,
|
45
|
+
Prism::InstanceVariableReadNode,
|
46
|
+
Prism::InstanceVariableTargetNode,
|
47
|
+
Prism::InstanceVariableWriteNode,
|
42
48
|
Prism::CallNode,
|
43
49
|
Prism::DefNode,
|
44
50
|
],
|
@@ -62,6 +68,12 @@ module RubyLsp
|
|
62
68
|
Prism::ConstantReadNode,
|
63
69
|
Prism::ConstantPathNode,
|
64
70
|
Prism::ConstantPathTargetNode,
|
71
|
+
Prism::InstanceVariableAndWriteNode,
|
72
|
+
Prism::InstanceVariableOperatorWriteNode,
|
73
|
+
Prism::InstanceVariableOrWriteNode,
|
74
|
+
Prism::InstanceVariableReadNode,
|
75
|
+
Prism::InstanceVariableTargetNode,
|
76
|
+
Prism::InstanceVariableWriteNode,
|
65
77
|
Prism::CallNode,
|
66
78
|
Prism::DefNode,
|
67
79
|
),
|
@@ -97,6 +109,12 @@ module RubyLsp
|
|
97
109
|
Prism::ConstantReadNode,
|
98
110
|
Prism::ConstantPathNode,
|
99
111
|
Prism::ConstantPathTargetNode,
|
112
|
+
Prism::InstanceVariableAndWriteNode,
|
113
|
+
Prism::InstanceVariableOperatorWriteNode,
|
114
|
+
Prism::InstanceVariableOrWriteNode,
|
115
|
+
Prism::InstanceVariableReadNode,
|
116
|
+
Prism::InstanceVariableTargetNode,
|
117
|
+
Prism::InstanceVariableWriteNode,
|
100
118
|
Prism::CallNode,
|
101
119
|
Prism::DefNode,
|
102
120
|
),
|
@@ -114,6 +132,14 @@ module RubyLsp
|
|
114
132
|
|
115
133
|
fully_qualified_name = T.must(entries.first).name
|
116
134
|
RubyIndexer::ReferenceFinder::ConstTarget.new(fully_qualified_name)
|
135
|
+
when
|
136
|
+
Prism::InstanceVariableAndWriteNode,
|
137
|
+
Prism::InstanceVariableOperatorWriteNode,
|
138
|
+
Prism::InstanceVariableOrWriteNode,
|
139
|
+
Prism::InstanceVariableReadNode,
|
140
|
+
Prism::InstanceVariableTargetNode,
|
141
|
+
Prism::InstanceVariableWriteNode
|
142
|
+
RubyIndexer::ReferenceFinder::InstanceVariableTarget.new(target_node.name.to_s)
|
117
143
|
when Prism::CallNode, Prism::DefNode
|
118
144
|
RubyIndexer::ReferenceFinder::MethodTarget.new(target_node.name.to_s)
|
119
145
|
end
|
@@ -17,7 +17,7 @@ module RubyLsp
|
|
17
17
|
sig { returns(Interface::SemanticTokensRegistrationOptions) }
|
18
18
|
def provider
|
19
19
|
Interface::SemanticTokensRegistrationOptions.new(
|
20
|
-
document_selector:
|
20
|
+
document_selector: nil,
|
21
21
|
legend: Interface::SemanticTokensLegend.new(
|
22
22
|
token_types: ResponseBuilders::SemanticHighlighting::TOKEN_TYPES.keys,
|
23
23
|
token_modifiers: ResponseBuilders::SemanticHighlighting::TOKEN_MODIFIERS.keys,
|
data/lib/ruby_lsp/server.rb
CHANGED
@@ -106,6 +106,8 @@ module RubyLsp
|
|
106
106
|
end,
|
107
107
|
),
|
108
108
|
)
|
109
|
+
when "rubyLsp/composeBundle"
|
110
|
+
compose_bundle(message)
|
109
111
|
when "$/cancelRequest"
|
110
112
|
@global_state.synchronize { @cancelled_requests << message[:params][:id] }
|
111
113
|
when nil
|
@@ -283,6 +285,7 @@ module RubyLsp
|
|
283
285
|
document_range_formatting_provider: true,
|
284
286
|
experimental: {
|
285
287
|
addon_detection: true,
|
288
|
+
compose_bundle: true,
|
286
289
|
},
|
287
290
|
),
|
288
291
|
serverInfo: {
|
@@ -338,7 +341,7 @@ module RubyLsp
|
|
338
341
|
unless @setup_error
|
339
342
|
if defined?(Requests::Support::RuboCopFormatter)
|
340
343
|
begin
|
341
|
-
@global_state.register_formatter("
|
344
|
+
@global_state.register_formatter("rubocop_internal", Requests::Support::RuboCopFormatter.new)
|
342
345
|
rescue ::RuboCop::Error => e
|
343
346
|
# The user may have provided unknown config switches in .rubocop or
|
344
347
|
# is trying to load a non-existent config file.
|
@@ -1039,16 +1042,20 @@ module RubyLsp
|
|
1039
1042
|
def handle_rubocop_config_change(uri)
|
1040
1043
|
return unless defined?(Requests::Support::RuboCopFormatter)
|
1041
1044
|
|
1042
|
-
|
1043
|
-
@global_state.register_formatter("
|
1045
|
+
# Register a new runner to reload configurations
|
1046
|
+
@global_state.register_formatter("rubocop_internal", Requests::Support::RuboCopFormatter.new)
|
1044
1047
|
|
1045
|
-
# Clear all
|
1046
|
-
# hash cannot be mutated during iteration or that will throw an error
|
1048
|
+
# Clear all document caches for pull diagnostics
|
1047
1049
|
@global_state.synchronize do
|
1048
|
-
@store.each do |
|
1049
|
-
|
1050
|
+
@store.each do |_uri, document|
|
1051
|
+
document.cache_set("textDocument/diagnostic", Document::EMPTY_CACHE)
|
1050
1052
|
end
|
1051
1053
|
end
|
1054
|
+
|
1055
|
+
# Request a pull diagnostic refresh from the editor
|
1056
|
+
if @global_state.client_capabilities.supports_diagnostic_refresh
|
1057
|
+
send_message(Request.new(id: @current_request_id, method: "workspace/diagnostic/refresh", params: nil))
|
1058
|
+
end
|
1052
1059
|
end
|
1053
1060
|
|
1054
1061
|
sig { params(message: T::Hash[Symbol, T.untyped]).void }
|
@@ -1211,16 +1218,15 @@ module RubyLsp
|
|
1211
1218
|
sig { void }
|
1212
1219
|
def check_formatter_is_available
|
1213
1220
|
return if @setup_error
|
1214
|
-
# Warn of an unavailable `formatter` setting, e.g. `
|
1215
|
-
|
1216
|
-
return unless @global_state.formatter == "rubocop"
|
1221
|
+
# Warn of an unavailable `formatter` setting, e.g. `rubocop_internal` on a project which doesn't have RuboCop.
|
1222
|
+
return unless @global_state.formatter == "rubocop_internal"
|
1217
1223
|
|
1218
1224
|
unless defined?(RubyLsp::Requests::Support::RuboCopRunner)
|
1219
1225
|
@global_state.formatter = "none"
|
1220
1226
|
|
1221
1227
|
send_message(
|
1222
1228
|
Notification.window_show_message(
|
1223
|
-
"Ruby LSP formatter is set to `
|
1229
|
+
"Ruby LSP formatter is set to `rubocop_internal` but RuboCop was not found in the Gemfile or gemspec.",
|
1224
1230
|
type: Constant::MessageType::ERROR,
|
1225
1231
|
),
|
1226
1232
|
)
|
@@ -1279,5 +1285,37 @@ module RubyLsp
|
|
1279
1285
|
|
1280
1286
|
addon.handle_window_show_message_response(result[:title])
|
1281
1287
|
end
|
1288
|
+
|
1289
|
+
sig { params(message: T::Hash[Symbol, T.untyped]).void }
|
1290
|
+
def compose_bundle(message)
|
1291
|
+
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
|
+
id = message[:id]
|
1294
|
+
|
1295
|
+
begin
|
1296
|
+
Bundler::LockfileParser.new(Bundler.default_lockfile.read)
|
1297
|
+
rescue Bundler::LockfileError => e
|
1298
|
+
send_message(Error.new(id: id, code: BUNDLE_COMPOSE_FAILED_CODE, message: e.message))
|
1299
|
+
return
|
1300
|
+
end
|
1301
|
+
|
1302
|
+
# We compose the bundle in a thread so that the LSP continues to work while we're checking for its validity. Once
|
1303
|
+
# we return the response back to the editor, then the restart is triggered
|
1304
|
+
Thread.new do
|
1305
|
+
send_log_message("Recomposing the bundle ahead of restart")
|
1306
|
+
pid = Process.spawn(command)
|
1307
|
+
_, status = Process.wait2(pid)
|
1308
|
+
|
1309
|
+
if status&.exitstatus == 0
|
1310
|
+
# Create a signal for the restart that it can skip composing the bundle and launch directly
|
1311
|
+
FileUtils.touch(already_composed_path)
|
1312
|
+
send_message(Result.new(id: id, response: { success: true }))
|
1313
|
+
else
|
1314
|
+
# This special error code makes the extension avoid restarting in case we already know that the composed
|
1315
|
+
# bundle is not valid
|
1316
|
+
send_message(Error.new(id: id, code: BUNDLE_COMPOSE_FAILED_CODE, message: "Failed to compose bundle"))
|
1317
|
+
end
|
1318
|
+
end
|
1319
|
+
end
|
1282
1320
|
end
|
1283
1321
|
end
|
@@ -57,6 +57,7 @@ module RubyLsp
|
|
57
57
|
@lockfile_hash_path = T.let(@custom_dir + "main_lockfile_hash", Pathname)
|
58
58
|
@last_updated_path = T.let(@custom_dir + "last_updated", Pathname)
|
59
59
|
@error_path = T.let(@custom_dir + "install_error", Pathname)
|
60
|
+
@already_composed_path = T.let(@custom_dir + "bundle_is_composed", Pathname)
|
60
61
|
|
61
62
|
dependencies, bundler_version = load_dependencies
|
62
63
|
@dependencies = T.let(dependencies, T::Hash[String, T.untyped])
|
@@ -71,6 +72,23 @@ module RubyLsp
|
|
71
72
|
def setup!
|
72
73
|
raise BundleNotLocked if !@launcher && @gemfile&.exist? && !@lockfile&.exist?
|
73
74
|
|
75
|
+
# If the bundle was composed ahead of time using our custom `rubyLsp/composeBundle` request, then we can skip the
|
76
|
+
# entire process and just return the composed environment
|
77
|
+
if @already_composed_path.exist?
|
78
|
+
$stderr.puts("Ruby LSP> Composed bundle was set up ahead of time. Skipping...")
|
79
|
+
@already_composed_path.delete
|
80
|
+
|
81
|
+
env = bundler_settings_as_env
|
82
|
+
env["BUNDLE_GEMFILE"] = @custom_gemfile.exist? ? @custom_gemfile.to_s : @gemfile.to_s
|
83
|
+
|
84
|
+
if env["BUNDLE_PATH"]
|
85
|
+
env["BUNDLE_PATH"] = File.expand_path(env["BUNDLE_PATH"], @project_path)
|
86
|
+
end
|
87
|
+
|
88
|
+
env["BUNDLER_VERSION"] = @bundler_version.to_s if @bundler_version
|
89
|
+
return env
|
90
|
+
end
|
91
|
+
|
74
92
|
# Automatically create and ignore the .ruby-lsp folder for users
|
75
93
|
@custom_dir.mkpath unless @custom_dir.exist?
|
76
94
|
ignore_file = @custom_dir + ".gitignore"
|
data/lib/ruby_lsp/utils.rb
CHANGED
@@ -25,7 +25,7 @@ module RubyLsp
|
|
25
25
|
end,
|
26
26
|
String,
|
27
27
|
)
|
28
|
-
GUESSED_TYPES_URL = "https://shopify.github.io/ruby-lsp
|
28
|
+
GUESSED_TYPES_URL = "https://shopify.github.io/ruby-lsp/#guessed-types"
|
29
29
|
|
30
30
|
# Request delegation for embedded languages is not yet standardized into the language server specification. Here we
|
31
31
|
# use this custom error class as a way to return a signal to the client that the request should be delegated to the
|
@@ -37,6 +37,8 @@ module RubyLsp
|
|
37
37
|
CODE = -32900
|
38
38
|
end
|
39
39
|
|
40
|
+
BUNDLE_COMPOSE_FAILED_CODE = -33000
|
41
|
+
|
40
42
|
# A notification to be sent to the client
|
41
43
|
class Message
|
42
44
|
extend T::Sig
|
@@ -162,7 +164,9 @@ module RubyLsp
|
|
162
164
|
|
163
165
|
sig { override.returns(T::Hash[Symbol, T.untyped]) }
|
164
166
|
def to_hash
|
165
|
-
{ method: @method
|
167
|
+
hash = { method: @method }
|
168
|
+
hash[:params] = T.unsafe(@params).to_hash if @params
|
169
|
+
hash
|
166
170
|
end
|
167
171
|
end
|
168
172
|
|
@@ -206,7 +210,9 @@ module RubyLsp
|
|
206
210
|
|
207
211
|
sig { override.returns(T::Hash[Symbol, T.untyped]) }
|
208
212
|
def to_hash
|
209
|
-
{ id: @id, method: @method
|
213
|
+
hash = { id: @id, method: @method }
|
214
|
+
hash[:params] = T.unsafe(@params).to_hash if @params
|
215
|
+
hash
|
210
216
|
end
|
211
217
|
end
|
212
218
|
|
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.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shopify
|
8
8
|
bindir: exe
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-01-
|
10
|
+
date: 2025-01-28 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: language_server-protocol
|
@@ -218,7 +218,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
218
218
|
- !ruby/object:Gem::Version
|
219
219
|
version: '0'
|
220
220
|
requirements: []
|
221
|
-
rubygems_version: 3.6.
|
221
|
+
rubygems_version: 3.6.3
|
222
222
|
specification_version: 4
|
223
223
|
summary: An opinionated language server for Ruby
|
224
224
|
test_files: []
|