ruby-lsp 0.17.4 → 0.17.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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.5
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-09 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: []