ruby-lsp 0.17.4 → 0.17.6

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.
@@ -11,19 +11,24 @@ module RubyLsp
11
11
 
12
12
  sig do
13
13
  params(
14
- response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::Location],
14
+ response_builder: ResponseBuilders::CollectionResponseBuilder[T.any(
15
+ Interface::Location,
16
+ Interface::LocationLink,
17
+ )],
15
18
  global_state: GlobalState,
19
+ language_id: Document::LanguageId,
16
20
  uri: URI::Generic,
17
21
  node_context: NodeContext,
18
22
  dispatcher: Prism::Dispatcher,
19
23
  typechecker_enabled: T::Boolean,
20
24
  ).void
21
25
  end
22
- def initialize(response_builder, global_state, uri, node_context, dispatcher, typechecker_enabled) # rubocop:disable Metrics/ParameterLists
26
+ def initialize(response_builder, global_state, language_id, uri, node_context, dispatcher, typechecker_enabled) # rubocop:disable Metrics/ParameterLists
23
27
  @response_builder = response_builder
24
28
  @global_state = global_state
25
29
  @index = T.let(global_state.index, RubyIndexer::Index)
26
30
  @type_inferrer = T.let(global_state.type_inferrer, TypeInferrer)
31
+ @language_id = language_id
27
32
  @uri = uri
28
33
  @node_context = node_context
29
34
  @typechecker_enabled = typechecker_enabled
@@ -49,7 +54,15 @@ module RubyLsp
49
54
  message = node.message
50
55
  return unless message
51
56
 
52
- handle_method_definition(message, @type_inferrer.infer_receiver_type(@node_context))
57
+ inferrer_receiver_type = @type_inferrer.infer_receiver_type(@node_context)
58
+
59
+ # Until we can properly infer the receiver type in erb files (maybe with ruby-lsp-rails),
60
+ # treating method calls' type as `nil` will allow users to get some completion support first
61
+ if @language_id == Document::LanguageId::ERB && inferrer_receiver_type == "Object"
62
+ inferrer_receiver_type = nil
63
+ end
64
+
65
+ handle_method_definition(message, inferrer_receiver_type)
53
66
  end
54
67
 
55
68
  sig { params(node: Prism::StringNode).void }
@@ -158,16 +171,13 @@ module RubyLsp
158
171
  return unless methods
159
172
 
160
173
  methods.each do |target_method|
161
- location = target_method.location
162
174
  file_path = target_method.file_path
163
175
  next if @typechecker_enabled && not_in_dependencies?(file_path)
164
176
 
