ruby-lsp 0.23.5 → 0.23.7
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Build Status](https://github.com/Shopify/ruby-lsp/workflows/CI/badge.svg)](https://github.com/Shopify/ruby-lsp/actions/workflows/ci.yml)
|
6
6
|
[![Ruby LSP extension](https://img.shields.io/badge/VS%20Code-Ruby%20LSP-success?logo=visual-studio-code)](https://marketplace.visualstudio.com/items?itemName=Shopify.ruby-lsp)
|
7
|
-
[![Ruby DX Slack](https://img.shields.io/badge/Slack-Ruby%20DX-success?logo=slack)](https://join.slack.com/t/ruby-dx/shared_invite/zt-
|
7
|
+
[![Ruby DX Slack](https://img.shields.io/badge/Slack-Ruby%20DX-success?logo=slack)](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: []
|