ruby-lsp 0.17.2 → 0.17.4

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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -0
  3. data/VERSION +1 -1
  4. data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +280 -74
  5. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +102 -102
  6. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +234 -56
  7. data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +147 -0
  8. data/lib/ruby_indexer/ruby_indexer.rb +1 -0
  9. data/lib/ruby_indexer/test/classes_and_modules_test.rb +49 -2
  10. data/lib/ruby_indexer/test/configuration_test.rb +1 -1
  11. data/lib/ruby_indexer/test/constant_test.rb +1 -1
  12. data/lib/ruby_indexer/test/index_test.rb +702 -71
  13. data/lib/ruby_indexer/test/instance_variables_test.rb +84 -7
  14. data/lib/ruby_indexer/test/method_test.rb +74 -24
  15. data/lib/ruby_indexer/test/rbs_indexer_test.rb +67 -0
  16. data/lib/ruby_indexer/test/test_case.rb +7 -0
  17. data/lib/ruby_lsp/document.rb +37 -8
  18. data/lib/ruby_lsp/global_state.rb +43 -18
  19. data/lib/ruby_lsp/internal.rb +2 -0
  20. data/lib/ruby_lsp/listeners/code_lens.rb +2 -2
  21. data/lib/ruby_lsp/listeners/completion.rb +53 -14
  22. data/lib/ruby_lsp/listeners/definition.rb +11 -7
  23. data/lib/ruby_lsp/listeners/hover.rb +14 -7
  24. data/lib/ruby_lsp/listeners/signature_help.rb +5 -2
  25. data/lib/ruby_lsp/node_context.rb +6 -1
  26. data/lib/ruby_lsp/requests/completion.rb +5 -4
  27. data/lib/ruby_lsp/requests/completion_resolve.rb +8 -0
  28. data/lib/ruby_lsp/requests/prepare_type_hierarchy.rb +88 -0
  29. data/lib/ruby_lsp/requests/support/common.rb +19 -1
  30. data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +12 -4
  31. data/lib/ruby_lsp/requests/type_hierarchy_supertypes.rb +91 -0
  32. data/lib/ruby_lsp/requests/workspace_symbol.rb +1 -21
  33. data/lib/ruby_lsp/requests.rb +2 -0
  34. data/lib/ruby_lsp/server.rb +54 -4
  35. data/lib/ruby_lsp/test_helper.rb +1 -1
  36. data/lib/ruby_lsp/type_inferrer.rb +86 -0
  37. metadata +29 -4
@@ -15,15 +15,26 @@ module RubyLsp
15
15
  typechecker_enabled: T::Boolean,
16
16
  dispatcher: Prism::Dispatcher,
17
17
  uri: URI::Generic,
18
+ trigger_character: T.nilable(String),
18
19
  ).void
19
20
  end
20
- def initialize(response_builder, global_state, node_context, typechecker_enabled, dispatcher, uri) # rubocop:disable Metrics/ParameterLists
21
+ def initialize( # rubocop:disable Metrics/ParameterLists
22
+ response_builder,
23
+ global_state,
24
+ node_context,
25
+ typechecker_enabled,
26
+ dispatcher,
27
+ uri,
28
+ trigger_character
29
+ )
21
30
  @response_builder = response_builder
22
31
  @global_state = global_state
23
32
  @index = T.let(global_state.index, RubyIndexer::Index)
33
+ @type_inferrer = T.let(global_state.type_inferrer, TypeInferrer)
24
34
  @node_context = node_context
25
35
  @typechecker_enabled = typechecker_enabled
26
36
  @uri = uri
37
+ @trigger_character = trigger_character
27
38
 
