ruby-lsp 0.17.13 → 0.17.15

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/exe/ruby-lsp +2 -0
  4. data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +28 -9
  5. data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +5 -3
  6. data/lib/ruby_indexer/test/classes_and_modules_test.rb +21 -0
  7. data/lib/ruby_indexer/test/configuration_test.rb +41 -7
  8. data/lib/ruby_lsp/document.rb +9 -114
  9. data/lib/ruby_lsp/erb_document.rb +16 -2
  10. data/lib/ruby_lsp/global_state.rb +1 -1
  11. data/lib/ruby_lsp/internal.rb +1 -0
  12. data/lib/ruby_lsp/listeners/definition.rb +8 -5
  13. data/lib/ruby_lsp/rbs_document.rb +41 -0
  14. data/lib/ruby_lsp/requests/code_action_resolve.rb +34 -18
  15. data/lib/ruby_lsp/requests/code_actions.rb +1 -1
  16. data/lib/ruby_lsp/requests/completion.rb +2 -2
  17. data/lib/ruby_lsp/requests/definition.rb +1 -1
  18. data/lib/ruby_lsp/requests/diagnostics.rb +1 -1
  19. data/lib/ruby_lsp/requests/document_highlight.rb +1 -1
  20. data/lib/ruby_lsp/requests/formatting.rb +1 -1
  21. data/lib/ruby_lsp/requests/hover.rb +1 -1
  22. data/lib/ruby_lsp/requests/inlay_hints.rb +1 -1
  23. data/lib/ruby_lsp/requests/on_type_formatting.rb +1 -1
  24. data/lib/ruby_lsp/requests/prepare_type_hierarchy.rb +1 -1
  25. data/lib/ruby_lsp/requests/selection_ranges.rb +2 -1
  26. data/lib/ruby_lsp/requests/show_syntax_tree.rb +1 -1
  27. data/lib/ruby_lsp/requests/signature_help.rb +1 -1
  28. data/lib/ruby_lsp/requests/support/formatter.rb +2 -2
  29. data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +1 -1
  30. data/lib/ruby_lsp/requests/support/rubocop_formatter.rb +2 -2
  31. data/lib/ruby_lsp/requests/support/syntax_tree_formatter.rb +2 -2
  32. data/lib/ruby_lsp/ruby_document.rb +119 -1
  33. data/lib/ruby_lsp/server.rb +96 -8
  34. data/lib/ruby_lsp/setup_bundler.rb +18 -7
  35. data/lib/ruby_lsp/store.rb +10 -6
  36. metadata +3 -2
@@ -29,7 +29,7 @@ module RubyLsp
29
29
 
30
30
  sig do
31
31
  params(
32
- document: Document,
32
+ document: T.any(RubyDocument, ERBDocument),
33
33
  position: T::Hash[Symbol, T.untyped],
34
34
  dispatcher: Prism::Dispatcher,
35
35
  ).void
@@ -40,7 +40,7 @@ module RubyLsp
40
40
  end
41
41
  end
42
42
 
43
- sig { params(global_state: GlobalState, document: Document).void }
43
+ sig { params(global_state: GlobalState, document: RubyDocument).void }
44
44
  def initialize(global_state, document)
45
45
  super()
46
46
  @document = document
@@ -32,7 +32,7 @@ module RubyLsp
32
32
 
33
33
  sig do
