ruby-lsp 0.17.7 → 0.17.8

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 671bab4fdbd77d1980781f55874d6cd448e1dcd11a6f534a176dfb00b098d2eb
4
- data.tar.gz: bd12fd2ef58588b3b5386fec48c0f561140efff180988f1971b67f635f0e1042
3
+ metadata.gz: e3a8a120b4cf3045cdeec98d2fb417ffdd300947b6426788d827346c43812ffc
4
+ data.tar.gz: b43a823e71fca99a9cf79b3a6ce19e7a0ec86e499b943aecae1666788859d055
5
5
  SHA512:
6
- metadata.gz: f3a2a3115e1745ad7263b7e113ef13795f9a1ed1ee118128a9ab2aaad703b17551acbd929f820700e6650efb76421418a13a78270ac8d3aaff080edc46731aee
7
- data.tar.gz: d29bb4d6c60727eef1a10905762a6d0c8946b7afc8aac6148ef970c8d752365f1577403497702f52a7211689cde7940b3d7cd1e1e0eb03d038e0dc6f7ceed738
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
- By creating a `.index.yml` file, these configurations can be overridden and tuned. Note that indexing dependent behavior, such as definition, hover, completion or workspace symbol will be impacted by the configurations placed here.
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.7
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 = name.start_with?("::") ? [name.delete_prefix("::")] : @stack + [name.delete_prefix("::")]
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
- nesting = name.start_with?("::") ? [name.delete_prefix("::")] : @stack + [name.delete_prefix("::")]
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
@@ -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 Errno::ENOENT
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
- add_local_completions(node, name)
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
@@ -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
- send_message(Error.new(
102
- id: message[:id],
103
- code: Constant::ErrorCodes::INTERNAL_ERROR,
104
- message: e.full_message,
105
- data: {
106
- errorClass: e.class.name,
107
- errorMessage: e.message,
108
- backtrace: e.backtrace&.join("\n"),
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(indexing_config)
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 { params(config_hash: T::Hash[String, T.untyped]).void }
770
- def perform_initial_indexing(config_hash)
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
@@ -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.7
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 00:00:00.000000000 Z
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.14
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