ruby-lsp 0.17.8 → 0.17.10

Sign up to get free protection for your applications and to get access to all the features.
@@ -36,10 +36,10 @@ module RubyLsp
36
36
  global_state: GlobalState,
37
37
  position: T::Hash[Symbol, T.untyped],
38
38
  dispatcher: Prism::Dispatcher,
39
- typechecker_enabled: T::Boolean,
39
+ sorbet_level: Document::SorbetLevel,
40
40
  ).void
41
41
  end
42
- def initialize(document, global_state, position, dispatcher, typechecker_enabled)
42
+ def initialize(document, global_state, position, dispatcher, sorbet_level)
43
43
  super()
44
44
  node_context = document.locate_node(position, node_types: Listeners::Hover::ALLOWED_TARGETS)
45
45
  target = node_context.node
@@ -65,7 +65,7 @@ module RubyLsp
65
65
  @target = T.let(target, T.nilable(Prism::Node))
66
66
  uri = document.uri
67
67
  @response_builder = T.let(ResponseBuilders::Hover.new, ResponseBuilders::Hover)
68
- Listeners::Hover.new(@response_builder, global_state, uri, node_context, dispatcher, typechecker_enabled)
68
+ Listeners::Hover.new(@response_builder, global_state, uri, node_context, dispatcher, sorbet_level)
69
69
  Addon.addons.each do |addon|
70
70
  addon.create_hover_listener(@response_builder, node_context, dispatcher)
71
71
  end
@@ -32,8 +32,9 @@ module RubyLsp
32
32
 
33
33
  END_REGEXES = T.let(
34
34
  [
35
- /\b(if|unless|for|while|class|module|until|def|case)\b.*/,
36
- /.*\s\bdo\b/,
35
+ /\b(if|unless|for|while|until)\b($|\s|\()/,
36
+ /\b(class|module|def|case)\b($|\s)/,
37
+ /.*\s\bdo\b($|\s)/,
37
38
  ],
38
39
  T::Array[Regexp],
39
40
  )
@@ -46,10 +46,10 @@ module RubyLsp
46
46
  position: T::Hash[Symbol, T.untyped],
47
47
  context: T.nilable(T::Hash[Symbol, T.untyped]),
48
48
  dispatcher: Prism::Dispatcher,
49
- typechecker_enabled: T::Boolean,
49
+ sorbet_level: Document::SorbetLevel,
50
50
  ).void
51
51
  end
52
- def initialize(document, global_state, position, context, dispatcher, typechecker_enabled) # rubocop:disable Metrics/ParameterLists
52
+ def initialize(document, global_state, position, context, dispatcher, sorbet_level) # rubocop:disable Metrics/ParameterLists
53
53
  super()
54
54
  node_context = document.locate_node(
55
55
  { line: position[:line], character: position[:character] },
@@ -61,7 +61,7 @@ module RubyLsp
61
61
  @target = T.let(target, T.nilable(Prism::Node))
62
62
  @dispatcher = dispatcher
63
63
  @response_builder = T.let(ResponseBuilders::SignatureHelp.new, ResponseBuilders::SignatureHelp)
64
- Listeners::SignatureHelp.new(@response_builder, global_state, node_context, dispatcher, typechecker_enabled)
64
+ Listeners::SignatureHelp.new(@response_builder, global_state, node_context, dispatcher, sorbet_level)
65
65
  end
66
66
 
67
67
  sig { override.returns(T.nilable(Interface::SignatureHelp)) }
@@ -130,13 +130,17 @@ module RubyLsp
130
130
  title: String,
131
131
  entries: T.any(T::Array[RubyIndexer::Entry], RubyIndexer::Entry),
132
132
  max_entries: T.nilable(Integer),
133
+ extra_links: T.nilable(String),
133
134
  ).returns(String)
134
135
  end
135
- def markdown_from_index_entries(title, entries, max_entries = nil)
136
+ def markdown_from_index_entries(title, entries, max_entries = nil, extra_links: nil)
136
137
  categorized_markdown = categorized_markdown_from_index_entries(title, entries, max_entries)
137
138
 
139
+ markdown = +(categorized_markdown[:title] || "")
140
+ markdown << "\n\n#{extra_links}" if extra_links
141
+
138
142
  <<~MARKDOWN.chomp
139
- #{categorized_markdown[:title]}
143
+ #{markdown}
140
144
 
141
145
  #{categorized_markdown[:links]}