28
39
  dispatcher.register(
29
40
  self,
@@ -42,7 +53,7 @@ module RubyLsp
42
53
  # Handle completion on regular constant references (e.g. `Bar`)
43
54
  sig { params(node: Prism::ConstantReadNode).void }
44
55
  def on_constant_read_node_enter(node)
45
- return if @global_state.typechecker
56
+ return if @global_state.has_type_checker
46
57
 
47
58
  name = constant_name(node)
48
59
  return if name.nil?
@@ -63,7 +74,7 @@ module RubyLsp
63
74
  # Handle completion on namespaced constant references (e.g. `Foo::Bar`)
64
75
  sig { params(node: Prism::ConstantPathNode).void }
65
76
  def on_constant_path_node_enter(node)
66
- return if @global_state.typechecker
77
+ return if @global_state.has_type_checker
67
78
 
68
79
  name = constant_name(node)
69
80
  return if name.nil?
@@ -107,7 +118,7 @@ module RubyLsp
107
118
  when "require_relative"
108
119
  complete_require_relative(node)
109
120
  else
110
- complete_self_receiver_method(node, name) if !@typechecker_enabled && self_receiver?(node)
121
+ complete_methods(node, name) unless @typechecker_enabled
111
122
  end
112
123
  end
113
124
 
@@ -158,7 +169,7 @@ module RubyLsp
158
169
  name.delete_suffix("::")
159
170
  else
160
171
  *namespace, incomplete_name = name.split("::")
161
- T.must(namespace).join("::")
172
+ namespace.join("::")
162
173
  end
163
174
 
164
175
  nesting = @node_context.nesting
@@ -192,7 +203,10 @@ module RubyLsp
192
203
 
193
204
  sig { params(name: String, location: Prism::Location).void }
194
205
  def handle_instance_variable_completion(name, location)
195
- @index.instance_variable_completion_candidates(name, @node_context.fully_qualified_name).each do |entry|
206
+ type = @type_inferrer.infer_receiver_type(@node_context)
207
+ return unless type
208
+
209
+ @index.instance_variable_completion_candidates(name, type).each do |entry|
196
210
  variable_name = entry.name
197
211
 
198
212
  @response_builder << Interface::CompletionItem.new(
@@ -257,20 +271,45 @@ module RubyLsp
257
271
  end
258
272
 
259
273
  sig { params(node: Prism::CallNode, name: String).void }
260
- def complete_self_receiver_method(node, name)
261
- receiver_entries = @index[@node_context.fully_qualified_name]
262
- return unless receiver_entries
274
+ def complete_methods(node, name)
275
+ type = @type_inferrer.infer_receiver_type(@node_context)
276
+ return unless type
277
+
278
+ # When the trigger character is a dot, Prism matches the name of the call node to whatever is next in the source
279
+ # code, leading to us searching for the wrong name. What we want to do instead is show every available method
280
+ # when dot is pressed
281
+ method_name = @trigger_character == "." ? nil : name
282
+
283
+ range = if method_name
284
+ range_from_location(T.must(node.message_loc))
285
+ else
286
+ loc = T.must(node.call_operator_loc)
287
+ Interface::Range.new(
288
+ start: Interface::Position.new(line: loc.start_line - 1, character: loc.start_column + 1),
289
+ end: Interface::Position.new(line: loc.start_line - 1, character: loc.start_column + 1),
290
+ )
291
+ end
263
292
 
264
- receiver = T.must(receiver_entries.first)
293
+ @index.method_completion_candidates(method_name, type).each do |entry|
294
+ entry_name = entry.name
265
295
 
266
- @index.method_completion_candidates(name, receiver.name).each do |entry|
267
- @response_builder << build_method_completion(T.cast(entry, RubyIndexer::Entry::Member), node)
296
+ @response_builder << Interface::CompletionItem.new(
297
+ label: entry_name,
298
+ filter_text: entry_name,
299
+ text_edit: Interface::TextEdit.new(range: range, new_text: entry_name),
300
+ kind: Constant::CompletionItemKind::METHOD,
301
+ data: {
302
+ owner_name: entry.owner&.name,
303
+ },
304
+ )
268
305
  end
306
+ rescue RubyIndexer::Index::NonExistingNamespaceError
307
+ # We have not indexed this namespace, so we can't provide any completions
269
308
  end
270
309
 
271
310
  sig do
272
311
  params(
273
- entry: RubyIndexer::Entry::Member,
312
+ entry: T.any(RubyIndexer::Entry::Member, RubyIndexer::Entry::MethodAlias),
274
313
  node: Prism::CallNode,
275
314
  ).returns(Interface::CompletionItem)
276
315
  end
@@ -283,7 +322,7 @@ module RubyLsp
283
322
  text_edit: Interface::TextEdit.new(range: range_from_location(T.must(node.message_loc)), new_text: name),
284
323
  kind: Constant::CompletionItemKind::METHOD,
285
324
  label_details: Interface::CompletionItemLabelDetails.new(
286
- detail: "(#{entry.parameters.map(&:decorated_name).join(", ")})",
325
+ detail: entry.decorated_parameters,
287
326
  description: entry.file_name,
288
327
  ),
289
328
  documentation: Interface::MarkupContent.new(
@@ -23,6 +23,7 @@ module RubyLsp
23
23
  @response_builder = response_builder
24
24
  @global_state = global_state
25
25
  @index = T.let(global_state.index, RubyIndexer::Index)
26
+ @type_inferrer = T.let(global_state.type_inferrer, TypeInferrer)
26
27
  @uri = uri
27
28
  @node_context = node_context
28
29
  @typechecker_enabled = typechecker_enabled
@@ -48,7 +49,7 @@ module RubyLsp
48
49
  message = node.message
49
50
  return unless message
50
51
 
51
- handle_method_definition(message, self_receiver?(node))
52
+ handle_method_definition(message, @type_inferrer.infer_receiver_type(@node_context))
52
53
  end
53
54
 
54
55
  sig { params(node: Prism::StringNode).void }
@@ -70,7 +71,7 @@ module RubyLsp
70
71
  value = expression.value
71
72
  return unless value
72
73
 
73
- handle_method_definition(value, false)
74
+ handle_method_definition(value, nil)
74
75
  end
75
76
 
76
77
  sig { params(node: Prism::ConstantPathNode).void }
@@ -123,7 +124,10 @@ module RubyLsp
123
124
 
124
125
  sig { params(name: String).void }
125
126
  def handle_instance_variable_definition(name)
126
- entries = @index.resolve_instance_variable(name, @node_context.fully_qualified_name)
127
+ type = @type_inferrer.infer_receiver_type(@node_context)
128
+ return unless type
129
+
130
+ entries = @index.resolve_instance_variable(name, type)
127
131
  return unless entries
128
132
 
129
133
  entries.each do |entry|
@@ -141,10 +145,10 @@ module RubyLsp
141
145
  # If by any chance we haven't indexed the owner, then there's no way to find the right declaration
142
146
  end
143
147
 
144
- sig { params(message: String, self_receiver: T::Boolean).void }
145
- def handle_method_definition(message, self_receiver)
146
- methods = if self_receiver
147
- @index.resolve_method(message, @node_context.fully_qualified_name)
148
+ sig { params(message: String, receiver_type: T.nilable(String)).void }
149
+ def handle_method_definition(message, receiver_type)
150
+ methods = if receiver_type
151
+ @index.resolve_method(message, receiver_type)
148
152
  else
149
153
  # If the method doesn't have a receiver, then we provide a few candidates to jump to
150
154
  # But we don't want to provide too many candidates, as it can be overwhelming
@@ -47,6 +47,7 @@ module RubyLsp
47
47
  @response_builder = response_builder
48
48
  @global_state = global_state
49
49
  @index = T.let(global_state.index, RubyIndexer::Index)
50
+ @type_inferrer = T.let(global_state.type_inferrer, TypeInferrer)
50
51
  @path = T.let(uri.to_standardized_path, T.nilable(String))
51
52
  @node_context = node_context
52
53
  @typechecker_enabled = typechecker_enabled
@@ -78,14 +79,14 @@ module RubyLsp
78
79
 
79
80
  sig { params(node: Prism::ConstantWriteNode).void }
80
81
  def on_constant_write_node_enter(node)
81
- return if @global_state.typechecker
82
+ return if @global_state.has_type_checker
82
83
 
83
84
  generate_hover(node.name.to_s, node.name_loc)
84
85
  end
85
86
 
86
87
  sig { params(node: Prism::ConstantPathNode).void }
87
88
  def on_constant_path_node_enter(node)
88
- return if @global_state.typechecker
89
+ return if @global_state.has_type_checker
89
90
 
90
91
  name = constant_name(node)
91
92
  return if name.nil?
@@ -95,8 +96,6 @@ module RubyLsp
95
96
 
96
97
  sig { params(node: Prism::CallNode).void }
97
98
  def on_call_node_enter(node)
98
- return unless self_receiver?(node)
99
-
100
99
  if @path && File.basename(@path) == GEMFILE_NAME && node.name == :gem
101
100
  generate_gem_hover(node)
102
101
  return
@@ -107,10 +106,15 @@ module RubyLsp
107
106
  message = node.message
108
107
  return unless message
109
108
 
110
- methods = @index.resolve_method(message, @node_context.fully_qualified_name)
109
+ type = @type_inferrer.infer_receiver_type(@node_context)
110
+ return unless type
111
+
112
+ methods = @index.resolve_method(message, type)
111
113
  return unless methods
112
114
 
113
- categorized_markdown_from_index_entries(message, methods).each do |category, content|
115
+ title = "#{message}#{T.must(methods.first).decorated_parameters}"
116
+
117
+ categorized_markdown_from_index_entries(title, methods).each do |category, content|
114
118
  @response_builder.push(content, category: category)
115
119
  end
116
120
  end
@@ -149,7 +153,10 @@ module RubyLsp
149
153
 
150
154
  sig { params(name: String).void }
151
155
  def handle_instance_variable_hover(name)
152
- entries = @index.resolve_instance_variable(name, @node_context.fully_qualified_name)
156
+ type = @type_inferrer.infer_receiver_type(@node_context)
157
+ return unless type
158
+
159
+ entries = @index.resolve_instance_variable(name, type)
153
160
  return unless entries
154
161
 
155
162
  categorized_markdown_from_index_entries(name, entries).each do |category, content|
@@ -21,6 +21,7 @@ module RubyLsp
21
21
  @response_builder = response_builder
22
22
  @global_state = global_state
23
23
  @index = T.let(global_state.index, RubyIndexer::Index)
24
+ @type_inferrer = T.let(global_state.type_inferrer, TypeInferrer)
24
25
  @node_context = node_context
25
26
  dispatcher.register(self, :on_call_node_enter)
26
27
  end
@@ -28,12 +29,14 @@ module RubyLsp
28
29
  sig { params(node: Prism::CallNode).void }
29
30
  def on_call_node_enter(node)
30
31
  return if @typechecker_enabled
31
- return unless self_receiver?(node)
32
32
 
33
33
  message = node.message
34
34
  return unless message
35
35
 
36
- methods = @index.resolve_method(message, @node_context.fully_qualified_name)
36
+ type = @type_inferrer.infer_receiver_type(@node_context)
37
+ return unless type
38
+
39
+ methods = @index.resolve_method(message, type)
37
40
  return unless methods
38
41
 
39
42
  target_method = methods.first
@@ -16,19 +16,24 @@ module RubyLsp
16
16
  sig { returns(T.nilable(Prism::CallNode)) }
17
17
  attr_reader :call_node
18
18
 
19
+ sig { returns(T.nilable(String)) }
20
+ attr_reader :surrounding_method
21
+
19
22
  sig do
20
23
  params(
21
24
  node: T.nilable(Prism::Node),
22
25
  parent: T.nilable(Prism::Node),
23
26
  nesting: T::Array[String],
24
27
  call_node: T.nilable(Prism::CallNode),
28
+ surrounding_method: T.nilable(String),
25
29
  ).void
26
30
  end
27
- def initialize(node, parent, nesting, call_node)
31
+ def initialize(node, parent, nesting, call_node, surrounding_method)
28
32
  @node = node
29
33
  @parent = parent
30
34
  @nesting = nesting
31
35
  @call_node = call_node
36
+ @surrounding_method = surrounding_method
32
37
  end
33
38
 
34
39
  sig { returns(String) }
@@ -36,7 +36,7 @@ module RubyLsp
36
36
  def provider
37
37
  Interface::CompletionOptions.new(
38
38
  resolve_provider: true,
39
- trigger_characters: ["/", "\"", "'", ":", "@"],
39
+ trigger_characters: ["/", "\"", "'", ":", "@", "."],
40
40
  completion_item: {
41
41
  labelDetailsSupport: true,
42
42
  },
@@ -48,18 +48,18 @@ module RubyLsp
48
48
  params(
49
49
  document: Document,
50
50
  global_state: GlobalState,
51
- position: T::Hash[Symbol, T.untyped],
51
+ params: T::Hash[Symbol, T.untyped],
52
52
  typechecker_enabled: T::Boolean,
53
53
  dispatcher: Prism::Dispatcher,
54
54
  ).void
55
55
  end
56
- def initialize(document, global_state, position, typechecker_enabled, dispatcher)
56
+ def initialize(document, global_state, params, typechecker_enabled, dispatcher)
57
57
  super()
58
58
  @target = T.let(nil, T.nilable(Prism::Node))
59
59
  @dispatcher = dispatcher
60
60
  # Completion always receives the position immediately after the character that was just typed. Here we adjust it
61
61
  # back by 1, so that we find the right node
62
- char_position = document.create_scanner.find_char_position(position) - 1
62
+ char_position = document.create_scanner.find_char_position(params[:position]) - 1
63
63
  node_context = document.locate(
64
64
  document.tree,
65
65
  char_position,
@@ -87,6 +87,7 @@ module RubyLsp
87
87
  typechecker_enabled,
88
88
  dispatcher,
89
89
  document.uri,
90
+ params.dig(:context, :triggerCharacter),
90
91
  )
91
92
 
92
93
  Addon.addons.each do |addon|
@@ -56,8 +56,16 @@ module RubyLsp
56
56
  end
57
57
  end
58
58
 
59
+ first_entry = T.must(entries.first)
60
+
61
+ if first_entry.is_a?(RubyIndexer::Entry::Member)
62
+ detail = first_entry.decorated_parameters
63
+ label = "#{label}#{first_entry.decorated_parameters}"
64
+ end
65
+
59
66
  @item[:labelDetails] = Interface::CompletionItemLabelDetails.new(
60
67
  description: entries.take(MAX_DOCUMENTATION_ENTRIES).map(&:file_name).join(","),
68
+ detail: detail,
61
69
  )
62
70
 
63
71
  @item[:documentation] = Interface::MarkupContent.new(
@@ -0,0 +1,88 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ module Requests
6
+ # ![Prepare type hierarchy demo](../../prepare_type_hierarchy.gif)
7
+ #
8
+ # The [prepare type hierarchy
9
+ # request](https://microsoft.github.io/language-server-protocol/specification#textDocument_prepareTypeHierarchy)
10
+ # displays the list of ancestors (supertypes) and descendants (subtypes) for the selected type.
11
+ #
12
+ # Currently only supports supertypes due to a limitation of the index.
13
+ #
14
+ # # Example
15
+ #
16
+ # ```ruby
17
+ # class Foo; end
18
+ # class Bar < Foo; end
19
+ #
20
+ # puts Bar # <-- right click on `Bar` and select "Show Type Hierarchy"
21
+ # ```
22
+ class PrepareTypeHierarchy < Request
23
+ extend T::Sig
24
+
25
+ include Support::Common
26
+
27
+ class << self
28
+ extend T::Sig
29
+
30
+ sig { returns(Interface::TypeHierarchyOptions) }
31
+ def provider
32
+ Interface::TypeHierarchyOptions.new
33
+ end
34
+ end
35
+
36
+ sig do
37
+ params(
38
+ document: Document,
39
+ index: RubyIndexer::Index,
40
+ position: T::Hash[Symbol, T.untyped],
41
+ ).void
42
+ end
43
+ def initialize(document, index, position)
44
+ super()
45
+
46
+ @document = document
47
+ @index = index
48
+ @position = position
49
+ end
50
+
51
+ sig { override.returns(T.nilable(T::Array[Interface::TypeHierarchyItem])) }
52
+ def perform
53
+ context = @document.locate_node(
54
+ @position,
55
+ node_types: [
56
+ Prism::ConstantReadNode,
57
+ Prism::ConstantWriteNode,
58
+ Prism::ConstantPathNode,
59
+ ],
60
+ )
61
+
62
+ node = context.node
63
+ parent = context.parent
64
+ return unless node && parent
65
+
66
+ target = determine_target(node, parent, @position)
67
+ entries = @index.resolve(target.slice, context.nesting)
68
+ return unless entries
69
+
70
+ # While the spec allows for multiple entries, VSCode seems to only support one
71
+ # We'll just return the first one for now
72
+ first_entry = T.must(entries.first)
73
+
74
+ range = range_from_location(first_entry.location)
75
+
76
+ [
77
+ Interface::TypeHierarchyItem.new(
78
+ name: first_entry.name,
79
+ kind: kind_for_entry(first_entry),
80
+ uri: URI::Generic.from_path(path: first_entry.file_path).to_s,
81
+ range: range,
82
+ selection_range: range,
83
+ ),
84
+ ]
85
+ end
86
+ end
87
+ end
88
+ end
@@ -26,7 +26,7 @@ module RubyLsp
26
26
  )
27
27
  end
28
28
 
29
- sig { params(location: Prism::Location).returns(Interface::Range) }
29
+ sig { params(location: T.any(Prism::Location, RubyIndexer::Location)).returns(Interface::Range) }
30
30
  def range_from_location(location)
31
31
  Interface::Range.new(
32
32
  start: Interface::Position.new(
@@ -186,6 +186,24 @@ module RubyLsp
186
186
  current = current.parent
187
187
  end
188
188
  end
189
+
190
+ sig { params(entry: RubyIndexer::Entry).returns(T.nilable(Integer)) }
191
+ def kind_for_entry(entry)
192
+ case entry
193
+ when RubyIndexer::Entry::Class
194
+ Constant::SymbolKind::CLASS
195
+ when RubyIndexer::Entry::Module
196
+ Constant::SymbolKind::NAMESPACE
197
+ when RubyIndexer::Entry::Constant
198
+ Constant::SymbolKind::CONSTANT
199
+ when RubyIndexer::Entry::Method
200
+ entry.name == "initialize" ? Constant::SymbolKind::CONSTRUCTOR : Constant::SymbolKind::METHOD
201
+ when RubyIndexer::Entry::Accessor
202
+ Constant::SymbolKind::PROPERTY
203
+ when RubyIndexer::Entry::InstanceVariable
204
+ Constant::SymbolKind::FIELD
205
+ end
206
+ end
189
207
  end
190
208
  end
191
209
  end
@@ -42,7 +42,7 @@ module RubyLsp
42
42
  def to_lsp_code_actions
43
43
  code_actions = []
44
44
 
45
- code_actions << autocorrect_action if @offense.correctable?
45
+ code_actions << autocorrect_action if correctable?
46
46
  code_actions << disable_line_action
47
47
 
48
48
  code_actions
@@ -70,7 +70,7 @@ module RubyLsp
70
70
  ),
71
71
  ),
72
72
  data: {
73
- correctable: @offense.correctable?,
73
+ correctable: correctable?,
74
74
  code_actions: to_lsp_code_actions,
75
75
  },
76
76
  )
@@ -81,7 +81,7 @@ module RubyLsp
81
81
  sig { returns(String) }
82
82
  def message
83
83
  message = @offense.message
84
- message += "\n\nThis offense is not auto-correctable.\n" unless @offense.correctable?
84
+ message += "\n\nThis offense is not auto-correctable.\n" unless correctable?
85
85
  message
86
86
  end
87
87
 
@@ -115,7 +115,7 @@ module RubyLsp
115
115
  uri: @uri.to_s,
116
116
  version: nil,
117
117
  ),
118
- edits: @offense.correctable? ? offense_replacements : [],
118
+ edits: correctable? ? offense_replacements : [],
119
119
  ),
120
120
  ],
