ruby-lsp 0.27.0.beta1 → 0.27.0.beta2

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.
@@ -5,6 +5,8 @@ module RubyLsp
5
5
  module Requests
6
6
  # @abstract
7
7
  class Request
8
+ include Support::Common
9
+
8
10
  class InvalidFormatter < StandardError; end
9
11
 
10
12
  # @abstract
@@ -26,24 +28,6 @@ module RubyLsp
26
28
  end
27
29
  end
28
30
 
29
- # Checks if a location covers a position
30
- #: (Prism::Location location, untyped position) -> bool
31
- def cover?(location, position)
32
- start_covered =
33
- location.start_line - 1 < position[:line] ||
34
- (
35
- location.start_line - 1 == position[:line] &&
36
- location.start_column <= position[:character]
37
- )
38
- end_covered =
39
- location.end_line - 1 > position[:line] ||
40
- (
41
- location.end_line - 1 == position[:line] &&
42
- location.end_column >= position[:character]
43
- )
44
- start_covered && end_covered
45
- end
46
-
47
31
  # Based on a constant node target, a constant path node parent and a position, this method will find the exact
48
32
  # portion of the constant path that matches the requested position, for higher precision in hover and
49
33
  # definition. For example:
@@ -62,27 +46,13 @@ module RubyLsp
62
46
  parent = target #: as Prism::ConstantPathNode
63
47
  .parent #: Prism::Node?
64
48
 
65
- while parent && cover?(parent.location, position)
49
+ while parent && covers_position?(parent.location, position)
66
50
  target = parent
67
51
  parent = target.is_a?(Prism::ConstantPathNode) ? target.parent : nil
68
52
  end
69
53
 
70
54
  target
71
55
  end
72
-
73
- # Checks if a given location covers the position requested
74
- #: (Prism::Location? location, Hash[Symbol, untyped] position) -> bool
75
- def covers_position?(location, position)
76
- return false unless location
77
-
78
- start_line = location.start_line - 1
79
- end_line = location.end_line - 1
80
- line = position[:line]
81
- character = position[:character]
82
-
83
- (start_line < line || (start_line == line && location.start_column <= character)) &&
84
- (end_line > line || (end_line == line && location.end_column >= character))
85
- end
86
56
  end
87
57
  end
88
58
  end
@@ -34,6 +34,19 @@ module RubyLsp
34
34
  )
35
35
  end
36
36
 
37
+ #: (Prism::Location? location, Hash[Symbol, untyped] position) -> bool
38
+ def covers_position?(location, position)
39
+ return false unless location
40
+
41
+ start_line = location.start_line - 1
42
+ end_line = location.end_line - 1
43
+ line = position[:line]
44
+ character = position[:character]
45
+
46
+ (start_line < line || (start_line == line && location.start_column <= character)) &&
47
+ (end_line > line || (end_line == line && location.end_column >= character))
48
+ end
49
+
37
50
  #: (Prism::Node node, title: String, command_name: String, arguments: Array[untyped]?, data: Hash[untyped, untyped]?) -> Interface::CodeLens
38
51
  def create_code_lens(node, title:, command_name:, arguments:, data:)
39
52
  range = range_from_node(node)
@@ -9,65 +9,101 @@ module RubyLsp
9
9
  class TypeHierarchySupertypes < Request
10
10
  include Support::Common
11
11
 
12
- #: (RubyIndexer::Index index, Hash[Symbol, untyped] item) -> void
13
- def initialize(index, item)
12
+ #: (GlobalState, Hash[Symbol, untyped]) -> void
13
+ def initialize(global_state, item)
14
14
  super()
15
15
 
16
- @index = index
16
+ @graph = global_state.graph #: Rubydex::Graph
17
17
  @item = item
18
18
  end
19
19
 
20
20
  # @override
21
21
  #: -> Array[Interface::TypeHierarchyItem]?
22
22
  def perform
