ruby-lsp 0.27.0.beta1 → 0.27.0.beta3

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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/exe/ruby-lsp +0 -46
  4. data/exe/ruby-lsp-check +0 -15
  5. data/lib/ruby_lsp/addon.rb +19 -19
  6. data/lib/ruby_lsp/global_state.rb +1 -6
  7. data/lib/ruby_lsp/internal.rb +3 -2
  8. data/lib/ruby_lsp/listeners/code_lens.rb +1 -1
  9. data/lib/ruby_lsp/listeners/completion.rb +246 -382
  10. data/lib/ruby_lsp/listeners/definition.rb +7 -10
  11. data/lib/ruby_lsp/listeners/document_link.rb +4 -0
  12. data/lib/ruby_lsp/listeners/hover.rb +234 -82
  13. data/lib/ruby_lsp/listeners/signature_help.rb +11 -12
  14. data/lib/ruby_lsp/listeners/spec_style.rb +6 -1
  15. data/lib/ruby_lsp/listeners/test_discovery.rb +38 -15
  16. data/lib/ruby_lsp/listeners/test_style.rb +21 -9
  17. data/lib/ruby_lsp/node_context.rb +31 -8
  18. data/lib/ruby_lsp/requests/completion_resolve.rb +55 -39
  19. data/lib/ruby_lsp/requests/discover_tests.rb +5 -41
  20. data/lib/ruby_lsp/requests/hover.rb +2 -5
  21. data/lib/ruby_lsp/requests/prepare_type_hierarchy.rb +66 -22
  22. data/lib/ruby_lsp/requests/references.rb +180 -66
  23. data/lib/ruby_lsp/requests/rename.rb +1 -1
  24. data/lib/ruby_lsp/requests/request.rb +3 -33
  25. data/lib/ruby_lsp/requests/support/common.rb +82 -68
  26. data/lib/ruby_lsp/requests/type_hierarchy_supertypes.rb +82 -46
  27. data/lib/ruby_lsp/ruby_document.rb +0 -73
  28. data/lib/ruby_lsp/rubydex/declaration.rb +174 -0
  29. data/lib/ruby_lsp/rubydex/definition.rb +73 -0
  30. data/lib/ruby_lsp/rubydex/reference.rb +6 -1
  31. data/lib/ruby_lsp/rubydex/signature.rb +107 -0
  32. data/lib/ruby_lsp/scripts/compose_bundle.rb +1 -1
  33. data/lib/ruby_lsp/server.rb +56 -171
  34. data/lib/ruby_lsp/test_helper.rb +0 -1
  35. data/lib/ruby_lsp/test_reporters/lsp_reporter.rb +1 -1
  36. data/lib/ruby_lsp/type_inferrer.rb +89 -11
  37. metadata +12 -18
  38. data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +0 -276
  39. data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +0 -1101
  40. data/lib/ruby_indexer/lib/ruby_indexer/enhancement.rb +0 -44
  41. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +0 -605
  42. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +0 -1077
  43. data/lib/ruby_indexer/lib/ruby_indexer/location.rb +0 -37
  44. data/lib/ruby_indexer/lib/ruby_indexer/prefix_tree.rb +0 -149
  45. data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +0 -294
  46. data/lib/ruby_indexer/lib/ruby_indexer/reference_finder.rb +0 -335
  47. data/lib/ruby_indexer/lib/ruby_indexer/visibility_scope.rb +0 -32
  48. data/lib/ruby_indexer/ruby_indexer.rb +0 -20
  49. data/lib/ruby_lsp/static_docs.rb +0 -20
  50. data/static_docs/break.md +0 -103
  51. data/static_docs/yield.md +0 -81
  52. /data/lib/{ruby_indexer/lib/ruby_indexer → ruby_lsp}/uri.rb +0 -0
@@ -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
@@ -4,22 +4,6 @@
4
4
  module RubyLsp