142
146
 
@@ -204,6 +208,11 @@ module RubyLsp
204
208
  Constant::SymbolKind::FIELD
205
209
  end
206
210
  end
211
+
212
+ sig { params(sorbet_level: Document::SorbetLevel).returns(T::Boolean) }
213
+ def sorbet_level_true_or_higher?(sorbet_level)
214
+ sorbet_level == Document::SorbetLevel::True || sorbet_level == Document::SorbetLevel::Strict
215
+ end
207
216
  end
208
217
  end
209
218
  end
@@ -477,15 +477,17 @@ module RubyLsp
477
477
  @global_state,
478
478
  params[:position],
479
479
  dispatcher,
480
- typechecker_enabled?(document),
480
+ sorbet_level(document),
481
481
  ).perform,
482
482
  ),
483
483
  )
484
484
  end
485
485
 
486
- sig { params(document: Document).returns(T::Boolean) }
487
- def typechecker_enabled?(document)
488
- @global_state.has_type_checker && document.sorbet_sigil_is_true_or_higher
486
+ sig { params(document: Document).returns(Document::SorbetLevel) }
487
+ def sorbet_level(document)
488
+ return Document::SorbetLevel::Ignore unless @global_state.has_type_checker
489
+
490
+ document.sorbet_level
489
491
  end
490
492
 
491
493
  sig { params(message: T::Hash[Symbol, T.untyped]).void }
@@ -594,7 +596,7 @@ module RubyLsp
594
596
  document,
595
597
  @global_state,
596
598
  params,
597
- typechecker_enabled?(document),
599
+ sorbet_level(document),
598
600
  dispatcher,
599
601
  ).perform,
600
602
  ),
@@ -624,7 +626,7 @@ module RubyLsp
624
626
  params[:position],
625
627
  params[:context],
626
628
  dispatcher,
627
- typechecker_enabled?(document),
629
+ sorbet_level(document),
628
630
  ).perform,
629
631
  ),
630
632
  )
@@ -644,7 +646,7 @@ module RubyLsp
644
646
  @global_state,
645
647
  params[:position],
646
648
  dispatcher,
647
- typechecker_enabled?(document),
649
+ sorbet_level(document),
648
650
  ).perform,
649
651
  ),
650
652
  )
@@ -763,8 +765,6 @@ module RubyLsp
763
765
  # stuck indexing files
764
766
  Thread.new do
765
767
  begin
766
- RubyIndexer::RBSIndexer.new(@global_state.index).index_ruby_core
767
-
768
768
  @global_state.index.index_all do |percentage|
769
769
  progress("indexing-progress", percentage)
770
770
  true
@@ -38,13 +38,18 @@ module RubyLsp
38
38
  document = @state[uri.to_s]
39
39
  return document unless document.nil?
40
40
 
41
- path = T.must(uri.to_standardized_path)
41
+ # For unsaved files (`untitled:Untitled-1` uris), there's no path to read from. If we don't have the untitled file
42
+ # already present in the store, then we have to raise non existing document error
43
+ path = uri.to_standardized_path
44
+ raise NonExistingDocumentError, uri.to_s unless path
45
+
42
46
  ext = File.extname(path)
43
47
  language_id = if ext == ".erb" || ext == ".rhtml"
44
48
  Document::LanguageId::ERB
45
49
  else
46
50
  Document::LanguageId::Ruby
47
51
  end
52
+
48
53
  set(uri: uri, source: File.binread(path), version: 0, language_id: language_id)
49
54
  T.must(@state[uri.to_s])
50
55
  rescue Errno::ENOENT
@@ -21,7 +21,7 @@ module RubyLsp
21
21
  &block)
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
- server.global_state.apply_options({})
24
+ server.global_state.apply_options({ initializationOptions: { experimentalFeaturesEnabled: true } })
25
25
  language_id = uri.to_s.end_with?(".erb") ? "erb" : "ruby"
26
26
 
27
27
  if source
@@ -7,12 +7,16 @@ module RubyLsp
7
7
  class TypeInferrer
8
8
  extend T::Sig
9
9
 
10
- sig { params(index: RubyIndexer::Index).void }
11
- def initialize(index)
10
+ sig { params(experimental_features: T::Boolean).returns(T::Boolean) }
11
+ attr_writer :experimental_features
12
+
13
+ sig { params(index: RubyIndexer::Index, experimental_features: T::Boolean).void }
14
+ def initialize(index, experimental_features = true)
12
15
  @index = index