23
- name = @item[:name]
24
- entries = @index[name]
25
-
26
- parents = Set.new #: Set[RubyIndexer::Entry::Namespace]
27
- return unless entries&.any?
28
-
29
- entries.each do |entry|
30
- next unless entry.is_a?(RubyIndexer::Entry::Namespace)
31
-
32
- if entry.is_a?(RubyIndexer::Entry::Class)
33
- parent_class_name = entry.parent_class
34
- if parent_class_name
35
- resolved_parent_entries = @index.resolve(parent_class_name, entry.nesting)
36
- resolved_parent_entries&.each do |entry|
37
- next unless entry.is_a?(RubyIndexer::Entry::Class)
38
-
39
- parents << entry
40
- end
41
- end
42
- end
43
-
44
- entry.mixin_operations.each do |mixin_operation|
45
- mixin_name = mixin_operation.module_name
46
- resolved_mixin_entries = @index.resolve(mixin_name, entry.nesting)
47
- next unless resolved_mixin_entries
48
-
49
- resolved_mixin_entries.each do |mixin_entry|
50
- next unless mixin_entry.is_a?(RubyIndexer::Entry::Module)
51
-
52
- parents << mixin_entry
53
- end
54
- end
55
- end
23
+ fully_qualified_name = @item.dig(:data, :fully_qualified_name) || @item[:name] #: String?
24
+ return unless fully_qualified_name
25
+
26
+ declaration = @graph[fully_qualified_name]
27
+ return unless declaration.is_a?(Rubydex::Namespace)
56
28
 
57
- parents.map { |entry| hierarchy_item(entry) }
29
+ compute_supertypes(declaration).filter_map { |name, backing| hierarchy_item(name, backing) }
58
30
  end
59
31
 
60
32
  private
61
33
 