5
5
  #: [ParseResultType = Prism::ParseLexResult]
6
6
  class RubyDocument < Document
7
- METHODS_THAT_CHANGE_DECLARATIONS = [
8
- :private_constant,
9
- :attr_reader,
10
- :attr_writer,
11
- :attr_accessor,
12
- :alias_method,
13
- :include,
14
- :prepend,
15
- :extend,
16
- :public,
17
- :protected,
18
- :private,
19
- :module_function,
20
- :private_class_method,
21
- ].freeze
22
-
23
7
  class << self
24
8
  #: (Prism::Node node, Integer char_position, code_units_cache: (^(Integer arg0) -> Integer | Prism::CodeUnitsCache), ?node_types: Array[singleton(Prism::Node)]) -> NodeContext
25
9
  def locate(node, char_position, code_units_cache:, node_types: [])
@@ -190,62 +174,5 @@ module RubyLsp
190
174
  node_types: node_types,
191
175
  )
192
176
  end
193
-
194
- #: -> bool
195
- def should_index?
196
- # This method controls when we should index documents. If there's no recent edit and the document has just been
197
- # opened, we need to index it
198
- return true unless @last_edit
199
-
200
- last_edit_may_change_declarations?
201
- end
202
-
203
- private
204
-
205
- #: -> bool
206
- def last_edit_may_change_declarations?
207
- case @last_edit
208
- when Delete
209
- # Not optimized yet. It's not trivial to identify that a declaration has been removed since the source is no
210
- # longer there and we don't remember the deleted text
211
- true
212
- when Insert, Replace
213
- position_may_impact_declarations?(@last_edit.range[:start])
214
- else
215
- false
216
- end
217
- end
218
-
219
- #: (Hash[Symbol, Integer] position) -> bool
220
- def position_may_impact_declarations?(position)
221
- node_context = locate_node(position)
222
- node_at_edit = node_context.node
223
-
224
- # Adjust to the parent when editing the constant of a class/module declaration
225
- if node_at_edit.is_a?(Prism::ConstantReadNode) || node_at_edit.is_a?(Prism::ConstantPathNode)
226
- node_at_edit = node_context.parent
227
- end
228
-
229
- case node_at_edit
230
- when Prism::ClassNode, Prism::ModuleNode, Prism::SingletonClassNode, Prism::DefNode,
231
- Prism::ConstantPathWriteNode, Prism::ConstantPathOrWriteNode, Prism::ConstantPathOperatorWriteNode,
232
- Prism::ConstantPathAndWriteNode, Prism::ConstantOrWriteNode, Prism::ConstantWriteNode,
233
- Prism::ConstantAndWriteNode, Prism::ConstantOperatorWriteNode, Prism::GlobalVariableAndWriteNode,
234
- Prism::GlobalVariableOperatorWriteNode, Prism::GlobalVariableOrWriteNode, Prism::GlobalVariableTargetNode,
235
- Prism::GlobalVariableWriteNode, Prism::InstanceVariableWriteNode, Prism::InstanceVariableAndWriteNode,
236
- Prism::InstanceVariableOperatorWriteNode, Prism::InstanceVariableOrWriteNode,
237
- Prism::InstanceVariableTargetNode, Prism::AliasMethodNode
238
- true
239
- when Prism::MultiWriteNode
240
- [*node_at_edit.lefts, *node_at_edit.rest, *node_at_edit.rights].any? do |node|
241
- node.is_a?(Prism::ConstantTargetNode) || node.is_a?(Prism::ConstantPathTargetNode)
242
- end
243
- when Prism::CallNode
244
- receiver = node_at_edit.receiver
245
- (!receiver || receiver.is_a?(Prism::SelfNode)) && METHODS_THAT_CHANGE_DECLARATIONS.include?(node_at_edit.name)
246
- else
247
- false
248
- end
249
- end
250
177
  end
251
178
  end