16
+ @experimental_features = experimental_features
13
17
  end
14
18
 
15
- sig { params(node_context: NodeContext).returns(T.nilable(String)) }
19
+ sig { params(node_context: NodeContext).returns(T.nilable(Type)) }
16
20
  def infer_receiver_type(node_context)
17
21
  node = node_context.node
18
22
 
@@ -28,7 +32,7 @@ module RubyLsp
28
32
 
29
33
  private
30
34
 
31
- sig { params(node: Prism::CallNode, node_context: NodeContext).returns(T.nilable(String)) }
35
+ sig { params(node: Prism::CallNode, node_context: NodeContext).returns(T.nilable(Type)) }
32
36
  def infer_receiver_for_call_node(node, node_context)
33
37
  receiver = node.receiver
34
38
 
@@ -47,23 +51,40 @@ module RubyLsp
47
51
  return unless name
48
52
 
49
53
  *parts, last = name.split("::")
50
- return "#{last}::<Class:#{last}>" if parts.empty?
54
+ return Type.new("#{last}::<Class:#{last}>") if parts.empty?
55
+
56
+ Type.new("#{parts.join("::")}::#{last}::<Class:#{last}>")
57
+ else
58
+ return unless @experimental_features
59
+
60
+ raw_receiver = node.receiver&.slice
51
61
 
52
- "#{parts.join("::")}::#{last}::<Class:#{last}>"
62
+ if raw_receiver
63
+ guessed_name = raw_receiver
64
+ .delete_prefix("@")
65
+ .delete_prefix("@@")
66
+ .split("_")
67
+ .map(&:capitalize)
68
+ .join
69
+
70
+ entries = @index.resolve(guessed_name, node_context.nesting) || @index.first_unqualified_const(guessed_name)
71
+ name = entries&.first&.name
72
+ GuessedType.new(name) if name
73
+ end
53
74
  end
54
75
  end
55
76
 
56
- sig { params(node_context: NodeContext).returns(String) }
77
+ sig { params(node_context: NodeContext).returns(Type) }
57
78
  def self_receiver_handling(node_context)
58
79
  nesting = node_context.nesting
59
80
  # If we're at the top level, then the invocation is happening on `<main>`, which is a special singleton that
60
81
  # inherits from Object
61
- return "Object" if nesting.empty?
62
- return node_context.fully_qualified_name if node_context.surrounding_method
82
+ return Type.new("Object") if nesting.empty?
83
+ return Type.new(node_context.fully_qualified_name) if node_context.surrounding_method
63
84
 
64
85
  # If we're not inside a method, then we're inside the body of a class or module, which is a singleton
65
86
  # context
66
- "#{nesting.join("::")}::<Class:#{nesting.last}>"
87
+ Type.new("#{nesting.join("::")}::<Class:#{nesting.last}>")
67
88
  end
68
89
 
69
90
  sig do
@@ -80,5 +101,22 @@ module RubyLsp
80
101
  Prism::ConstantPathNode::MissingNodesInConstantPathError
81
102
  nil
82
103
  end
104
+
105
+ # A known type
106
+ class Type
107
+ extend T::Sig
108
+
109
+ sig { returns(String) }
110
+ attr_reader :name
111
+
112
+ sig { params(name: String).void }
113
+ def initialize(name)
114
+ @name = name
115
+ end
116
+ end
117
+
118
+ # A type that was guessed based on the receiver raw name
119
+ class GuessedType < Type
120
+ end
83
121
  end
84
122
  end
@@ -25,6 +25,7 @@ module RubyLsp
25
25
  end,
26
26
  String,
27
27
  )
28
+ GUESSED_TYPES_URL = "https://github.com/Shopify/ruby-lsp/blob/main/DESIGN_AND_ROADMAP.md#guessed-types"
28
29
 
29
30
  # A notification to be sent to the client
30
31
  class Message
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.8
4
+ version: 0.17.10
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-18 00:00:00.000000000 Z
11
+ date: 2024-07-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: language_server-protocol
@@ -204,7 +204,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
204
204
  - !ruby/object:Gem::Version
205
205
  version: '0'
206
206
  requirements: []
207
- rubygems_version: 3.5.15
207
+ rubygems_version: 3.5.16
208
208
  signing_key:
209
209
  specification_version: 4
210
210
  summary: An opinionated language server for Ruby