62
- #: (RubyIndexer::Entry entry) -> Interface::TypeHierarchyItem
63
- def hierarchy_item(entry)
64
- Interface::TypeHierarchyItem.new(
65
- name: entry.name,
66
- kind: kind_for_entry(entry),
67
- uri: entry.uri.to_s,
68
- range: range_from_location(entry.location),
69
- selection_range: range_from_location(entry.name_location),
70
- detail: entry.file_name,
34
+ # Returns an array of `[display_name, backing_declaration]` pairs. `display_name` is the name shown in the type
35
+ # hierarchy item (which may be a synthesized singleton class name like `Object::<Object>`). `backing_declaration`
36
+ # is the namespace whose primary definition provides the location for the hierarchy item — it may differ from the
37
+ # display name when the singleton class is implicit and has no definitions of its own, in which case we fall back
38
+ # to the attached object's definition so the user still lands somewhere useful.
39
+ #
40
+ #: (Rubydex::Namespace) -> Array[[String, Rubydex::Namespace]]
41
+ def compute_supertypes(declaration)
42
+ case declaration
43
+ when Rubydex::SingletonClass
44
+ singleton_supertypes(declaration)
45
+ when Rubydex::Class
46
+ class_supertypes(declaration)
47
+ else
48
+ explicit_supertypes(declaration)
49
+ end
50
+ end
51
+
52
+ #: (Rubydex::Class) -> Array[[String, Rubydex::Namespace]]
53
+ def class_supertypes(declaration)
54
+ # `BasicObject` is the root of the Ruby class hierarchy
55
+ supertypes = explicit_supertypes(declaration)
56
+ return supertypes if declaration.name == "BasicObject"
57
+
58
+ # If the class has any superclass reference (resolved or unresolved), don't re-add the implicit `Object`.
59
+ has_superclass = declaration.definitions.any? do |d|
60
+ d.is_a?(Rubydex::ClassDefinition) && !d.superclass.nil?
61
+ end
62
+ return supertypes if has_superclass
63
+
64
+ object = @graph["Object"] #: as Rubydex::Namespace
65
+ supertypes << ["Object", object]
66
+ supertypes
67
+ end
68
+
69
+ #: (Rubydex::Namespace) -> Array[[String, Rubydex::Namespace]]
70
+ def explicit_supertypes(declaration)
71
+ declaration.direct_supertypes.map { |s| [s.name, s] }
72
+ end
73
+
74
+ # Singleton classes don't have their own superclass references. Their direct supertype is the singleton class of
75
+ # the attached object's superclass, computed recursively so that nested singleton classes (e.g.
76
+ # `Foo::<Foo>::<<Foo>>`) still resolve to the matching depth on the parent chain. When the synthesized singleton
77
+ # class name has no backing declaration with definitions (implicit singleton), we fall back to the attached
78
+ # supertype's backing so the user is still navigated to a meaningful location.
79
+ #
80
+ #: (Rubydex::SingletonClass) -> Array[[String, Rubydex::Namespace]]
81
+ def singleton_supertypes(declaration)
82
+ attached = declaration.owner
83
+ return [] unless attached.is_a?(Rubydex::Namespace)
84
+
85
+ compute_supertypes(attached).map do |parent_name, parent_backing|
86
+ singleton_name = singleton_name_of(parent_name)
87
+ found = @graph[singleton_name]
88
+ backing = found.is_a?(Rubydex::Namespace) && found.definitions.any? ? found : parent_backing
89
+ [singleton_name, backing]
90
+ end
91
+ end
92
+
93
+ #: (String) -> String
94
+ def singleton_name_of(name)
95
+ unqualified = name.split("::").last || name
96
+ "#{name}::<#{unqualified}>"
97
+ end
98
+
99
+ #: (String, Rubydex::Namespace) -> Interface::TypeHierarchyItem?
100
+ def hierarchy_item(name, declaration)
101
+ primary = declaration.definitions.first #: Rubydex::Definition?
102
+ return unless primary
103
+
104
+ primary.to_lsp_type_hierarchy_item(
105
+ name,
106
+ detail: declaration.lsp_type_hierarchy_detail,
71
107
  )
72
108
  end
73
109
  end
@@ -0,0 +1,48 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Rubydex
5
+ class Declaration
6
+ # Detail text shown on a `TypeHierarchyItem` for this declaration. Hints at multiplicity
7
+ # when the declaration spans more than one re-open; otherwise falls back to the primary
8
+ # definition's file name so users can quickly see where the type comes from.
9
+ #
10
+ #: () -> String?
11
+ def lsp_type_hierarchy_detail
12
+ defs = definitions
13
+ count = defs.count
14
+ return "#{count} definitions" if count > 1
15
+
16
+ primary = defs.first
17
+ return unless primary
18
+
19
+ uri = URI(primary.location.uri)
20
+ path = uri.full_path
21
+ path ? File.basename(path) : uri.to_s
22
+ end
23
+ end
24
+
25
+ class Namespace
26
+ # Resolved, deduplicated direct supertypes across every re-open of this declaration.
27
+ # Aggregates each definition's own `superclass`/`include`/`prepend` references and drops
28
+ # unresolved ones. Order is stable (first-seen across definitions).
29
+ #: () -> Array[Rubydex::Namespace]
30
+ def direct_supertypes
31
+ seen = {} #: Hash[String, Rubydex::Namespace]
32
+
33
+ definitions.each do |definition|
34
+ definition.direct_supertype_references.each do |ref|
35
+ next unless ref.is_a?(ResolvedConstantReference)
36
+
37
+ target = ref.declaration
38
+ next unless target.is_a?(Namespace)
39
+ next if seen.key?(target.name)
40
+
41
+ seen[target.name] = target
42
+ end
43
+ end
44
+
45
+ seen.values
46
+ end
47
+ end
48
+ end
@@ -21,6 +21,29 @@ module Rubydex
21
21
  raise RubyLsp::AbstractMethodInvokedError
22
22
  end
23
23
 
24
+ # Direct ancestor references contributed by this definition (superclass, includes, prepends).
25
+ # Extends are intentionally excluded here because they extend the singleton class, not the
26
+ # instance-side ancestor chain. Definition subclasses that can't contribute ancestors return [].
27
+ #: () -> Array[Rubydex::ConstantReference]
28
+ def direct_supertype_references
29
+ []
30
+ end
31
+
32
+ #: (String name, ?detail: String?) -> RubyLsp::Interface::TypeHierarchyItem
33
+ def to_lsp_type_hierarchy_item(name, detail: nil)
34
+ range = to_lsp_selection_range
35
+
36
+ RubyLsp::Interface::TypeHierarchyItem.new(
37
+ name: name,
38
+ kind: to_lsp_kind,
39
+ uri: location.uri,
40
+ range: range,
41
+ selection_range: to_lsp_name_range || range,
42
+ detail: detail,
43
+ data: { fully_qualified_name: name },
44
+ )
45
+ end
46
+
24
47
  #: (String name) -> RubyLsp::Interface::WorkspaceSymbol
25
48
  def to_lsp_workspace_symbol(name)
26
49
  # We use the namespace as the container name, but we also use the full name as the regular name. The reason we do
@@ -86,15 +109,47 @@ module Rubydex
86
109
  end
87
110
  end
88
111
 
112
+ # Shared supertype aggregation for Rubydex definition types that carry namespace mixins
113
+ # (`ClassDefinition`, `ModuleDefinition`, `SingletonClassDefinition`). The including class is
114
+ # expected to provide `#mixins`, which every Rubydex namespace definition already does.
115
+ # @abstract
116
+ module NamespaceDefinition
117
+ # @abstract
118
+ #: () -> Array[Rubydex::Mixin]
119
+ def mixins
120
+ raise RubyLsp::AbstractMethodInvokedError
121
+ end
122
+
123
+ #: () -> Array[Rubydex::ConstantReference]
124
+ def direct_supertype_references
125
+ mixins.filter_map do |mixin|
126
+ mixin.constant_reference if mixin.is_a?(Include) || mixin.is_a?(Prepend)
127
+ end
128
+ end
129
+ end
130
+
89
131
  class ClassDefinition
132
+ include NamespaceDefinition
133
+
90
134
  # @override
91
135
  #: () -> Integer
92
136
  def to_lsp_kind
93
137
  RubyLsp::Constant::SymbolKind::CLASS
94
138
  end
139
+
140
+ # @override
141
+ #: () -> Array[Rubydex::ConstantReference]
142
+ def direct_supertype_references
143
+ refs = super
144
+ superclass_ref = superclass
145
+ refs << superclass_ref if superclass_ref
146
+ refs
147
+ end
95
148
  end
96
149
 
97
150
  class ModuleDefinition
151
+ include NamespaceDefinition
152
+
98
153
  # @override
99
154
  #: () -> Integer
100
155
  def to_lsp_kind
@@ -103,6 +158,8 @@ module Rubydex
103
158
  end
104
159
 
105
160
  class SingletonClassDefinition
161
+ include NamespaceDefinition
162
+
106
163
  # @override
107
164
  #: () -> Integer
108
165
  def to_lsp_kind
@@ -2,7 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Rubydex
5
- class ConstantReference
5
+ class Reference
6
6
  #: () -> RubyLsp::Interface::Range
7
7
  def to_lsp_range
8
8
  loc = location
@@ -12,5 +12,10 @@ module Rubydex
12
12
  end: RubyLsp::Interface::Position.new(line: loc.end_line, character: loc.end_column),
13
13
  )