@@ -0,0 +1,174 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Rubydex
5
+ # @abstract
6
+ class Declaration
7
+ # Detail text shown on a `TypeHierarchyItem` for this declaration. Hints at multiplicity
8
+ # when the declaration spans more than one re-open; otherwise falls back to the primary
9
+ # definition's file name so users can quickly see where the type comes from.
10
+ #
11
+ #: -> String?
12
+ def lsp_type_hierarchy_detail
13
+ defs = definitions
14
+ count = defs.count
15
+ return "#{count} definitions" if count > 1
16
+
17
+ primary = defs.first
18
+ return unless primary
19
+
20
+ uri = URI(primary.location.uri)
21
+ path = uri.full_path
22
+ path ? File.basename(path) : uri.to_s
23
+ end
24
+
25
+ # @abstract
26
+ #: -> Integer
27
+ def to_lsp_completion_kind
28
+ raise RubyLsp::AbstractMethodInvokedError
29
+ end
30
+ end
31
+
32
+ # @abstract
33
+ class Namespace
34
+ # Resolved, deduplicated direct supertypes across every re-open of this declaration.
35
+ # Aggregates each definition's own `superclass`/`include`/`prepend` references and drops
36
+ # unresolved ones. Order is stable (first-seen across definitions).
37
+ #: -> Array[Rubydex::Namespace]
38
+ def direct_supertypes
39
+ seen = {} #: Hash[String, Rubydex::Namespace]
40
+
41
+ definitions.each do |definition|
42
+ definition.direct_supertype_references.each do |ref|
43
+ next unless ref.is_a?(ResolvedConstantReference)
44
+
45
+ target = ref.declaration
46
+ next unless target.is_a?(Namespace)
47
+ next if seen.key?(target.name)
48
+
49
+ seen[target.name] = target
50
+ end
51
+ end
52
+
53
+ seen.values
54
+ end
55
+ end
56
+
57
+ class Class
58
+ # @override
59
+ #: -> Integer
60
+ def to_lsp_completion_kind
61
+ RubyLsp::Constant::CompletionItemKind::CLASS
62
+ end
63
+ end
64
+
65
+ class Module
66
+ # @override
67
+ #: -> Integer
68
+ def to_lsp_completion_kind
69
+ RubyLsp::Constant::CompletionItemKind::MODULE
70
+ end
71
+ end
72
+
73
+ class SingletonClass
74
+ # @override
75
+ #: -> Integer
76
+ def to_lsp_completion_kind
77
+ RubyLsp::Constant::CompletionItemKind::CLASS
78
+ end
79
+ end
80
+
81
+ class Todo
82
+ # @override
83
+ #: -> Integer
84
+ def to_lsp_completion_kind
85
+ RubyLsp::Constant::CompletionItemKind::CLASS
86
+ end
87
+ end
88
+
89
+ class Constant
90
+ # @override
91
+ #: -> Integer
92
+ def to_lsp_completion_kind
93
+ RubyLsp::Constant::CompletionItemKind::CONSTANT
94
+ end
95
+ end
96
+
97
+ class ConstantAlias
98
+ # @override
99
+ #: -> Integer
100
+ def to_lsp_completion_kind
101
+ RubyLsp::Constant::CompletionItemKind::CONSTANT
102
+ end
103
+ end
104
+
105
+ class Method
106
+ # @override
107
+ #: -> Integer
108
+ def to_lsp_completion_kind
109
+ RubyLsp::Constant::CompletionItemKind::METHOD
110
+ end
111
+
112
+ # All signatures collected across every definition (re-opens, RBS overloads, alias targets) of this method.
113
+ #: () -> Array[Rubydex::Signature]
114
+ def signatures
115
+ definitions.flat_map do |defn|
116
+ case defn
117
+ when Rubydex::MethodDefinition, Rubydex::MethodAliasDefinition
118
+ defn.signatures
119
+ else
120
+ []
121
+ end
122
+ end
123
+ end
124
+
125
+ # Decorated parameter list of the first signature, e.g. `(a, b = <default>, &block)`. Returns `()` when there are
126
+ # no signatures (e.g. an unresolved alias).
127
+ #: () -> String
128
+ def decorated_parameters
129
+ first = signatures.first
130
+ return "()" unless first
131
+
132
+ "(#{first.format})"
133
+ end
134
+
135
+ # Suffix line that hints at additional overloads beyond the first signature, matching the legacy index entry
136
+ # rendering used in hover.
137
+ #: () -> String
138
+ def formatted_signatures
139
+ count = signatures.size
140
+ case count
141
+ when 0, 1
142
+ ""
143
+ when 2
144
+ "\n(+1 overload)"
145
+ else
146
+ "\n(+#{count - 1} overloads)"
147
+ end
148
+ end
149
+ end
150
+
151
+ class InstanceVariable
152
+ # @override
153
+ #: -> Integer
154
+ def to_lsp_completion_kind
155
+ RubyLsp::Constant::CompletionItemKind::FIELD
156
+ end
157
+ end
158
+
159
+ class ClassVariable
160
+ # @override
161
+ #: -> Integer
162
+ def to_lsp_completion_kind
163
+ RubyLsp::Constant::CompletionItemKind::FIELD
164
+ end
165
+ end
166
+
167
+ class GlobalVariable
168
+ # @override
169
+ #: -> Integer
170
+ def to_lsp_completion_kind
171
+ RubyLsp::Constant::CompletionItemKind::VARIABLE
172
+ end
173
+ end
174
+ 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
@@ -126,6 +183,14 @@ module Rubydex
126
183
  end