121
121
  ),
@@ -193,6 +193,14 @@ module RubyLsp
193
193
  line.length
194
194
  end
195
195
  end
196
+
197
+ # When `RuboCop::LSP.enable` is called, contextual autocorrect will not offer itself
198
+ # as `correctable?` to prevent annoying changes while typing. Instead check if
199
+ # a corrector is present. If it is, then that means some code transformation can be applied.
200
+ sig { returns(T::Boolean) }
201
+ def correctable?
202
+ !@offense.corrector.nil?
203
+ end
196
204
  end
197
205
  end
198
206
  end
@@ -0,0 +1,91 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ module Requests
6
+ # ![Type hierarchy supertypes demo](../../type_hierarchy_supertypes.gif)
7
+ #
8
+ # The [type hierarchy supertypes
9
+ # request](https://microsoft.github.io/language-server-protocol/specification#typeHierarchy_supertypes)
10
+ # displays the list of ancestors (supertypes) for the selected type.
11
+ #
12
+ # # Example
13
+ #
14
+ # ```ruby
15
+ # class Foo; end
16
+ # class Bar < Foo; end
17
+ #
18
+ # puts Bar # <-- right click on `Bar` and select "Show Type Hierarchy"
19
+ # ```
20
+ class TypeHierarchySupertypes < Request
21
+ extend T::Sig
22
+
23
+ include Support::Common
24
+
25
+ sig { params(index: RubyIndexer::Index, item: T::Hash[Symbol, T.untyped]).void }
26
+ def initialize(index, item)
27
+ super()
28
+
29
+ @index = index
30
+ @item = item
31
+ end
32
+
33
+ sig { override.returns(T.nilable(T::Array[Interface::TypeHierarchyItem])) }
34
+ def perform
35
+ name = @item[:name]
36
+ entries = @index[name]
37
+
38
+ parents = T.let(Set.new, T::Set[RubyIndexer::Entry::Namespace])
39
+ return unless entries&.any?
40
+
41
+ entries.each do |entry|
42
+ next unless entry.is_a?(RubyIndexer::Entry::Namespace)
43
+
44
+ if entry.is_a?(RubyIndexer::Entry::Class)
45
+ parent_class_name = entry.parent_class
46
+ if parent_class_name
47
+ resolved_parent_entries = @index.resolve(parent_class_name, entry.nesting)
48
+ resolved_parent_entries&.each do |entry|
49
+ next unless entry.is_a?(RubyIndexer::Entry::Class)
50
+
51
+ parents << entry
52
+ end
53
+ end
54
+ end
55
+
56
+ entry.mixin_operations.each do |mixin_operation|
57
+ next if mixin_operation.is_a?(RubyIndexer::Entry::Extend)
58
+
59
+ mixin_name = mixin_operation.module_name
60
+ resolved_mixin_entries = @index.resolve(mixin_name, entry.nesting)
61
+ next unless resolved_mixin_entries
62
+
63
+ resolved_mixin_entries.each do |mixin_entry|
64
+ next unless mixin_entry.is_a?(RubyIndexer::Entry::Module)
65
+
66
+ parents << mixin_entry
67
+ end
68
+ end
69
+ end
70
+
71
+ parents.map { |entry| hierarchy_item(entry) }
72
+ end
73
+
74
+ private
75
+
76
+ sig { params(entry: RubyIndexer::Entry).returns(Interface::TypeHierarchyItem) }
77
+ def hierarchy_item(entry)
78
+ range = range_from_location(entry.location)
79
+
80
+ Interface::TypeHierarchyItem.new(
81
+ name: entry.name,
82
+ kind: kind_for_entry(entry),
83
+ uri: URI::Generic.from_path(path: entry.file_path).to_s,
84
+ range: range,
85
+ selection_range: range,
86
+ detail: entry.file_name,
87
+ )
88
+ end
89
+ end
90
+ end
91
+ end
@@ -52,7 +52,7 @@ module RubyLsp
52
52
 