14
14
  end
15
+
16
+ #: () -> RubyLsp::Interface::Location
17
+ def to_lsp_location
18
+ RubyLsp::Interface::Location.new(uri: location.uri, range: to_lsp_range)
19
+ end
15
20
  end
16
21
  end
@@ -3,7 +3,11 @@
3
3
 
4
4
  module RubyLsp
5
5
  class Server < BaseServer
6
- NON_REPORTABLE_SETUP_ERRORS = [Bundler::GemNotFound, Bundler::GitError].freeze #: Array[singleton(StandardError)]
6
+ NON_REPORTABLE_SETUP_ERRORS = [
7
+ Bundler::GemNotFound,
8
+ Bundler::GitError,
9
+ Bundler::Dsl::DSLError,
10
+ ].freeze #: Array[singleton(StandardError)]
7
11
 
8
12
  # Only for testing
9
13
  #: GlobalState
@@ -120,6 +124,16 @@ module RubyLsp
120
124
  @global_state.synchronize { @cancelled_requests << message[:params][:id] }
121
125
  when nil
122
126
  process_response(message) if message[:result]
127
+ else
128
+ id = message[:id]
129
+
130
+ if id
131
+ send_message(Error.new(
132
+ id: id,
133
+ code: Constant::ErrorCodes::METHOD_NOT_FOUND,
134
+ message: "Method not found: #{message[:method]}",
135
+ ))
136
+ end
123
137
  end