34
34
  params(
35
- document: Document,
35
+ document: T.any(RubyDocument, ERBDocument),
36
36
  global_state: GlobalState,
37
37
  position: T::Hash[Symbol, T.untyped],
38
38
  dispatcher: Prism::Dispatcher,
@@ -52,7 +52,7 @@ module RubyLsp
52
52
 
53
53
  sig do
54
54
  params(
55
- document: Document,
55
+ document: T.any(RubyDocument, ERBDocument),
56
56
  range: T::Hash[Symbol, T.untyped],
57
57
  hints_configuration: RequestConfig,
58
58
  dispatcher: Prism::Dispatcher,
@@ -42,7 +42,7 @@ module RubyLsp
42
42
 
43
43
  sig do
44
44
  params(
45
- document: Document,
45
+ document: RubyDocument,
46
46
  position: T::Hash[Symbol, T.untyped],
47
47
  trigger_character: String,
48
48
  client_name: String,
@@ -35,7 +35,7 @@ module RubyLsp
35
35
 
36
36
  sig do
37
37
  params(
38
- document: Document,
38
+ document: T.any(RubyDocument, ERBDocument),
39
39
  index: RubyIndexer::Index,
40
40
  position: T::Hash[Symbol, T.untyped],
41
41
  ).void
@@ -23,7 +23,8 @@ module RubyLsp
23
23
  class SelectionRanges < Request
24
24
  extend T::Sig
25
25
  include Support::Common
26
- sig { params(document: Document).void }
26
+
27
+ sig { params(document: T.any(RubyDocument, ERBDocument)).void }
27
28
  def initialize(document)
28
29
  super()
29
30
  @document = document
@@ -20,7 +20,7 @@ module RubyLsp
20
20
  class ShowSyntaxTree < Request
21
21
  extend T::Sig
22
22
 
23
- sig { params(document: Document, range: T.nilable(T::Hash[Symbol, T.untyped])).void }
23
+ sig { params(document: RubyDocument, range: T.nilable(T::Hash[Symbol, T.untyped])).void }
24
24
  def initialize(document, range)
25
25
  super()
26
26
  @document = document
@@ -41,7 +41,7 @@ module RubyLsp
41
41
 
42
42
  sig do
43
43
  params(
44
- document: Document,
44
+ document: T.any(RubyDocument, ERBDocument),
45
45
  global_state: GlobalState,
46
46
  position: T::Hash[Symbol, T.untyped],
47
47
  context: T.nilable(T::Hash[Symbol, T.untyped]),
@@ -10,13 +10,13 @@ module RubyLsp
10
10
 
11
11
  interface!
12
12
 
13
- sig { abstract.params(uri: URI::Generic, document: Document).returns(T.nilable(String)) }
13
+ sig { abstract.params(uri: URI::Generic, document: RubyDocument).returns(T.nilable(String)) }
14
14
  def run_formatting(uri, document); end
15
15
 
16
16
  sig do
17
17
  abstract.params(
18
18
  uri: URI::Generic,
19
- document: Document,
19
+ document: RubyDocument,
20
20
  ).returns(T.nilable(T::Array[Interface::Diagnostic]))
21
21
  end
22
22
  def run_diagnostic(uri, document); end
@@ -31,7 +31,7 @@ module RubyLsp
31
31
 
32
32
  # TODO: avoid passing document once we have alternative ways to get at
33
33
  # encoding and file source
34
- sig { params(document: Document, offense: RuboCop::Cop::Offense, uri: URI::Generic).void }
34
+ sig { params(document: RubyDocument, offense: RuboCop::Cop::Offense, uri: URI::Generic).void }
35
35
  def initialize(document, offense, uri)
36
36
  @document = document
37
37
  @offense = offense
@@ -17,7 +17,7 @@ module RubyLsp
17
17
  @format_runner = T.let(RuboCopRunner.new("-a"), RuboCopRunner)
18
18
  end
19
19
 
20
- sig { override.params(uri: URI::Generic, document: Document).returns(T.nilable(String)) }
20
+ sig { override.params(uri: URI::Generic, document: RubyDocument).returns(T.nilable(String)) }
21
21
  def run_formatting(uri, document)
22
22
  filename = T.must(uri.to_standardized_path || uri.opaque)
23
23
 
@@ -29,7 +29,7 @@ module RubyLsp
29
29
  sig do
30
30
  override.params(
31
31
  uri: URI::Generic,
32
- document: Document,
32
+ document: RubyDocument,
33
33
  ).returns(T.nilable(T::Array[Interface::Diagnostic]))
34
34
  end
35
35
  def run_diagnostic(uri, document)
@@ -29,7 +29,7 @@ module RubyLsp
29
29
  )
30
30
  end
31
31
 
32
- sig { override.params(uri: URI::Generic, document: Document).returns(T.nilable(String)) }
32
+ sig { override.params(uri: URI::Generic, document: RubyDocument).returns(T.nilable(String)) }
33
33
  def run_formatting(uri, document)
34
34
  path = uri.to_standardized_path
35
35
  return if path && @options.ignore_files.any? { |pattern| File.fnmatch?("*/#{pattern}", path) }
@@ -40,7 +40,7 @@ module RubyLsp
40
40
  sig do
41
41
  override.params(
42
42
  uri: URI::Generic,
43
- document: Document,
43
+ document: RubyDocument,
44
44
  ).returns(T.nilable(T::Array[Interface::Diagnostic]))
45
45
  end
46
46
  def run_diagnostic(uri, document)
@@ -3,6 +3,11 @@
3
3
 
4
4
  module RubyLsp
5
5
  class RubyDocument < Document
6
+ extend T::Sig
7
+ extend T::Generic
8
+
9
+ ParseResultType = type_member { { fixed: Prism::ParseResult } }
10
+
6
11
  class SorbetLevel < T::Enum
7
12
  enums do
8
13
  None = new("none")
@@ -13,7 +18,110 @@ module RubyLsp
13
18
  end
14
19
  end
15
20
 
16
- sig { override.returns(Prism::ParseResult) }
21
+ class << self
22
+ extend T::Sig
23
+
24
+ sig do
25
+ params(
26
+ node: Prism::Node,
27
+ char_position: Integer,
28
+ node_types: T::Array[T.class_of(Prism::Node)],
29
+ ).returns(NodeContext)
30
+ end
31
+ def locate(node, char_position, node_types: [])
32
+ queue = T.let(node.child_nodes.compact, T::Array[T.nilable(Prism::Node)])
33
+ closest = node
34
+ parent = T.let(nil, T.nilable(Prism::Node))
35
+ nesting_nodes = T.let(
36
+ [],
37
+ T::Array[T.any(
38
+ Prism::ClassNode,
39
+ Prism::ModuleNode,
40
+ Prism::SingletonClassNode,
41
+ Prism::DefNode,
42
+ Prism::BlockNode,
43
+ Prism::LambdaNode,
44
+ Prism::ProgramNode,
45
+ )],
46
+ )
47
+
48
+ nesting_nodes << node if node.is_a?(Prism::ProgramNode)
49
+ call_node = T.let(nil, T.nilable(Prism::CallNode))
50
+
51
+ until queue.empty?
52
+ candidate = queue.shift
53
+
54
+ # Skip nil child nodes
55
+ next if candidate.nil?
56
+
57
+ # Add the next child_nodes to the queue to be processed. The order here is important! We want to move in the
58
+ # same order as the visiting mechanism, which means searching the child nodes before moving on to the next
59
+ # sibling
60
+ T.unsafe(queue).unshift(*candidate.child_nodes)
61
+
62
+ # Skip if the current node doesn't cover the desired position
63
+ loc = candidate.location
64
+ next unless (loc.start_offset...loc.end_offset).cover?(char_position)
65
+
66
+ # If the node's start character is already past the position, then we should've found the closest node
67
+ # already
68
+ break if char_position < loc.start_offset
69
+
70
+ # If the candidate starts after the end of the previous nesting level, then we've exited that nesting level
71
+ # and need to pop the stack
72
+ previous_level = nesting_nodes.last
73
+ nesting_nodes.pop if previous_level && loc.start_offset > previous_level.location.end_offset
74
+
75
+ # Keep track of the nesting where we found the target. This is used to determine the fully qualified name of
76
+ # the target when it is a constant
77
+ case candidate
78
+ when Prism::ClassNode, Prism::ModuleNode, Prism::SingletonClassNode, Prism::DefNode, Prism::BlockNode,
79
+ Prism::LambdaNode
80
+ nesting_nodes << candidate
81
+ end
82
+
83
+ if candidate.is_a?(Prism::CallNode)
84
+ arg_loc = candidate.arguments&.location
85
+ blk_loc = candidate.block&.location
86
+ if (arg_loc && (arg_loc.start_offset...arg_loc.end_offset).cover?(char_position)) ||
87
+ (blk_loc && (blk_loc.start_offset...blk_loc.end_offset).cover?(char_position))
88
+ call_node = candidate
89
+ end
90
+ end
91
+
92
+ # If there are node types to filter by, and the current node is not one of those types, then skip it
93
+ next if node_types.any? && node_types.none? { |type| candidate.class == type }
94
+
95
+ # If the current node is narrower than or equal to the previous closest node, then it is more precise
96
+ closest_loc = closest.location
97
+ if loc.end_offset - loc.start_offset <= closest_loc.end_offset - closest_loc.start_offset
98
+ parent = closest
99
+ closest = candidate
100
+ end
101
+ end
102
+
103
+ # When targeting the constant part of a class/module definition, we do not want the nesting to be duplicated.
104
+ # That is, when targeting Bar in the following example:
105
+ #
106
+ # ```ruby
107
+ # class Foo::Bar; end
108
+ # ```
109
+ # The correct target is `Foo::Bar` with an empty nesting. `Foo::Bar` should not appear in the nesting stack,
110
+ # even though the class/module node does indeed enclose the target, because it would lead to incorrect behavior
111
+ if closest.is_a?(Prism::ConstantReadNode) || closest.is_a?(Prism::ConstantPathNode)
112
+ last_level = nesting_nodes.last
113
+
114
+ if (last_level.is_a?(Prism::ModuleNode) || last_level.is_a?(Prism::ClassNode)) &&
115
+ last_level.constant_path == closest
116
+ nesting_nodes.pop
117
+ end
118
+ end
119
+
120
+ NodeContext.new(closest, parent, nesting_nodes, call_node)
121
+ end
122
+ end
123
+
124
+ sig { override.returns(ParseResultType) }
17
125
  def parse
18
126
  return @parse_result unless @needs_parsing
19
127
 
@@ -84,5 +192,15 @@ module RubyLsp
84
192
  end
85
193
  end
86
194
  end
195
+
196
+ sig do
197
+ params(
198
+ position: T::Hash[Symbol, T.untyped],
199
+ node_types: T::Array[T.class_of(Prism::Node)],
200
+ ).returns(NodeContext)
201
+ end
202
+ def locate_node(position, node_types: [])
203
+ RubyDocument.locate(@parse_result.value, create_scanner.find_char_position(position), node_types: node_types)
204
+ end
87
205
  end
88
206
  end
@@ -298,9 +298,12 @@ module RubyLsp
298
298
  language_id = case text_document[:languageId]
299
299
  when "erb", "eruby"
300
300
  Document::LanguageId::ERB
301
+ when "rbs"
302
+ Document::LanguageId::RBS
301
303
  else
302
304
  Document::LanguageId::Ruby
303
305
  end
306
+
304
307
  @store.set(
305
308
  uri: text_document[:uri],
306
309
  source: text_document[:text],
@@ -341,7 +344,12 @@ module RubyLsp
341
344
  def text_document_selection_range(message)
342
345
  uri = message.dig(:params, :textDocument, :uri)
343
346
  ranges = @store.cache_fetch(uri, "textDocument/selectionRange") do |document|
344
- Requests::SelectionRanges.new(document).perform
347
+ case document
348
+ when RubyDocument, ERBDocument
349
+ Requests::SelectionRanges.new(document).perform
350
+ else
351
+ []
352
+ end
345
353
  end
346
354
 
347
355
  # Per the selection range request spec (https://microsoft.github.io/language-server-protocol/specification#textDocument_selectionRange),
@@ -363,6 +371,11 @@ module RubyLsp
363
371
  uri = URI(message.dig(:params, :textDocument, :uri))
364
372
  document = @store.get(uri)
365
373
 
374
+ unless document.is_a?(RubyDocument) || document.is_a?(ERBDocument)
375
+ send_empty_response(message[:id])
376
+ return
377
+ end
378
+
366
379
  # If the response has already been cached by another request, return it
367
380
  cached_response = document.cache_get(message[:method])
368
381
  if cached_response
@@ -407,6 +420,12 @@ module RubyLsp
407
420
  range = params[:range]
408
421
  uri = params.dig(:textDocument, :uri)
409
422
  document = @store.get(uri)
423
+
424
+ unless document.is_a?(RubyDocument) || document.is_a?(ERBDocument)
425
+ send_empty_response(message[:id])
426
+ return
427
+ end
428
+
410
429
  start_line = range.dig(:start, :line)
411
430
  end_line = range.dig(:end, :line)
412
431
 
@@ -436,6 +455,10 @@ module RubyLsp
436
455
  end
437
456
 
438
457
  document = @store.get(uri)
458
+ unless document.is_a?(RubyDocument)
459
+ send_empty_response(message[:id])
460
+ return
461
+ end
439
462
 
440
463
  response = Requests::Formatting.new(@global_state, document).perform
441
464
  send_message(Result.new(id: message[:id], response: response))
@@ -452,6 +475,12 @@ module RubyLsp
452
475
  params = message[:params]
453
476
  dispatcher = Prism::Dispatcher.new
454
477
  document = @store.get(params.dig(:textDocument, :uri))
478
+
479
+ unless document.is_a?(RubyDocument) || document.is_a?(ERBDocument)
480
+ send_empty_response(message[:id])
481
+ return
482
+ end
483
+
455
484
  request = Requests::DocumentHighlight.new(document, params[:position], dispatcher)
456
485
  dispatcher.dispatch(document.parse_result.value)
457
486
  send_message(Result.new(id: message[:id], response: request.perform))
@@ -462,6 +491,11 @@ module RubyLsp
462
491
  params = message[:params]
463
492
  document = @store.get(params.dig(:textDocument, :uri))
464
493
 
494
+ unless document.is_a?(RubyDocument)
495
+ send_empty_response(message[:id])
496
+ return
497
+ end
498
+
465
499
  send_message(
466
500
  Result.new(
467
501
  id: message[:id],
@@ -481,6 +515,11 @@ module RubyLsp
481
515
  dispatcher = Prism::Dispatcher.new
482
516
  document = @store.get(params.dig(:textDocument, :uri))
483
517
 
518
+ unless document.is_a?(RubyDocument) || document.is_a?(ERBDocument)
519
+ send_empty_response(message[:id])
520
+ return
521
+ end
522
+
484
523
  send_message(
485
524
  Result.new(
486
525
  id: message[:id],
@@ -495,7 +534,7 @@ module RubyLsp
495
534
  )
496
535
  end
497
536
 
498
- sig { params(document: Document).returns(RubyDocument::SorbetLevel) }
537
+ sig { params(document: Document[T.untyped]).returns(RubyDocument::SorbetLevel) }
499
538
  def sorbet_level(document)
500
539
  return RubyDocument::SorbetLevel::Ignore unless @global_state.has_type_checker
501
540
  return RubyDocument::SorbetLevel::Ignore unless document.is_a?(RubyDocument)
@@ -509,6 +548,12 @@ module RubyLsp
509
548
  hints_configurations = T.must(@store.features_configuration.dig(:inlayHint))
510
549
  dispatcher = Prism::Dispatcher.new
511
550
  document = @store.get(params.dig(:textDocument, :uri))
551
+
552
+ unless document.is_a?(RubyDocument) || document.is_a?(ERBDocument)
553
+ send_empty_response(message[:id])
554
+ return
555
+ end
556
+
512
557
  request = Requests::InlayHints.new(document, params[:range], hints_configurations, dispatcher)
513
558
  dispatcher.visit(document.parse_result.value)
514
559
  send_message(Result.new(id: message[:id], response: request.perform))
@@ -519,6 +564,11 @@ module RubyLsp
519
564
  params = message[:params]
520
565
  document = @store.get(params.dig(:textDocument, :uri))
521
566
 
567
+ unless document.is_a?(RubyDocument) || document.is_a?(ERBDocument)
568
+ send_empty_response(message[:id])
569
+ return
570
+ end
571
+
522
572
  send_message(
523
573
  Result.new(
524
574
  id: message[:id],
@@ -581,7 +631,10 @@ module RubyLsp
581
631
  document = @store.get(uri)
582
632
 
583
633
  response = document.cache_fetch("textDocument/diagnostic") do |document|
584
- Requests::Diagnostics.new(@global_state, document).perform
634
+ case document
635
+ when RubyDocument
636
+ Requests::Diagnostics.new(@global_state, document).perform
637
+ end
585
638
  end
586
639
 
587
640
  send_message(
@@ -604,6 +657,11 @@ module RubyLsp
604
657
  dispatcher = Prism::Dispatcher.new
605
658
  document = @store.get(params.dig(:textDocument, :uri))
606
659
 
660
+ unless document.is_a?(RubyDocument) || document.is_a?(ERBDocument)
661
+ send_empty_response(message[:id])
662
+ return
663
+ end
664
+
607
665
  send_message(
608
666
  Result.new(
609
667
  id: message[:id],
@@ -632,6 +690,11 @@ module RubyLsp
632
690
  dispatcher = Prism::Dispatcher.new
633
691
  document = @store.get(params.dig(:textDocument, :uri))
634
692
 
693
+ unless document.is_a?(RubyDocument) || document.is_a?(ERBDocument)
694
+ send_empty_response(message[:id])
695
+ return
696
+ end
697
+
635
698
  send_message(
636
699
  Result.new(
637
700
  id: message[:id],
@@ -653,6 +716,11 @@ module RubyLsp
653
716
  dispatcher = Prism::Dispatcher.new
654
717
  document = @store.get(params.dig(:textDocument, :uri))
655
718
 
719
+ unless document.is_a?(RubyDocument) || document.is_a?(ERBDocument)
720
+ send_empty_response(message[:id])
721
+ return
722
+ end
723
+
656
724
  send_message(
657
725
  Result.new(
658
726
  id: message[:id],
@@ -710,9 +778,16 @@ module RubyLsp
710
778
  sig { params(message: T::Hash[Symbol, T.untyped]).void }
711
779
  def text_document_show_syntax_tree(message)
712
780
  params = message[:params]
781
+ document = @store.get(params.dig(:textDocument, :uri))
782
+
783
+ unless document.is_a?(RubyDocument)
784
+ send_empty_response(message[:id])
785
+ return
786
+ end
787
+
713
788
  response = {
714
789
  ast: Requests::ShowSyntaxTree.new(
715
- @store.get(params.dig(:textDocument, :uri)),
790
+ document,
716
791
  params[:range],
717
792
  ).perform,
718
793
  }
@@ -722,11 +797,19 @@ module RubyLsp
722
797
  sig { params(message: T::Hash[Symbol, T.untyped]).void }
723
798
  def text_document_prepare_type_hierarchy(message)
724
799
  params = message[:params]
800
+ document = @store.get(params.dig(:textDocument, :uri))
801
+
802
+ unless document.is_a?(RubyDocument) || document.is_a?(ERBDocument)
803
+ send_empty_response(message[:id])
804
+ return
805
+ end
806
+
725
807
  response = Requests::PrepareTypeHierarchy.new(
726
- @store.get(params.dig(:textDocument, :uri)),
808
+ document,
727
809
  @global_state.index,
728
810
  params[:position],
729
811
  ).perform
812
+
730
813
  send_message(Result.new(id: message[:id], response: response))
731
814
  end
732
815
 
@@ -793,6 +876,11 @@ module RubyLsp
793
876
  send_message(Notification.window_show_error("Error while indexing: #{error.message}"))
794
877
  end
795
878
 
879
+ # Indexing produces a high number of short lived object allocations. That might lead to some fragmentation and
880
+ # an unnecessarily expanded heap. Compacting ensures that the heap is as small as possible and that future
881
+ # allocations and garbage collections are faster
882
+ GC.compact unless @test_mode
883
+
796
884
  # Always end the progress notification even if indexing failed or else it never goes away and the user has no
797
885
  # way of dismissing it
798
886
  end_progress("indexing-progress")
@@ -912,10 +1000,10 @@ module RubyLsp
912
1000
 
913
1001
  return unless indexing_options
914
1002
 
1003
+ configuration = @global_state.index.configuration
1004
+ configuration.workspace_path = @global_state.workspace_path
915
1005
  # The index expects snake case configurations, but VS Code standardizes on camel case settings
916
- @global_state.index.configuration.apply_config(
917
- indexing_options.transform_keys { |key| key.to_s.gsub(/([A-Z])/, "_\\1").downcase },
918
- )
1006
+ configuration.apply_config(indexing_options.transform_keys { |key| key.to_s.gsub(/([A-Z])/, "_\\1").downcase })
919
1007
  end
920
1008
  end
921
1009
  end
@@ -43,13 +43,14 @@ module RubyLsp
43
43
  @gemfile_name = T.let(@gemfile&.basename&.to_s || "Gemfile", String)
44
44
 
45
45
  # Custom bundle paths
46
- @custom_dir = T.let(Pathname.new(".ruby-lsp").expand_path(Dir.pwd), Pathname)
46
+ @custom_dir = T.let(Pathname.new(".ruby-lsp").expand_path(@project_path), Pathname)
47
47
  @custom_gemfile = T.let(@custom_dir + @gemfile_name, Pathname)
48
48
  @custom_lockfile = T.let(@custom_dir + (@lockfile&.basename || "Gemfile.lock"), Pathname)
49
49
  @lockfile_hash_path = T.let(@custom_dir + "main_lockfile_hash", Pathname)
50
50
  @last_updated_path = T.let(@custom_dir + "last_updated", Pathname)
51
51
 
52
52
  @dependencies = T.let(load_dependencies, T::Hash[String, T.untyped])
53
+ @rails_app = T.let(rails_app?, T::Boolean)
53
54
  @retry = T.let(false, T::Boolean)
54
55
  end
55
56
 
@@ -62,7 +63,7 @@ module RubyLsp
62
63
  # Do not set up a custom bundle if LSP dependencies are already in the Gemfile
63
64
  if @dependencies["ruby-lsp"] &&
64
65
  @dependencies["debug"] &&
65
- (@dependencies["rails"] ? @dependencies["ruby-lsp-rails"] : true)
66
+ (@rails_app ? @dependencies["ruby-lsp-rails"] : true)
66
67
  $stderr.puts(
67
68
  "Ruby LSP> Skipping custom bundle setup since LSP dependencies are already in #{@gemfile}",
68
69
  )
@@ -148,7 +149,7 @@ module RubyLsp
148
149
  parts << 'gem "debug", require: false, group: :development, platforms: :mri'
149
150
  end
150
151
 
151
- if @dependencies["rails"] && !@dependencies["ruby-lsp-rails"]
152
+ if @rails_app && !@dependencies["ruby-lsp-rails"]
152
153
  parts << 'gem "ruby-lsp-rails", require: false, group: :development'
153
154
  end
154
155
 
@@ -182,14 +183,14 @@ module RubyLsp
182
183
  # `.ruby-lsp` folder, which is not the user's intention. For example, if the path is configured as `vendor`, we
183
184
  # want to install it in the top level `vendor` and not `.ruby-lsp/vendor`
184
185
  path = Bundler.settings["path"]
185
- expanded_path = File.expand_path(path, Dir.pwd) if path
186
+ expanded_path = File.expand_path(path, @project_path) if path
186
187
 
187
188
  # Use the absolute `BUNDLE_PATH` to prevent accidentally creating unwanted folders under `.ruby-lsp`
188
189
  env = {}
189
190
  env["BUNDLE_GEMFILE"] = bundle_gemfile.to_s
190
191
  env["BUNDLE_PATH"] = expanded_path if expanded_path
191
192
 
192
- local_config_path = File.join(Dir.pwd, ".bundle")
193
+ local_config_path = File.join(@project_path, ".bundle")
193
194
  env["BUNDLE_APP_CONFIG"] = local_config_path if Dir.exist?(local_config_path)
194
195
 
195
196
  # If `ruby-lsp` and `debug` (and potentially `ruby-lsp-rails`) are already in the Gemfile, then we shouldn't try
@@ -209,7 +210,7 @@ module RubyLsp
209
210
  command << " && bundle update "
210
211
  command << "ruby-lsp " unless @dependencies["ruby-lsp"]
211
212
  command << "debug " unless @dependencies["debug"]
212
- command << "ruby-lsp-rails " if @dependencies["rails"] && !@dependencies["ruby-lsp-rails"]
213
+ command << "ruby-lsp-rails " if @rails_app && !@dependencies["ruby-lsp-rails"]
213
214
  command << "--pre" if @experimental
214
215
  command.delete_suffix!(" ")
215
216
  command << ")"
@@ -244,7 +245,7 @@ module RubyLsp
244
245
  def should_bundle_update?
245
246
  # If `ruby-lsp`, `ruby-lsp-rails` and `debug` are in the Gemfile, then we shouldn't try to upgrade them or else it
246
247
  # will produce version control changes
247
- if @dependencies["rails"]
248
+ if @rails_app
248
249
  return false if @dependencies.values_at("ruby-lsp", "ruby-lsp-rails", "debug").all?
249
250
 
250
251
  # If the custom lockfile doesn't include `ruby-lsp`, `ruby-lsp-rails` or `debug`, we need to run bundle install
@@ -280,5 +281,15 @@ module RubyLsp
280
281
 
281
282
  @custom_lockfile.write(content)
282
283
  end
284
+
285
+ # Detects if the project is a Rails app by looking if the superclass of the main class is `Rails::Application`
286
+ sig { returns(T::Boolean) }
287
+ def rails_app?
288
+ config = Pathname.new("config/application.rb").expand_path
289
+ application_contents = config.read(external_encoding: Encoding::UTF_8) if config.exist?
290
+ return false unless application_contents
291
+
292
+ /class .* < (::)?Rails::Application/.match?(application_contents)
293
+ end
283
294
  end
284
295
  end
@@ -18,7 +18,7 @@ module RubyLsp
18
18
 
19
19
  sig { void }
20
20
  def initialize
21
- @state = T.let({}, T::Hash[String, Document])
21
+ @state = T.let({}, T::Hash[String, Document[T.untyped]])
22
22
  @supports_progress = T.let(true, T::Boolean)
23
23
  @features_configuration = T.let(
24
24
  {
@@ -33,7 +33,7 @@ module RubyLsp
33
33
  @client_name = T.let("Unknown", String)
34
34
  end
35
35
 
36
- sig { params(uri: URI::Generic).returns(Document) }
36
+ sig { params(uri: URI::Generic).returns(Document[T.untyped]) }
37
37
  def get(uri)
38
38
  document = @state[uri.to_s]
39
39
  return document unless document.nil?
@@ -44,8 +44,11 @@ module RubyLsp
44
44
  raise NonExistingDocumentError, uri.to_s unless path
45
45
 
46
46
  ext = File.extname(path)
47
- language_id = if ext == ".erb" || ext == ".rhtml"
47
+ language_id = case ext
48
+ when ".erb", ".rhtml"
48
49
  Document::LanguageId::ERB
50
+ when ".rbs"
51
+ Document::LanguageId::RBS
49
52
  else
50
53
  Document::LanguageId::Ruby
51
54
  end
@@ -66,13 +69,14 @@ module RubyLsp
66
69
  ).void
67
70
  end
68
71
  def set(uri:, source:, version:, language_id:, encoding: Encoding::UTF_8)
69
- document = case language_id
72
+ @state[uri.to_s] = case language_id
70
73
  when Document::LanguageId::ERB
71
74
  ERBDocument.new(source: source, version: version, uri: uri, encoding: encoding)
75
+ when Document::LanguageId::RBS
76
+ RBSDocument.new(source: source, version: version, uri: uri, encoding: encoding)
72
77
  else
73
78
  RubyDocument.new(source: source, version: version, uri: uri, encoding: encoding)
74
79
  end
75
- @state[uri.to_s] = document
76
80
  end
77
81
 
78
82
  sig { params(uri: URI::Generic, edits: T::Array[T::Hash[Symbol, T.untyped]], version: Integer).void }
@@ -100,7 +104,7 @@ module RubyLsp
100
104
  .params(
101
105
  uri: URI::Generic,
102
106
  request_name: String,
103
- block: T.proc.params(document: Document).returns(T.type_parameter(:T)),
107
+ block: T.proc.params(document: Document[T.untyped]).returns(T.type_parameter(:T)),
104
108
  ).returns(T.type_parameter(:T))
105
109
  end
106
110
  def cache_fetch(uri, request_name, &block)