53
53
  Interface::WorkspaceSymbol.new(
54
54
  name: entry.name,
55
- container_name: T.must(container).join("::"),
55
+ container_name: container.join("::"),
56
56
  kind: kind,
57
57
  location: Interface::Location.new(
58
58
  uri: URI::Generic.from_path(path: file_path).to_s,
@@ -64,26 +64,6 @@ module RubyLsp
64
64
  )
65
65
  end
66
66
  end
67
-
68
- private
69
-
70
- sig { params(entry: RubyIndexer::Entry).returns(T.nilable(Integer)) }
71
- def kind_for_entry(entry)
72
- case entry
73
- when RubyIndexer::Entry::Class
74
- Constant::SymbolKind::CLASS
75
- when RubyIndexer::Entry::Module
76
- Constant::SymbolKind::NAMESPACE
77
- when RubyIndexer::Entry::Constant
78
- Constant::SymbolKind::CONSTANT
79
- when RubyIndexer::Entry::Method
80
- entry.name == "initialize" ? Constant::SymbolKind::CONSTRUCTOR : Constant::SymbolKind::METHOD
81
- when RubyIndexer::Entry::Accessor
82
- Constant::SymbolKind::PROPERTY
83
- when RubyIndexer::Entry::InstanceVariable
84
- Constant::SymbolKind::FIELD
85
- end
86
- end
87
67
  end
88
68
  end
89
69
  end
@@ -47,6 +47,8 @@ module RubyLsp
47
47
  autoload :ShowSyntaxTree, "ruby_lsp/requests/show_syntax_tree"
48
48
  autoload :WorkspaceSymbol, "ruby_lsp/requests/workspace_symbol"
49
49
  autoload :SignatureHelp, "ruby_lsp/requests/signature_help"
50
+ autoload :PrepareTypeHierarchy, "ruby_lsp/requests/prepare_type_hierarchy"
51
+ autoload :TypeHierarchySupertypes, "ruby_lsp/requests/type_hierarchy_supertypes"
50
52
 
51
53
  # :nodoc:
52
54
  module Support