124
138
  rescue DelegateRequestError
125
139
  send_message(Error.new(id: message[:id], code: DelegateRequestError::CODE, message: "DELEGATE_REQUEST"))
@@ -422,8 +436,11 @@ module RubyLsp
422
436
 
423
437
  if [:ruby, :rbs].include?(language_id)
424
438
  graph = @global_state.graph
425
- graph.index_source(text_document[:uri].to_s, document.source, language_id.to_s)
426
- graph.resolve
439
+
440
+ benchmark("index_source") do
441
+ graph.index_source(text_document[:uri].to_s, document.source, language_id.to_s)
442
+ end
443
+ benchmark("incremental_resolve") { graph.resolve }
427
444
  end
428
445
  end
429
446
 
@@ -1056,8 +1073,8 @@ module RubyLsp
1056
1073
  acc << path
1057
1074
  end
1058
1075
  end
1059
- graph.index_all(additions_and_changes)
1060
- graph.resolve
1076
+ benchmark("index_all") { graph.index_all(additions_and_changes) }
1077
+ benchmark("incremental_resolve") { graph.resolve }
1061
1078
 
1062
1079
  index = @global_state.index
1063
1080
  changes.each do |change|
@@ -1195,7 +1212,7 @@ module RubyLsp
1195
1212
 
1196
1213
  response = Requests::PrepareTypeHierarchy.new(
1197
1214
  document,
1198
- @global_state.index,
1215
+ @global_state,
1199
1216
  params[:position],
1200
1217
  ).perform
1201
1218
 
@@ -1205,7 +1222,7 @@ module RubyLsp
1205
1222
  #: (Hash[Symbol, untyped] message) -> void
1206
1223
  def type_hierarchy_supertypes(message)
1207
1224
  response = Requests::TypeHierarchySupertypes.new(
1208
- @global_state.index,
1225
+ @global_state,
1209
1226
  message.dig(:params, :item),
1210
1227
  ).perform
1211
1228
  send_message(Result.new(id: message[:id], response: response))
@@ -1255,10 +1272,10 @@ module RubyLsp
1255
1272
  #: -> void
1256
1273
  def perform_initial_indexing
1257
1274
  progress("indexing-progress", message: "Indexing workspace...")
1258
- @global_state.graph.index_workspace
1275
+ benchmark("index_workspace") { @global_state.graph.index_workspace }
1259
1276
 
1260
1277
  progress("indexing-progress", message: "Resolving graph...")
1261
- @global_state.graph.resolve
1278
+ benchmark("full_resolve") { @global_state.graph.resolve }
1262
1279
 
1263
1280
  # The begin progress invocation happens during `initialize`, so that the notification is sent before we are
1264
1281
  # stuck indexing files
@@ -1558,5 +1575,28 @@ module RubyLsp
1558
1575
  response: code_lens,
1559
1576
  ))
1560
1577
  end
1578
+
1579
+ #: [T] (String) { () -> T } -> T
1580
+ def benchmark(label, &block)
1581
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
1582
+ result = block.call
1583
+ duration = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond) - start
1584
+
1585
+ send_message(Notification.telemetry({
1586
+ type: "data",
1587
+ eventName: "ruby_lsp.response_time",
1588
+ data: {
1589
+ type: "histogram",
1590
+ value: duration,
1591
+ attributes: {
1592
+ message: label,
1593
+ lspVersion: RubyLsp::VERSION,
1594
+ rubyVersion: RUBY_VERSION,
1595
+ },
1596
+ },
1597
+ }))
1598
+
1599
+ result
1600
+ end
1561
1601
  end