127
184
  end
128
185
 
186
+ class ConstantVisibilityDefinition
187
+ # @override
188
+ #: () -> Integer
189
+ def to_lsp_kind
190
+ RubyLsp::Constant::SymbolKind::CONSTANT
191
+ end
192
+ end
193
+
129
194
  class MethodDefinition
130
195
  # @override
131
196
  #: () -> Integer
@@ -142,6 +207,14 @@ module Rubydex
142
207
  end
143
208
  end
144
209
 
210
+ class MethodVisibilityDefinition
211
+ # @override
212
+ #: () -> Integer
213
+ def to_lsp_kind
214
+ RubyLsp::Constant::SymbolKind::METHOD
215
+ end
216
+ end
217
+
145
218
  class AttrReaderDefinition
146
219
  # @override
147
220
  #: () -> Integer
@@ -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
@@ -0,0 +1,107 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Rubydex
5
+ class Signature
6
+ # Returns a string with the decorated names of the parameters of this signature, e.g.
7
+ # `(a, b = <default>, *c, d, e:, f: <default>, **g, &h)`.
8
+ #: () -> String
9
+ def format
10
+ parameters.map { |param| decorated_name(param) }.join(", ")
11
+ end
12
+
13
+ # Returns `true` if the given call node arguments array matches this signature. The matching is intentionally lenient
14
+ # because this method is used to detect which overload should be displayed in signature help while the user is still
15
+ # typing the call. We prefer returning `true` for situations that cannot be analyzed statically (e.g. presence of
16
+ # splats, keyword splats, forwarding) and accept missing arguments since the user may not be done typing yet.
17
+ #: (Array[Prism::Node] arguments) -> bool
18
+ def matches?(arguments)
19
+ min_pos = 0
20
+ max_pos = 0 #: (Integer | Float)
21
+ names = []
22
+ has_forward = false #: bool
23
+ has_keyword_rest = false #: bool
24
+
25
+ parameters.each do |param|
26
+ case param
27
+ when PositionalParameter, PostParameter
28
+ min_pos += 1
29
+ max_pos += 1
30
+ when OptionalPositionalParameter
31
+ max_pos += 1
32
+ when RestPositionalParameter
33
+ max_pos = Float::INFINITY
34
+ when ForwardParameter
35
+ max_pos = Float::INFINITY
36
+ has_forward = true
37
+ when KeywordParameter, OptionalKeywordParameter
38
+ names << param.name
39
+ when RestKeywordParameter
40
+ has_keyword_rest = true
41
+ end
42
+ end
43
+
44
+ keyword_hash_nodes, positional_args = arguments.partition { |arg| arg.is_a?(Prism::KeywordHashNode) }
45
+ keyword_args = keyword_hash_nodes.first #: as Prism::KeywordHashNode?
46
+ &.elements
47
+ forwarding_arguments, positionals = positional_args.partition do |arg|
48
+ arg.is_a?(Prism::ForwardingArgumentsNode)
49
+ end
50
+
51
+ return true if has_forward && min_pos == 0
52
+
53
+ # If the only argument passed is a forwarding argument, then anything will match
54
+ (positionals.empty? && forwarding_arguments.any?) ||
55
+ (
56
+ positional_arguments_match?(positionals, forwarding_arguments, keyword_args, min_pos, max_pos) &&
57
+ (has_forward || has_keyword_rest || keyword_arguments_match?(keyword_args, names))
58
+ )
59
+ end
60
+
61
+ private
62
+
63
+ #: (Parameter) -> String
64
+ def decorated_name(param)
65
+ case param
66
+ when OptionalPositionalParameter
67
+ "#{param.name} = <default>"
68
+ when RestPositionalParameter
69
+ "*#{param.name}"
70
+ when KeywordParameter
71
+ "#{param.name}:"
72
+ when OptionalKeywordParameter
73
+ "#{param.name}: <default>"
74
+ when RestKeywordParameter
75
+ "**#{param.name}"
76
+ when BlockParameter
77
+ "&#{param.name}"
78
+ else
79
+ param.name.to_s
80
+ end
81
+ end
82
+
83
+ #: (Array[Prism::Node] positional_args, Array[Prism::Node] forwarding_arguments, Array[Prism::Node]? keyword_args, Integer min_pos, (Integer | Float) max_pos) -> bool
84
+ def positional_arguments_match?(positional_args, forwarding_arguments, keyword_args, min_pos, max_pos)
85
+ (min_pos > 0 && positional_args.any? { |arg| arg.is_a?(Prism::SplatNode) }) ||
86
+ (min_pos - positional_args.length > 0 && keyword_args&.any? { |arg| arg.is_a?(Prism::AssocSplatNode) }) ||
87
+ (min_pos - positional_args.length > 0 && forwarding_arguments.any?) ||
88
+ (min_pos > 0 && positional_args.length <= max_pos) ||
89
+ (min_pos == 0 && positional_args.empty?)
90
+ end
91
+
92
+ #: (Array[Prism::Node]? args, Array[Symbol] names) -> bool
93
+ def keyword_arguments_match?(args, names)
94
+ return true unless args
95
+ return true if args.any? { |arg| arg.is_a?(Prism::AssocSplatNode) }
96
+
97
+ arg_names = args.filter_map do |arg|
98
+ next unless arg.is_a?(Prism::AssocNode)
99
+
100
+ key = arg.key
101
+ key.value&.to_sym if key.is_a?(Prism::SymbolNode)
102
+ end
103
+
104
+ (arg_names - names).empty?
105
+ end
106
+ end
107
+ end
@@ -5,7 +5,7 @@ def compose(raw_initialize, **options)
5
5
  require_relative "../setup_bundler"
6
6
  require "json"
7
7
  require "uri"
8
- require_relative "../../ruby_indexer/lib/ruby_indexer/uri"
8
+ require_relative "../uri"
9
9
 
10
10
  initialize_request = JSON.parse(raw_initialize, symbolize_names: true)
11
11
  workspace_uri = initialize_request.dig(:params, :workspaceFolders, 0, :uri)