165
- @response_builder << Interface::Location.new(
166
- uri: URI::Generic.from_path(path: file_path).to_s,
167
- range: Interface::Range.new(
168
- start: Interface::Position.new(line: location.start_line - 1, character: location.start_column),
169
- end: Interface::Position.new(line: location.end_line - 1, character: location.end_column),
170
- ),
177
+ @response_builder << Interface::LocationLink.new(
178
+ target_uri: URI::Generic.from_path(path: file_path).to_s,
179
+ target_range: range_from_location(target_method.location),
180
+ target_selection_range: range_from_location(target_method.name_location),
171
181
  )
172
182
  end
173
183
  end
@@ -218,19 +228,16 @@ module RubyLsp
218
228
  return if first_entry.private? && first_entry.name != "#{@node_context.fully_qualified_name}::#{value}"
219
229
 
220
230
  entries.each do |entry|
221
- location = entry.location
222
231
  # If the project has Sorbet, then we only want to handle go to definition for constants defined in gems, as an
223
232
  # additional behavior on top of jumping to RBIs. Sorbet can already handle go to definition for all constants
224
233
  # in the project, even if the files are typed false
225
234
  file_path = entry.file_path
226
235
  next if @typechecker_enabled && not_in_dependencies?(file_path)
227
236
 
228
- @response_builder << Interface::Location.new(
229
- uri: URI::Generic.from_path(path: file_path).to_s,
230
- range: Interface::Range.new(
231
- start: Interface::Position.new(line: location.start_line - 1, character: location.start_column),
232
- end: Interface::Position.new(line: location.end_line - 1, character: location.end_column),
233
- ),
237
+ @response_builder << Interface::LocationLink.new(
238
+ target_uri: URI::Generic.from_path(path: file_path).to_s,
239
+ target_range: range_from_location(entry.location),
240
+ target_selection_range: range_from_location(entry.name_location),
234
241
  )
235
242
  end
236
243
  end
@@ -69,7 +69,7 @@ module RubyLsp
69
69
 
70
70
  # Find the closest statements node, so that we place the refactor in a valid position
71
71
  node_context = @document
72
- .locate(@document.tree, start_index, node_types: [Prism::StatementsNode, Prism::BlockNode])
72
+ .locate(@document.parse_result.value, start_index, node_types: [Prism::StatementsNode, Prism::BlockNode])
73
73
 
74
74
  closest_statements = node_context.node
75
75
  parent_statements = node_context.parent
@@ -164,7 +164,7 @@ module RubyLsp
164
164
  extracted_source = T.must(@document.source[start_index...end_index])
165
165
 
166
166
  # Find the closest method declaration node, so that we place the refactor in a valid position
167
- node_context = @document.locate(@document.tree, start_index, node_types: [Prism::DefNode])
167
+ node_context = @document.locate(@document.parse_result.value, start_index, node_types: [Prism::DefNode])
168
168
  closest_def = T.cast(node_context.node, Prism::DefNode)
169
169
  return Error::InvalidTargetRange if closest_def.nil?
170
170
 
@@ -61,7 +61,7 @@ module RubyLsp
61
61
  # back by 1, so that we find the right node
62
62
  char_position = document.create_scanner.find_char_position(params[:position]) - 1
63
63
  node_context = document.locate(
64
- document.tree,
64
+ document.parse_result.value,
65
65
  char_position,
66
66
  node_types: [
67
67
  Prism::CallNode,
@@ -40,7 +40,8 @@ module RubyLsp
40
40
  def perform
41
41
  # Based on the spec https://microsoft.github.io/language-server-protocol/specification#textDocument_completion,
42
42
  # a completion resolve request must always return the original completion item without modifying ANY fields
43
- # other than label details and documentation. If we modify anything, the completion behaviour might be broken.
43
+ # other than detail and documentation (NOT labelDetails). If we modify anything, the completion behaviour might
44
+ # be broken.
44
45
  #
45
46
  # For example, forgetting to return the `insertText` included in the original item will make the editor use the
46
47
  # `label` for the text edit instead
@@ -59,15 +60,9 @@ module RubyLsp
59
60
  first_entry = T.must(entries.first)
60
61
 
61
62
  if first_entry.is_a?(RubyIndexer::Entry::Member)
62
- detail = first_entry.decorated_parameters
63
63
  label = "#{label}#{first_entry.decorated_parameters}"
64
64
  end
65
65
 
66
- @item[:labelDetails] = Interface::CompletionItemLabelDetails.new(
67
- description: entries.take(MAX_DOCUMENTATION_ENTRIES).map(&:file_name).join(","),
68
- detail: detail,
69
- )
70
-
71
66
  @item[:documentation] = Interface::MarkupContent.new(
72
67
  kind: "markdown",
73
68
  value: markdown_from_index_entries(label, entries, MAX_DOCUMENTATION_ENTRIES),
@@ -42,8 +42,8 @@ module RubyLsp
42
42
  def initialize(document, global_state, position, dispatcher, typechecker_enabled)
43
43
  super()
44
44
  @response_builder = T.let(
45
- ResponseBuilders::CollectionResponseBuilder[Interface::Location].new,
46
- ResponseBuilders::CollectionResponseBuilder[Interface::Location],
45
+ ResponseBuilders::CollectionResponseBuilder[T.any(Interface::Location, Interface::LocationLink)].new,
46
+ ResponseBuilders::CollectionResponseBuilder[T.any(Interface::Location, Interface::LocationLink)],
47
47
  )
48
48
  @dispatcher = dispatcher
49
49
 
@@ -90,6 +90,7 @@ module RubyLsp
90
90
  Listeners::Definition.new(
91
91
  @response_builder,
92
92
  global_state,
93
+ document.language_id,
93
94
  document.uri,
94
95
  node_context,
95
96
  dispatcher,
@@ -104,7 +105,7 @@ module RubyLsp
104
105
  @target = T.let(target, T.nilable(Prism::Node))
105
106
  end
106
107
 
107
- sig { override.returns(T::Array[Interface::Location]) }
108
+ sig { override.returns(T::Array[T.any(Interface::Location, Interface::LocationLink)]) }
108
109
  def perform
109
110
  @dispatcher.dispatch_once(@target) if @target
110
111
  @response_builder.response
@@ -40,6 +40,8 @@ module RubyLsp
40
40
  return unless @active_formatter
41
41
  return if @document.syntax_error?
42
42
 
43
+ # We don't format erb documents yet
44
+
43
45
  formatted_text = @active_formatter.run_formatting(@uri, @document)
44
46
  return unless formatted_text
45
47
 
@@ -34,7 +34,7 @@ module RubyLsp
34
34
  sig { override.returns(T.all(T::Array[Support::SelectionRange], Object)) }
35
35
  def perform
36
36
  # [node, parent]
37
- queue = [[@document.tree, nil]]
37
+ queue = [[@document.parse_result.value, nil]]
38
38
 
39
39
  until queue.empty?
40
40
  node, parent = queue.shift
@@ -25,6 +25,7 @@ module RubyLsp
25
25
  super()
26
26
  @document = document
27
27
  @range = range
28
+ @tree = T.let(document.parse_result.value, Prism::ProgramNode)
28
29
  end
29
30
 
30
31
  sig { override.returns(String) }
@@ -32,7 +33,7 @@ module RubyLsp
32
33
  return ast_for_range if @range
33
34
 
34
35
  output_string = +""
35
- PP.pp(@document.tree, output_string)
36
+ PP.pp(@tree, output_string)
36
37
  output_string
37
38
  end
38
39
 
@@ -46,7 +47,7 @@ module RubyLsp
46
47
  start_char = scanner.find_char_position(range[:start])
47
48
  end_char = scanner.find_char_position(range[:end])
48
49
 
49
- queue = @document.tree.statements.body.dup
50
+ queue = @tree.statements.body.dup
50
51
  found_nodes = []
51
52
 
52
53
  until queue.empty?
@@ -54,8 +54,6 @@ module RubyLsp
54
54
  end
55
55
 
56
56
  entry.mixin_operations.each do |mixin_operation|
57
- next if mixin_operation.is_a?(RubyIndexer::Entry::Extend)
58
-
59
57
  mixin_name = mixin_operation.module_name
60
58
  resolved_mixin_entries = @index.resolve(mixin_name, entry.nesting)
61
59
  next unless resolved_mixin_entries
@@ -75,14 +73,12 @@ module RubyLsp
75
73
 
76
74
  sig { params(entry: RubyIndexer::Entry).returns(Interface::TypeHierarchyItem) }
77
75
  def hierarchy_item(entry)
78
- range = range_from_location(entry.location)
79
-
80
76
  Interface::TypeHierarchyItem.new(
81
77
  name: entry.name,
82
78
  kind: kind_for_entry(entry),
83
79
  uri: URI::Generic.from_path(path: entry.file_path).to_s,
84
- range: range,
85
- selection_range: range,
80
+ range: range_from_location(entry.location),
81
+ selection_range: range_from_location(entry.name_location),
86
82
  detail: entry.file_name,
87
83
  )
88
84
  end
@@ -10,5 +10,15 @@ module RubyLsp
10
10
  @needs_parsing = false
11
11
  @parse_result = Prism.parse(@source)
12
12
  end
13
+
14
+ sig { override.returns(T::Boolean) }
15
+ def syntax_error?
16
+ @parse_result.failure?
17
+ end
18
+
19
+ sig { override.returns(LanguageId) }
20
+ def language_id
21
+ LanguageId::Ruby
22
+ end
13
23
  end
14
24
  end
@@ -98,7 +98,16 @@ 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(id: message[:id], code: Constant::ErrorCodes::INTERNAL_ERROR, message: e.full_message))
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
+ ))
102
111
  end
103
112
 
104
113
  $stderr.puts("Error processing #{message[:method]}: #{e.full_message}")
@@ -137,7 +146,6 @@ module RubyLsp
137
146
  progress = options.dig(:capabilities, :window, :workDoneProgress)
138
147
  @store.supports_progress = progress.nil? ? true : progress
139
148
  configured_features = options.dig(:initializationOptions, :enabledFeatures)
140
- @store.experimental_features = options.dig(:initializationOptions, :experimentalFeaturesEnabled) || false
141
149
 
142
150
  configured_hints = options.dig(:initializationOptions, :featuresConfiguration, :inlayHint)
143
151
  T.must(@store.features_configuration.dig(:inlayHint)).configuration.merge!(configured_hints) if configured_hints
@@ -280,11 +288,18 @@ module RubyLsp
280
288
  def text_document_did_open(message)
281
289
  @mutex.synchronize do
282
290
  text_document = message.dig(:params, :textDocument)
291
+ language_id = case text_document[:languageId]
292
+ when "erb"
293
+ Document::LanguageId::ERB
294
+ else
295
+ Document::LanguageId::Ruby
296
+ end
283
297
  @store.set(
284
298
  uri: text_document[:uri],
285
299
  source: text_document[:text],
286
300
  version: text_document[:version],
287
301
  encoding: @global_state.encoding,
302
+ language_id: language_id,
288
303
  )
289
304
  end
290
305
  end
@@ -348,15 +363,17 @@ module RubyLsp
348
363
  return
349
364
  end
350
365
 
366
+ parse_result = document.parse_result
367
+
351
368
  # Run requests for the document
352
369
  dispatcher = Prism::Dispatcher.new
353
- folding_range = Requests::FoldingRanges.new(document.parse_result.comments, dispatcher)
370
+ folding_range = Requests::FoldingRanges.new(parse_result.comments, dispatcher)
354
371
  document_symbol = Requests::DocumentSymbol.new(uri, dispatcher)
355
- document_link = Requests::DocumentLink.new(uri, document.comments, dispatcher)
372
+ document_link = Requests::DocumentLink.new(uri, parse_result.comments, dispatcher)
356
373
  code_lens = Requests::CodeLens.new(@global_state, uri, dispatcher)
357
374
 
358
375
  semantic_highlighting = Requests::SemanticHighlighting.new(@global_state, dispatcher)
359
- dispatcher.dispatch(document.tree)
376
+ dispatcher.dispatch(parse_result.value)
360
377
 
361
378
  # Store all responses retrieve in this round of visits in the cache and then return the response for the request
362
379
  # we actually received
@@ -388,7 +405,7 @@ module RubyLsp
388
405
 
389
406
  dispatcher = Prism::Dispatcher.new
390
407
  request = Requests::SemanticHighlighting.new(@global_state, dispatcher, range: start_line..end_line)
391
- dispatcher.visit(document.tree)
408
+ dispatcher.visit(document.parse_result.value)
392
409
 
393
410
  response = request.perform
394
411
  send_message(Result.new(id: message[:id], response: response))
@@ -411,7 +428,11 @@ module RubyLsp
411
428
  return
412
429
  end
413
430
 
414
- response = Requests::Formatting.new(@global_state, @store.get(uri)).perform
431
+ document = @store.get(uri)
432
+
433
+ return send_empty_response(message[:id]) if document.is_a?(ERBDocument)
434
+
435
+ response = Requests::Formatting.new(@global_state, document).perform
415
436
  send_message(Result.new(id: message[:id], response: response))
416
437
  rescue Requests::Request::InvalidFormatter => error
417
438
  send_message(Notification.window_show_error("Configuration error: #{error.message}"))
@@ -427,19 +448,22 @@ module RubyLsp
427
448
  dispatcher = Prism::Dispatcher.new
428
449
  document = @store.get(params.dig(:textDocument, :uri))
429
450
  request = Requests::DocumentHighlight.new(document, params[:position], dispatcher)
430
- dispatcher.dispatch(document.tree)
451
+ dispatcher.dispatch(document.parse_result.value)
431
452
  send_message(Result.new(id: message[:id], response: request.perform))
432
453
  end
433
454
 
434
455
  sig { params(message: T::Hash[Symbol, T.untyped]).void }
435
456
  def text_document_on_type_formatting(message)
436
457
  params = message[:params]
458
+ document = @store.get(params.dig(:textDocument, :uri))
459
+
460
+ return send_empty_response(message[:id]) if document.is_a?(ERBDocument)
437
461
 
438
462
  send_message(
439
463
  Result.new(
440
464
  id: message[:id],
441
465
  response: Requests::OnTypeFormatting.new(
442
- @store.get(params.dig(:textDocument, :uri)),
466
+ document,
443
467
  params[:position],
444
468
  params[:ch],
445
469
  @store.client_name,
@@ -480,7 +504,7 @@ module RubyLsp
480
504
  dispatcher = Prism::Dispatcher.new
481
505
  document = @store.get(params.dig(:textDocument, :uri))
482
506
  request = Requests::InlayHints.new(document, params[:range], hints_configurations, dispatcher)
483
- dispatcher.visit(document.tree)
507
+ dispatcher.visit(document.parse_result.value)
484
508
  send_message(Result.new(id: message[:id], response: request.perform))
485
509
  end
486
510
 
@@ -489,6 +513,8 @@ module RubyLsp
489
513
  params = message[:params]
490
514
  document = @store.get(params.dig(:textDocument, :uri))
491
515
 
516
+ return send_empty_response(message[:id]) if document.is_a?(ERBDocument)
517
+
492
518
  send_message(
493
519
  Result.new(
494
520
  id: message[:id],
@@ -542,7 +568,11 @@ module RubyLsp
542
568
  return
543
569
  end
544
570
 
545
- response = @store.cache_fetch(uri, "textDocument/diagnostic") do |document|
571
+ document = @store.get(uri)
572
+
573
+ return send_empty_response(message[:id]) if document.is_a?(ERBDocument)
574
+
575
+ response = document.cache_fetch("textDocument/diagnostic") do |document|
546
576
  Requests::Diagnostics.new(@global_state, document).perform
547
577
  end
548
578
 
@@ -8,9 +8,6 @@ module RubyLsp
8
8
  sig { returns(T::Boolean) }
9
9
  attr_accessor :supports_progress
10
10
 
11
- sig { returns(T::Boolean) }
12
- attr_accessor :experimental_features
13
-
14
11
  sig { returns(T::Hash[Symbol, RequestConfig]) }
15
12
  attr_accessor :features_configuration
16
13
 
@@ -21,7 +18,6 @@ module RubyLsp
21
18
  def initialize
22
19
  @state = T.let({}, T::Hash[String, Document])
23
20
  @supports_progress = T.let(true, T::Boolean)
24
- @experimental_features = T.let(false, T::Boolean)
25
21
  @features_configuration = T.let(
26
22
  {
27
23
  inlayHint: RequestConfig.new({
@@ -41,13 +37,32 @@ module RubyLsp
41
37
  return document unless document.nil?
42
38
 
43
39
  path = T.must(uri.to_standardized_path)
44
- set(uri: uri, source: File.binread(path), version: 0)
40
+ ext = File.extname(path)
41
+ language_id = if ext == ".erb" || ext == ".rhtml"
42
+ Document::LanguageId::ERB
43
+ else
44
+ Document::LanguageId::Ruby
45
+ end
46
+ set(uri: uri, source: File.binread(path), version: 0, language_id: language_id)
45
47
  T.must(@state[uri.to_s])
46
48
  end
47
49
 
48
- sig { params(uri: URI::Generic, source: String, version: Integer, encoding: Encoding).void }
49
- def set(uri:, source:, version:, encoding: Encoding::UTF_8)
50
- document = RubyDocument.new(source: source, version: version, uri: uri, encoding: encoding)
50
+ sig do
51
+ params(
52
+ uri: URI::Generic,
53
+ source: String,
54
+ version: Integer,
55
+ language_id: Document::LanguageId,
56
+ encoding: Encoding,
57
+ ).void
58
+ end
59
+ def set(uri:, source:, version:, language_id:, encoding: Encoding::UTF_8)
60
+ document = case language_id
61
+ when Document::LanguageId::ERB
62
+ ERBDocument.new(source: source, version: version, uri: uri, encoding: encoding)
63
+ else
64
+ RubyDocument.new(source: source, version: version, uri: uri, encoding: encoding)
65
+ end
51
66
  @state[uri.to_s] = document
52
67
  end
53
68
 
@@ -22,6 +22,7 @@ module RubyLsp
22
22
  server = RubyLsp::Server.new(test_mode: true)
23
23
  server.global_state.stubs(:has_type_checker).returns(false) if stub_no_typechecker
24
24
  server.global_state.apply_options({})
25
+ language_id = uri.to_s.end_with?(".erb") ? "erb" : "ruby"
25
26
 
26
27
  if source
27
28
  server.process_message({
@@ -31,6 +32,7 @@ module RubyLsp
31
32
  uri: uri,
32
33
  text: source,
33
34
  version: 1,
35
+ languageId: language_id,
34
36
  },
35
37
  },
36
38
  })
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
4
+ version: 0.17.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-06-18 00:00:00.000000000 Z
11
+ date: 2024-07-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: language_server-protocol
@@ -120,6 +120,7 @@ files:
120
120
  - lib/ruby_lsp/base_server.rb
121
121
  - lib/ruby_lsp/check_docs.rb
122
122
  - lib/ruby_lsp/document.rb
123
+ - lib/ruby_lsp/erb_document.rb
123
124
  - lib/ruby_lsp/global_state.rb
124
125
  - lib/ruby_lsp/internal.rb
125
126
  - lib/ruby_lsp/listeners/code_lens.rb
@@ -190,7 +191,7 @@ licenses:
190
191
  metadata:
191
192
  allowed_push_host: https://rubygems.org
192
193
  documentation_uri: https://shopify.github.io/ruby-lsp/
193
- post_install_message:
194
+ post_install_message:
194
195
  rdoc_options: []
195
196
  require_paths:
196
197
  - lib
@@ -205,8 +206,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
205
206
  - !ruby/object:Gem::Version
206
207
  version: '0'
207
208
  requirements: []
208
- rubygems_version: 3.5.11
209
- signing_key:
209
+ rubygems_version: 3.5.14
210
+ signing_key:
210
211
  specification_version: 4
211
212
  summary: An opinionated language server for Ruby
212
213
  test_files: []