ruby-lsp 0.17.7 → 0.17.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +6 -1
- data/VERSION +1 -1
- data/exe/ruby-lsp +25 -0
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +16 -3
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +38 -0
- data/lib/ruby_lsp/base_server.rb +1 -1
- data/lib/ruby_lsp/listeners/completion.rb +67 -6
- data/lib/ruby_lsp/server.rb +67 -37
- data/lib/ruby_lsp/store.rb +4 -0
- metadata +3 -5
- data/exe/ruby-lsp-doctor +0 -23
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e3a8a120b4cf3045cdeec98d2fb417ffdd300947b6426788d827346c43812ffc
|
4
|
+
data.tar.gz: b43a823e71fca99a9cf79b3a6ce19e7a0ec86e499b943aecae1666788859d055
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9662d88d3f37b4fd7b744a69f38f2a20cd4bf00291f63968578e50bef3400916870efdcf58b6e81dd78a6829c4f8a7dd174e6d212ddc3a265420b29eaa2b6d8a
|
7
|
+
data.tar.gz: b788ef9185e02a732cd2dccef6273d8dd3fb471ebb8c2300b3ac78cc48d3952eee378a777cc4cf4ceb927590fff7bcdfc57c95f0924b21e589eb850ec9128641
|
data/README.md
CHANGED
@@ -78,7 +78,12 @@ default gems, except for
|
|
78
78
|
- Gems that only appear under the `:development` group
|
79
79
|
- All Ruby files under `test/**/*.rb`
|
80
80
|
|
81
|
-
|
81
|
+
This behaviour can be overridden and tuned. Learn how to configure it [for VS Code](vscode/README.md#Indexing-Configuration) or [for other editors](EDITORS.md#Indexing-Configuration).
|
82
|
+
|
83
|
+
Note that indexing-dependent behavior, such as definition, hover, completion or workspace symbol will be impacted by
|
84
|
+
the configuration changes.
|
85
|
+
|
86
|
+
The older approach of using a `.index.yml` file has been deprecated and will be removed in a future release.
|
82
87
|
|
83
88
|
```yaml
|
84
89
|
# Exclude files based on a given pattern. Often used to exclude test files or fixtures
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.17.
|
1
|
+
0.17.8
|
data/exe/ruby-lsp
CHANGED
@@ -36,6 +36,10 @@ parser = OptionParser.new do |opts|
|
|
36
36
|
options[:experimental] = true
|
37
37
|
end
|
38
38
|
|
39
|
+
opts.on("--doctor", "Run troubleshooting steps") do
|
40
|
+
options[:doctor] = true
|
41
|
+
end
|
42
|
+
|
39
43
|
opts.on("-h", "--help", "Print this help") do
|
40
44
|
puts opts.help
|
41
45
|
puts
|
@@ -107,4 +111,25 @@ if options[:time_index]
|
|
107
111
|
return
|
108
112
|
end
|
109
113
|
|
114
|
+
if options[:doctor]
|
115
|
+
if File.exist?(".index.yml")
|
116
|
+
begin
|
117
|
+
config = YAML.parse_file(".index.yml").to_ruby
|
118
|
+
rescue => e
|
119
|
+
abort("Error parsing config: #{e.message}")
|
120
|
+
end
|
121
|
+
RubyIndexer.configuration.apply_config(config)
|
122
|
+
end
|
123
|
+
|
124
|
+
index = RubyIndexer::Index.new
|
125
|
+
|
126
|
+
puts "Globbing for indexable files"
|
127
|
+
|
128
|
+
RubyIndexer.configuration.indexables.each do |indexable|
|
129
|
+
puts "indexing: #{indexable.full_path}"
|
130
|
+
index.index_single(indexable)
|
131
|
+
end
|
132
|
+
return
|
133
|
+
end
|
134
|
+
|
110
135
|
RubyLsp::Server.new.start
|
@@ -71,7 +71,7 @@ module RubyIndexer
|
|
71
71
|
|
72
72
|
superclass = node.superclass
|
73
73
|
|
74
|
-
nesting =
|
74
|
+
nesting = actual_nesting(name)
|
75
75
|
|
76
76
|
parent_class = case superclass
|
77
77
|
when Prism::ConstantReadNode, Prism::ConstantPathNode
|
@@ -119,8 +119,7 @@ module RubyIndexer
|
|
119
119
|
|
120
120
|
comments = collect_comments(node)
|
121
121
|
|
122
|
-
|
123
|
-
entry = Entry::Module.new(nesting, @file_path, node.location, constant_path.location, comments)
|
122
|
+
entry = Entry::Module.new(actual_nesting(name), @file_path, node.location, constant_path.location, comments)
|
124
123
|
|
125
124
|
@owner_stack << entry
|
126
125
|
@index.add(entry)
|
@@ -709,5 +708,19 @@ module RubyIndexer
|
|
709
708
|
:"(#{names_with_commas})"
|
710
709
|
end
|
711
710
|
end
|
711
|
+
|
712
|
+
sig { params(name: String).returns(T::Array[String]) }
|
713
|
+
def actual_nesting(name)
|
714
|
+
nesting = @stack + [name]
|
715
|
+
corrected_nesting = []
|
716
|
+
|
717
|
+
nesting.reverse_each do |name|
|
718
|
+
corrected_nesting.prepend(name.delete_prefix("::"))
|
719
|
+
|
720
|
+
break if name.start_with?("::")
|
721
|
+
end
|
722
|
+
|
723
|
+
corrected_nesting
|
724
|
+
end
|
712
725
|
end
|
713
726
|
end
|
@@ -546,5 +546,43 @@ module RubyIndexer
|
|
546
546
|
assert_equal(7, name_location.start_column)
|
547
547
|
assert_equal(10, name_location.end_column)
|
548
548
|
end
|
549
|
+
|
550
|
+
def test_indexing_namespaces_inside_top_level_references
|
551
|
+
index(<<~RUBY)
|
552
|
+
module ::Foo
|
553
|
+
class Bar
|
554
|
+
end
|
555
|
+
end
|
556
|
+
RUBY
|
557
|
+
|
558
|
+
# We want to explicitly verify that we didn't introduce the leading `::` by accident, but `Index#[]` deletes the
|
559
|
+
# prefix when we use `refute_entry`
|
560
|
+
entries = @index.instance_variable_get(:@entries)
|
561
|
+
refute(entries.key?("::Foo"))
|
562
|
+
refute(entries.key?("::Foo::Bar"))
|
563
|
+
assert_entry("Foo", Entry::Module, "/fake/path/foo.rb:0-0:3-3")
|
564
|
+
assert_entry("Foo::Bar", Entry::Class, "/fake/path/foo.rb:1-2:2-5")
|
565
|
+
end
|
566
|
+
|
567
|
+
def test_indexing_namespaces_inside_nested_top_level_references
|
568
|
+
index(<<~RUBY)
|
569
|
+
class Baz
|
570
|
+
module ::Foo
|
571
|
+
class Bar
|
572
|
+
end
|
573
|
+
|
574
|
+
class ::Qux
|
575
|
+
end
|
576
|
+
end
|
577
|
+
end
|
578
|
+
RUBY
|
579
|
+
|
580
|
+
refute_entry("Baz::Foo")
|
581
|
+
refute_entry("Baz::Foo::Bar")
|
582
|
+
assert_entry("Baz", Entry::Class, "/fake/path/foo.rb:0-0:8-3")
|
583
|
+
assert_entry("Foo", Entry::Module, "/fake/path/foo.rb:1-2:7-5")
|
584
|
+
assert_entry("Foo::Bar", Entry::Class, "/fake/path/foo.rb:2-4:3-7")
|
585
|
+
assert_entry("Qux", Entry::Class, "/fake/path/foo.rb:5-4:6-7")
|
586
|
+
end
|
549
587
|
end
|
550
588
|
end
|
data/lib/ruby_lsp/base_server.rb
CHANGED
@@ -53,7 +53,7 @@ module RubyLsp
|
|
53
53
|
|
54
54
|
# We don't want to try to parse documents on text synchronization notifications
|
55
55
|
@store.get(parsed_uri).parse unless method.start_with?("textDocument/did")
|
56
|
-
rescue
|
56
|
+
rescue Store::NonExistingDocumentError
|
57
57
|
# If we receive a request for a file that no longer exists, we don't want to fail
|
58
58
|
end
|
59
59
|
end
|
@@ -7,6 +7,50 @@ module RubyLsp
|
|
7
7
|
extend T::Sig
|
8
8
|
include Requests::Support::Common
|
9
9
|
|
10
|
+
KEYWORDS = [
|
11
|
+
"alias",
|
12
|
+
"and",
|
13
|
+
"begin",
|
14
|
+
"BEGIN",
|
15
|
+
"break",
|
16
|
+
"case",
|
17
|
+
"class",
|
18
|
+
"def",
|
19
|
+
"defined?",
|
20
|
+
"do",
|
21
|
+
"else",
|
22
|
+
"elsif",
|
23
|
+
"end",
|
24
|
+
"END",
|
25
|
+
"ensure",
|
26
|
+
"false",
|
27
|
+
"for",
|
28
|
+
"if",
|
29
|
+
"in",
|
30
|
+
"module",
|
31
|
+
"next",
|
32
|
+
"nil",
|
33
|
+
"not",
|
34
|
+
"or",
|
35
|
+
"redo",
|
36
|
+
"rescue",
|
37
|
+
"retry",
|
38
|
+
"return",
|
39
|
+
"self",
|
40
|
+
"super",
|
41
|
+
"then",
|
42
|
+
"true",
|
43
|
+
"undef",
|
44
|
+
"unless",
|
45
|
+
"until",
|
46
|
+
"when",
|
47
|
+
"while",
|
48
|
+
"yield",
|
49
|
+
"__ENCODING__",
|
50
|
+
"__FILE__",
|
51
|
+
"__LINE__",
|
52
|
+
].freeze
|
53
|
+
|
10
54
|
sig do
|
11
55
|
params(
|
12
56
|
response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::CompletionItem],
|
@@ -277,7 +321,11 @@ module RubyLsp
|
|
277
321
|
|
278
322
|
sig { params(node: Prism::CallNode, name: String).void }
|
279
323
|
def complete_methods(node, name)
|
280
|
-
|
324
|
+
# If the node has a receiver, then we don't need to provide local nor keyword completions
|
325
|
+
if !@global_state.has_type_checker && !node.receiver
|
326
|
+
add_local_completions(node, name)
|
327
|
+
add_keyword_completions(node, name)
|
328
|
+
end
|
281
329
|
|
282
330
|
type = @type_inferrer.infer_receiver_type(@node_context)
|
283
331
|
return unless type
|
@@ -326,11 +374,6 @@ module RubyLsp
|
|
326
374
|
|
327
375
|
sig { params(node: Prism::CallNode, name: String).void }
|
328
376
|
def add_local_completions(node, name)
|
329
|
-
return if @global_state.has_type_checker
|
330
|
-
|
331
|
-
# If the call node has a receiver, then it cannot possibly be a local variable
|
332
|
-
return if node.receiver
|
333
|
-
|
334
377
|
range = range_from_location(T.must(node.message_loc))
|
335
378
|
|
336
379
|
@node_context.locals_for_scope.each do |local|
|
@@ -349,6 +392,24 @@ module RubyLsp
|
|
349
392
|
end
|
350
393
|
end
|
351
394
|
|
395
|
+
sig { params(node: Prism::CallNode, name: String).void }
|
396
|
+
def add_keyword_completions(node, name)
|
397
|
+
range = range_from_location(T.must(node.message_loc))
|
398
|
+
|
399
|
+
KEYWORDS.each do |keyword|
|
400
|
+
next unless keyword.start_with?(name)
|
401
|
+
|
402
|
+
@response_builder << Interface::CompletionItem.new(
|
403
|
+
label: keyword,
|
404
|
+
text_edit: Interface::TextEdit.new(range: range, new_text: keyword),
|
405
|
+
kind: Constant::CompletionItemKind::KEYWORD,
|
406
|
+
data: {
|
407
|
+
skip_resolve: true,
|
408
|
+
},
|
409
|
+
)
|
410
|
+
end
|
411
|
+
end
|
412
|
+
|
352
413
|
sig { params(label: String, node: Prism::StringNode).returns(Interface::CompletionItem) }
|
353
414
|
def build_completion(label, node)
|
354
415
|
# We should use the content location as we only replace the content and not the delimiters of the string
|
data/lib/ruby_lsp/server.rb
CHANGED
@@ -98,16 +98,27 @@ module RubyLsp
|
|
98
98
|
rescue StandardError, LoadError => e
|
99
99
|
# If an error occurred in a request, we have to return an error response or else the editor will hang
|
100
100
|
if message[:id]
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
101
|
+
# If a document is deleted before we are able to process all of its enqueued requests, we will try to read it
|
102
|
+
# from disk and it raise this error. This is expected, so we don't include the `data` attribute to avoid
|
103
|
+
# reporting these to our telemetry
|
104
|
+
if e.is_a?(Store::NonExistingDocumentError)
|
105
|
+
send_message(Error.new(
|
106
|
+
id: message[:id],
|
107
|
+
code: Constant::ErrorCodes::INVALID_PARAMS,
|
108
|
+
message: e.full_message,
|
109
|
+
))
|
110
|
+
else
|
111
|
+
send_message(Error.new(
|
112
|
+
id: message[:id],
|
113
|
+
code: Constant::ErrorCodes::INTERNAL_ERROR,
|
114
|
+
message: e.full_message,
|
115
|
+
data: {
|
116
|
+
errorClass: e.class.name,
|
117
|
+
errorMessage: e.message,
|
118
|
+
backtrace: e.backtrace&.join("\n"),
|
119
|
+
},
|
120
|
+
))
|
121
|
+
end
|
111
122
|
end
|
112
123
|
|
113
124
|
$stderr.puts("Error processing #{message[:method]}: #{e.full_message}")
|
@@ -243,6 +254,8 @@ module RubyLsp
|
|
243
254
|
)
|
244
255
|
end
|
245
256
|
|
257
|
+
process_indexing_configuration(options.dig(:initializationOptions, :indexing))
|
258
|
+
|
246
259
|
begin_progress("indexing-progress", "Ruby LSP: indexing files")
|
247
260
|
end
|
248
261
|
|
@@ -251,28 +264,6 @@ module RubyLsp
|
|
251
264
|
load_addons
|
252
265
|
RubyVM::YJIT.enable if defined?(RubyVM::YJIT.enable)
|
253
266
|
|
254
|
-
indexing_config = {}
|
255
|
-
|
256
|
-
# Need to use the workspace URI, otherwise, this will fail for people working on a project that is a symlink.
|
257
|
-
index_path = File.join(@global_state.workspace_path, ".index.yml")
|
258
|
-
|
259
|
-
if File.exist?(index_path)
|
260
|
-
begin
|
261
|
-
indexing_config = YAML.parse_file(index_path).to_ruby
|
262
|
-
rescue Psych::SyntaxError => e
|
263
|
-
message = "Syntax error while loading configuration: #{e.message}"
|
264
|
-
send_message(
|
265
|
-
Notification.new(
|
266
|
-
method: "window/showMessage",
|
267
|
-
params: Interface::ShowMessageParams.new(
|
268
|
-
type: Constant::MessageType::WARNING,
|
269
|
-
message: message,
|
270
|
-
),
|
271
|
-
),
|
272
|
-
)
|
273
|
-
end
|
274
|
-
end
|
275
|
-
|
276
267
|
if defined?(Requests::Support::RuboCopFormatter)
|
277
268
|
@global_state.register_formatter("rubocop", Requests::Support::RuboCopFormatter.new)
|
278
269
|
end
|
@@ -280,7 +271,7 @@ module RubyLsp
|
|
280
271
|
@global_state.register_formatter("syntax_tree", Requests::Support::SyntaxTreeFormatter.new)
|
281
272
|
end
|
282
273
|
|
283
|
-
perform_initial_indexing
|
274
|
+
perform_initial_indexing
|
284
275
|
check_formatter_is_available
|
285
276
|
end
|
286
277
|
|
@@ -766,12 +757,10 @@ module RubyLsp
|
|
766
757
|
Addon.addons.each(&:deactivate)
|
767
758
|
end
|
768
759
|
|
769
|
-
sig {
|
770
|
-
def perform_initial_indexing
|
760
|
+
sig { void }
|
761
|
+
def perform_initial_indexing
|
771
762
|
# The begin progress invocation happens during `initialize`, so that the notification is sent before we are
|
772
763
|
# stuck indexing files
|
773
|
-
RubyIndexer.configuration.apply_config(config_hash)
|
774
|
-
|
775
764
|
Thread.new do
|
776
765
|
begin
|
777
766
|
RubyIndexer::RBSIndexer.new(@global_state.index).index_ruby_core
|
@@ -872,5 +861,46 @@ module RubyLsp
|
|
872
861
|
)
|
873
862
|
end
|
874
863
|
end
|
864
|
+
|
865
|
+
sig { params(indexing_options: T.nilable(T::Hash[Symbol, T.untyped])).void }
|
866
|
+
def process_indexing_configuration(indexing_options)
|
867
|
+
# Need to use the workspace URI, otherwise, this will fail for people working on a project that is a symlink.
|
868
|
+
index_path = File.join(@global_state.workspace_path, ".index.yml")
|
869
|
+
|
870
|
+
if File.exist?(index_path)
|
871
|
+
begin
|
872
|
+
RubyIndexer.configuration.apply_config(YAML.parse_file(index_path).to_ruby)
|
873
|
+
send_message(
|
874
|
+
Notification.new(
|
875
|
+
method: "window/showMessage",
|
876
|
+
params: Interface::ShowMessageParams.new(
|
877
|
+
type: Constant::MessageType::WARNING,
|
878
|
+
message: "The .index.yml configuration file is deprecated. " \
|
879
|
+
"Please use editor settings to configure the index",
|
880
|
+
),
|
881
|
+
),
|
882
|
+
)
|
883
|
+
rescue Psych::SyntaxError => e
|
884
|
+
message = "Syntax error while loading configuration: #{e.message}"
|
885
|
+
send_message(
|
886
|
+
Notification.new(
|
887
|
+
method: "window/showMessage",
|
888
|
+
params: Interface::ShowMessageParams.new(
|
889
|
+
type: Constant::MessageType::WARNING,
|
890
|
+
message: message,
|
891
|
+
),
|
892
|
+
),
|
893
|
+
)
|
894
|
+
end
|
895
|
+
return
|
896
|
+
end
|
897
|
+
|
898
|
+
return unless indexing_options
|
899
|
+
|
900
|
+
# The index expects snake case configurations, but VS Code standardizes on camel case settings
|
901
|
+
RubyIndexer.configuration.apply_config(
|
902
|
+
indexing_options.transform_keys { |key| key.to_s.gsub(/([A-Z])/, "_\\1").downcase },
|
903
|
+
)
|
904
|
+
end
|
875
905
|
end
|
876
906
|
end
|
data/lib/ruby_lsp/store.rb
CHANGED
@@ -5,6 +5,8 @@ module RubyLsp
|
|
5
5
|
class Store
|
6
6
|
extend T::Sig
|
7
7
|
|
8
|
+
class NonExistingDocumentError < StandardError; end
|
9
|
+
|
8
10
|
sig { returns(T::Boolean) }
|
9
11
|
attr_accessor :supports_progress
|
10
12
|
|
@@ -45,6 +47,8 @@ module RubyLsp
|
|
45
47
|
end
|
46
48
|
set(uri: uri, source: File.binread(path), version: 0, language_id: language_id)
|
47
49
|
T.must(@state[uri.to_s])
|
50
|
+
rescue Errno::ENOENT
|
51
|
+
raise NonExistingDocumentError, uri.to_s
|
48
52
|
end
|
49
53
|
|
50
54
|
sig do
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-lsp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.17.
|
4
|
+
version: 0.17.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shopify
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-07-
|
11
|
+
date: 2024-07-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: language_server-protocol
|
@@ -84,7 +84,6 @@ email:
|
|
84
84
|
executables:
|
85
85
|
- ruby-lsp
|
86
86
|
- ruby-lsp-check
|
87
|
-
- ruby-lsp-doctor
|
88
87
|
extensions: []
|
89
88
|
extra_rdoc_files: []
|
90
89
|
files:
|
@@ -93,7 +92,6 @@ files:
|
|
93
92
|
- VERSION
|
94
93
|
- exe/ruby-lsp
|
95
94
|
- exe/ruby-lsp-check
|
96
|
-
- exe/ruby-lsp-doctor
|
97
95
|
- lib/core_ext/uri.rb
|
98
96
|
- lib/rubocop/cop/ruby_lsp/use_language_server_aliases.rb
|
99
97
|
- lib/rubocop/cop/ruby_lsp/use_register_with_handler_method.rb
|
@@ -206,7 +204,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
206
204
|
- !ruby/object:Gem::Version
|
207
205
|
version: '0'
|
208
206
|
requirements: []
|
209
|
-
rubygems_version: 3.5.
|
207
|
+
rubygems_version: 3.5.15
|
210
208
|
signing_key:
|
211
209
|
specification_version: 4
|
212
210
|
summary: An opinionated language server for Ruby
|
data/exe/ruby-lsp-doctor
DELETED
@@ -1,23 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
$LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
|
5
|
-
require "ruby_lsp/internal"
|
6
|
-
|
7
|
-
if File.exist?(".index.yml")
|
8
|
-
begin
|
9
|
-
config = YAML.parse_file(".index.yml").to_ruby
|
10
|
-
rescue => e
|
11
|
-
abort("Error parsing config: #{e.message}")
|
12
|
-
end
|
13
|
-
RubyIndexer.configuration.apply_config(config)
|
14
|
-
end
|
15
|
-
|
16
|
-
index = RubyIndexer::Index.new
|
17
|
-
|
18
|
-
puts "Globbing for indexable files"
|
19
|
-
|
20
|
-
RubyIndexer.configuration.indexables.each do |indexable|
|
21
|
-
puts "indexing: #{indexable.full_path}"
|
22
|
-
index.index_single(indexable)
|
23
|
-
end
|