1562
1602
  end
@@ -19,7 +19,7 @@ module RubyLsp
19
19
  infer_receiver_for_call_node(node, node_context)
20
20
  when Prism::InstanceVariableReadNode, Prism::InstanceVariableAndWriteNode, Prism::InstanceVariableWriteNode,
21
21
  Prism::InstanceVariableOperatorWriteNode, Prism::InstanceVariableOrWriteNode, Prism::InstanceVariableTargetNode,
22
- Prism::SuperNode, Prism::ForwardingSuperNode
22
+ Prism::SuperNode, Prism::ForwardingSuperNode, Prism::DefNode
23
23
  self_receiver_handling(node_context)
24
24
  when Prism::ClassVariableAndWriteNode, Prism::ClassVariableWriteNode, Prism::ClassVariableOperatorWriteNode,
25
25
  Prism::ClassVariableOrWriteNode, Prism::ClassVariableReadNode, Prism::ClassVariableTargetNode
@@ -129,21 +129,99 @@ module RubyLsp
129
129
  GuessedType.new(declaration.name)
130
130
  end
131
131
 
132
- #: (NodeContext node_context) -> Type
132
+ #: (NodeContext node_context) -> Type?
133
133
  def self_receiver_handling(node_context)
134
134
  nesting = node_context.nesting
135
135
  # If we're at the top level, then the invocation is happening on `<main>`, which is a special singleton that
136
136
  # inherits from Object
137
137
  return Type.new("Object") if nesting.empty?
138
- return Type.new(node_context.fully_qualified_name) if node_context.surrounding_method
138
+
139
+ surrounding_method = node_context.surrounding_method
140
+
141
+ if surrounding_method
142
+ receiver_name = surrounding_method.receiver
143
+
144
+ case receiver_name
145
+ when "self"
146
+ # `def self.foo` — self is the singleton of the enclosing class/module
147
+ return resolve_singleton_type_from_nesting(nesting)
148
+ when "none"
149
+ # Instance method — self is an instance of the enclosing class/module
150
+ return resolve_type_from_nesting(nesting)
151
+ when nil
152
+ # Dynamic receiver that we cannot handle
153
+ return
154
+ else
155
+ # Explicit constant receiver (e.g. `def Bar.baz`) — self is that constant's singleton class
156
+ resolved = resolve_receiver_singleton_type(receiver_name, nesting)
157
+ return resolved if resolved
158
+
159
+ return resolve_type_from_nesting(nesting)
160
+ end
161
+ end
139
162
 
140
163
  # If we're not inside a method, then we're inside the body of a class or module, which is a singleton
141
- # context.
142
- #
143
- # If the class/module definition is using compact style (e.g.: `class Foo::Bar`), then we need to split the name
144
- # into its individual parts to build the correct singleton name
145
- parts = nesting.flat_map { |part| part.split("::") }
146
- Type.new("#{parts.join("::")}::<#{parts.last}>")
164
+ # context. Resolve through the graph to get the correct fully qualified name
165
+ resolve_singleton_type_from_nesting(nesting)
166
+ end
167
+
168
+ # Resolves the fully qualified name of the innermost constant from the nesting and returns it as a type.
169
+ # For instance methods, the nesting won't have singleton markers, so the result is an instance type.
170
+ # For `def self.` methods, the nesting includes a singleton marker, which is preserved in the result.
171
+ #: (Array[String] nesting) -> Type
172
+ def resolve_type_from_nesting(nesting)
173
+ resolved_name = resolve_nesting_fully_qualified_name(nesting)
174
+ Type.new(resolved_name)
175
+ end
176
+
177
+ # Resolves the nesting and returns a singleton type (appends `::<Last>`)
178
+ #: (Array[String] nesting) -> Type
179
+ def resolve_singleton_type_from_nesting(nesting)
180
+ resolved_name = resolve_nesting_fully_qualified_name(nesting)
181
+ last_part = resolved_name.split("::").last #: as !nil
182
+ Type.new("#{resolved_name}::<#{last_part}>")
183
+ end
184
+
185
+ # Resolves the innermost constant in the nesting through the graph, handling compact-path definitions
186
+ # like `class Bar::Baz` inside a different module where the lexical nesting doesn't reflect the true
187
+ # constant hierarchy. Falls back to lexical joining if resolution fails.
188
+ #: (Array[String] nesting) -> String
189
+ def resolve_nesting_fully_qualified_name(nesting)
190
+ nesting_parts = nesting.dup
191
+ trailing_singletons = [] #: Array[String]
192
+
193
+ nesting_parts.reverse_each do |part|
194
+ break unless part.start_with?("<")
195
+
196
+ popped = nesting_parts.pop #: as !nil
197
+ trailing_singletons.unshift(popped)
198
+ end
199
+
200
+ if nesting_parts.any?
201
+ resolved = @graph.resolve_constant(
202
+ nesting_parts.last, #: as !nil
203
+ nesting_parts[0...-1], #: as !nil
204
+ )
205
+
206
+ if resolved
207
+ parts = resolved.name.split("::") + trailing_singletons
208
+ return parts.join("::")
209
+ end
210
+ end
211
+
212
+ # Fallback to lexical joining if resolution fails
213
+ nesting.flat_map { |part| part.split("::") }.join("::")
214
+ end
215
+
216
+ #: (String, Array[String]) -> Type?
217
+ def resolve_receiver_singleton_type(receiver_name, nesting)
218
+ receiver_declaration = @graph.resolve_constant(receiver_name, nesting)
219
+ return unless receiver_declaration.is_a?(Rubydex::Namespace)
220
+
221
+ singleton = receiver_declaration.singleton_class
222
+ return unless singleton
223
+
224
+ Type.new(singleton.name)
147
225
  end
148
226
 
149
227
  #: (NodeContext node_context) -> Type?
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-lsp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.27.0.beta1
4
+ version: 0.27.0.beta2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
@@ -106,7 +106,6 @@ files:
106
106
  - lib/ruby_indexer/lib/ruby_indexer/location.rb
107
107
  - lib/ruby_indexer/lib/ruby_indexer/prefix_tree.rb
108
108
  - lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb
109
- - lib/ruby_indexer/lib/ruby_indexer/reference_finder.rb
110
109
  - lib/ruby_indexer/lib/ruby_indexer/uri.rb
111
110
  - lib/ruby_indexer/lib/ruby_indexer/visibility_scope.rb
112
111
  - lib/ruby_indexer/ruby_indexer.rb
@@ -182,6 +181,7 @@ files:
182
181
  - lib/ruby_lsp/response_builders/signature_help.rb
183
182
  - lib/ruby_lsp/response_builders/test_collection.rb
184
183
  - lib/ruby_lsp/ruby_document.rb
184
+ - lib/ruby_lsp/rubydex/declaration.rb
185
185
  - lib/ruby_lsp/rubydex/definition.rb
186
186
  - lib/ruby_lsp/rubydex/reference.rb
187
187
  - lib/ruby_lsp/scope.rb
@@ -189,7 +189,6 @@ files:
189
189
  - lib/ruby_lsp/scripts/compose_bundle_windows.rb
190
190
  - lib/ruby_lsp/server.rb
191
191
  - lib/ruby_lsp/setup_bundler.rb
192
- - lib/ruby_lsp/static_docs.rb
193
192
  - lib/ruby_lsp/store.rb
194
193
  - lib/ruby_lsp/test_helper.rb
195
194
  - lib/ruby_lsp/test_reporters/lsp_reporter.rb
@@ -197,8 +196,6 @@ files:
197
196
  - lib/ruby_lsp/test_reporters/test_unit_reporter.rb
198
197
  - lib/ruby_lsp/type_inferrer.rb
199
198
  - lib/ruby_lsp/utils.rb
200
- - static_docs/break.md
201
- - static_docs/yield.md
202
199
  homepage: https://github.com/Shopify/ruby-lsp
203
200
  licenses:
204
201
  - MIT