ruby-lsp 0.27.0.beta2 → 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 (38) 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/global_state.rb +0 -5
  6. data/lib/ruby_lsp/internal.rb +2 -1
  7. data/lib/ruby_lsp/listeners/code_lens.rb +1 -1
  8. data/lib/ruby_lsp/listeners/completion.rb +246 -382
  9. data/lib/ruby_lsp/listeners/definition.rb +6 -9
  10. data/lib/ruby_lsp/listeners/hover.rb +11 -9
  11. data/lib/ruby_lsp/listeners/signature_help.rb +11 -12
  12. data/lib/ruby_lsp/listeners/test_discovery.rb +17 -1
  13. data/lib/ruby_lsp/listeners/test_style.rb +1 -1
  14. data/lib/ruby_lsp/requests/completion_resolve.rb +49 -29
  15. data/lib/ruby_lsp/requests/references.rb +21 -7
  16. data/lib/ruby_lsp/requests/rename.rb +1 -1
  17. data/lib/ruby_lsp/requests/support/common.rb +69 -68
  18. data/lib/ruby_lsp/ruby_document.rb +0 -73
  19. data/lib/ruby_lsp/rubydex/declaration.rb +128 -2
  20. data/lib/ruby_lsp/rubydex/definition.rb +16 -0
  21. data/lib/ruby_lsp/rubydex/signature.rb +107 -0
  22. data/lib/ruby_lsp/scripts/compose_bundle.rb +1 -1
  23. data/lib/ruby_lsp/server.rb +7 -162
  24. data/lib/ruby_lsp/test_helper.rb +0 -1
  25. data/lib/ruby_lsp/test_reporters/lsp_reporter.rb +1 -1
  26. data/lib/ruby_lsp/type_inferrer.rb +2 -2
  27. metadata +11 -14
  28. data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +0 -276
  29. data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +0 -1101
  30. data/lib/ruby_indexer/lib/ruby_indexer/enhancement.rb +0 -44
  31. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +0 -605
  32. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +0 -1077
  33. data/lib/ruby_indexer/lib/ruby_indexer/location.rb +0 -37
  34. data/lib/ruby_indexer/lib/ruby_indexer/prefix_tree.rb +0 -149
  35. data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +0 -294
  36. data/lib/ruby_indexer/lib/ruby_indexer/visibility_scope.rb +0 -32
  37. data/lib/ruby_indexer/ruby_indexer.rb +0 -19
  38. /data/lib/{ruby_indexer/lib/ruby_indexer → ruby_lsp}/uri.rb +0 -0
@@ -106,7 +106,7 @@ module RubyLsp
106
106
 
107
107
  #: (Prism::ConstantPathNode node) -> void
108
108
  def on_constant_path_node_enter(node)
109
- name = RubyIndexer::Index.constant_name(node)
109
+ name = constant_name(node)
110
110
  return if name.nil?
111
111
 
112
112
  handle_constant_definition(name)
@@ -114,7 +114,7 @@ module RubyLsp
114
114
 
115
115
  #: (Prism::ConstantReadNode node) -> void
116
116
  def on_constant_read_node_enter(node)
117
- name = RubyIndexer::Index.constant_name(node)
117
+ name = constant_name(node)
118
118
  return if name.nil?
119
119
 
120
120
  handle_constant_definition(name)
@@ -302,6 +302,9 @@ module RubyLsp
302
302
  return unless declaration
303
303
 
304
304
  Array(declaration).each do |decl|
305
+ next if decl.is_a?(Rubydex::Method) &&
306
+ !method_reachable_from_call_site?(decl, receiver_type, @graph, @node_context)
307
+
305
308
  decl.definitions.each do |definition|
306
309
  location = definition.location
307
310
  uri = URI(location.uri)
@@ -367,13 +370,7 @@ module RubyLsp
367
370
  def handle_constant_definition(value)
368
371
  declaration = @graph.resolve_constant(value, @node_context.nesting)
369
372
  return unless declaration
370
-
371
- # [RUBYDEX] TODO: temporarily commented out until we have the visibility API
372
- #
373
- # We should only allow jumping to the definition of private constants if the constant is defined in the same
374
- # namespace as the reference
375
- #
376
- # return if declaration.private? && declaration.name != "#{@node_context.fully_qualified_name}::#{value}"
373
+ return unless constant_reachable_from_call_site?(declaration, value, @node_context)
377
374
 
378
375
  declaration.definitions.each do |definition|
379
376
  # If the project has Sorbet, then we only want to handle go to definition for constants defined in gems, as an
@@ -15,7 +15,6 @@ module RubyLsp
15
15
  def initialize(response_builder, global_state, uri, node_context, dispatcher, sorbet_level, position) # rubocop:disable Metrics/ParameterLists
16
16
  @response_builder = response_builder
17
17
  @global_state = global_state
18
- @index = global_state.index #: RubyIndexer::Index
19
18
  @graph = global_state.graph #: Rubydex::Graph
20
19
  @type_inferrer = global_state.type_inferrer #: TypeInferrer
21
20
  @path = uri.to_standardized_path #: String?
@@ -117,7 +116,7 @@ module RubyLsp
117
116
  def on_constant_read_node_enter(node)
118
117
  return unless @sorbet_level.ignore?
119
118
 
120
- name = RubyIndexer::Index.constant_name(node)
119
+ name = constant_name(node)
121
120
  return if name.nil?
122
121
 
123
122
  generate_hover(name, node.location)
@@ -134,7 +133,7 @@ module RubyLsp
134
133
  def on_constant_path_node_enter(node)
135
134
  return unless @sorbet_level.ignore?
136
135
 
137
- name = RubyIndexer::Index.constant_name(node)
136
+ name = constant_name(node)
138
137
  return if name.nil?
139
138
 
140
139
  generate_hover(name, node.location)
@@ -457,20 +456,22 @@ module RubyLsp
457
456
  type = @type_inferrer.infer_receiver_type(@node_context)
458
457
  return unless type
459
458
 
460
- methods = @index.resolve_method(message, type.name, inherited_only: inherited_only)
461
- return unless methods
459
+ owner = @graph[type.name]
460
+ return unless owner.is_a?(Rubydex::Namespace)
462
461
 
463
- first_method = methods.first #: as !nil
462
+ method = owner.find_member("#{message}()", only_inherited: inherited_only)
463
+ return unless method.is_a?(Rubydex::Method)
464
+ return unless method_reachable_from_call_site?(method, type, @graph, @node_context)
464
465
 
465
- title = "#{message}#{first_method.decorated_parameters}"
466
- title << first_method.formatted_signatures
466
+ title = +"#{message}#{method.decorated_parameters}"
467
+ title << method.formatted_signatures
467
468
 
468
469
  if type.is_a?(TypeInferrer::GuessedType)
469
470
  title << "\n\nGuessed receiver: #{type.name}"
470
471
  @response_builder.push("[Learn more about guessed types](#{GUESSED_TYPES_URL})\n", category: :links)
471
472
  end
472
473
 
473
- categorized_markdown_from_index_entries(title, methods).each do |category, content|
474
+ categorized_markdown_from_definitions(title, method.definitions).each do |category, content|
474
475
  @response_builder.push(content, category: category)
475
476
  end
476
477
  end
@@ -513,6 +514,7 @@ module RubyLsp
513
514
  def generate_hover(name, location)
514
515
  declaration = @graph.resolve_constant(name, @node_context.nesting)
515
516
  return unless declaration
517
+ return unless constant_reachable_from_call_site?(declaration, name, @node_context)
516
518
 
517
519
  categorized_markdown_from_definitions(declaration.name, declaration.definitions).each do |category, content|
518
520
  @response_builder.push(content, category: category)
@@ -11,7 +11,7 @@ module RubyLsp
11
11
  @sorbet_level = sorbet_level
12
12
  @response_builder = response_builder
13
13
  @global_state = global_state
14
- @index = global_state.index #: RubyIndexer::Index
14
+ @graph = global_state.graph #: Rubydex::Graph
15
15
  @type_inferrer = global_state.type_inferrer #: TypeInferrer
16
16
  @node_context = node_context
17
17
  dispatcher.register(self, :on_call_node_enter)
@@ -27,18 +27,17 @@ module RubyLsp
27
27
  type = @type_inferrer.infer_receiver_type(@node_context)
28
28
  return unless type
29
29
 
30
- methods = @index.resolve_method(message, type.name)
31
- return unless methods
30
+ owner = @graph[type.name]
31
+ return unless owner.is_a?(Rubydex::Namespace)
32
32
 
33
- target_method = methods.first
34
- return unless target_method
33
+ target_method = owner.find_member("#{message}()")
34
+ return unless target_method.is_a?(Rubydex::Method)
35
35
 
36
36
  signatures = target_method.signatures
37
37
 
38
- # If the method doesn't have any parameters, there's no need to show signature help
38
+ # If the method doesn't have any signatures, there's nothing to show
39
39
  return if signatures.empty?
40
40
 
41
- name = target_method.name
42
41
  title = +""
43
42
 
44
43
  extra_links = if type.is_a?(TypeInferrer::GuessedType)
@@ -49,7 +48,7 @@ module RubyLsp
49
48
  active_signature, active_parameter = determine_active_signature_and_parameter(node, signatures)
50
49
 
51
50
  signature_help = Interface::SignatureHelp.new(
52
- signatures: generate_signatures(signatures, name, methods, title, extra_links),
51
+ signatures: generate_signatures(signatures, message, target_method, title, extra_links),
53
52
  active_signature: active_signature,
54
53
  active_parameter: active_parameter,
55
54
  )
@@ -58,7 +57,7 @@ module RubyLsp
58
57
 
59
58
  private
60
59
 
61
- #: (Prism::CallNode node, Array[RubyIndexer::Entry::Signature] signatures) -> [Integer, Integer]
60
+ #: (Prism::CallNode node, Array[Rubydex::Signature] signatures) -> [Integer, Integer]
62
61
  def determine_active_signature_and_parameter(node, signatures)
63
62
  arguments_node = node.arguments
64
63
  arguments = arguments_node&.arguments || []
@@ -86,15 +85,15 @@ module RubyLsp
86
85
  [active_sig_index, active_parameter]
87
86
  end
88
87
 
89
- #: (Array[RubyIndexer::Entry::Signature] signatures, String method_name, Array[RubyIndexer::Entry] methods, String title, String? extra_links) -> Array[Interface::SignatureInformation]
90
- def generate_signatures(signatures, method_name, methods, title, extra_links)
88
+ #: (Array[Rubydex::Signature] signatures, String method_name, Rubydex::Method method, String title, String? extra_links) -> Array[Interface::SignatureInformation]
89
+ def generate_signatures(signatures, method_name, method, title, extra_links)
91
90
  signatures.map do |signature|
92
91
  Interface::SignatureInformation.new(
93
92
  label: "#{method_name}(#{signature.format})",
94
93
  parameters: signature.parameters.map { |param| Interface::ParameterInformation.new(label: param.name) },
95
94
  documentation: Interface::MarkupContent.new(
96
95
  kind: "markdown",
97
- value: markdown_from_index_entries(title, methods, extra_links: extra_links),
96
+ value: markdown_from_definitions(title, method.definitions, extra_links: extra_links),
98
97
  ),
99
98
  )
100
99
  end
@@ -56,7 +56,23 @@ module RubyLsp
56
56
 
57
57
  #: (String? name) -> String
58
58
  def calc_fully_qualified_name(name)
59
- RubyIndexer::Index.actual_nesting(@nesting, name).join("::")
59
+ parts = name ? @nesting + [name] : @nesting
60
+ return "" if parts.empty?
61
+
62
+ last = parts.last #: as !nil
63
+ rest = parts[0...-1] #: as !nil
64
+
65
+ resolved = @graph.resolve_constant(last, rest)
66
+ return resolved.name if resolved
67
+
68
+ # Fallback for unresolved constants (e.g. dynamic references): preserve top-level reset semantics by
69
+ # truncating at the first `::`-prefixed part when scanning from the innermost out.
70
+ corrected = []
71
+ parts.reverse_each do |part|
72
+ corrected.prepend(part.delete_prefix("::"))
73
+ break if part.start_with?("::")
74
+ end
75
+ corrected.join("::")
60
76
  end
61
77
 
62
78
  #: (Prism::ClassNode node, String fully_qualified_name) -> Array[String]
@@ -220,7 +220,7 @@ module RubyLsp
220
220
  name = node.name.to_s
221
221
  return unless name.start_with?("test_")
222
222
 
223
- current_group_name = RubyIndexer::Index.actual_nesting(@nesting, nil).join("::")
223
+ current_group_name = calc_fully_qualified_name(nil)
224
224
  parent = @parent_stack.last
225
225
  return unless parent.is_a?(Requests::Support::TestItem)
226
226
 
@@ -16,6 +16,12 @@ module RubyLsp
16
16
  class CompletionResolve < Request
17
17
  include Requests::Support::Common
18
18
 
19
+ METHOD_KINDS = [
20
+ Constant::CompletionItemKind::METHOD,
21
+ Constant::CompletionItemKind::CONSTRUCTOR,
22
+ Constant::CompletionItemKind::FUNCTION,
23
+ ].freeze #: Array[Integer]
24
+
19
25
  # set a limit on the number of documentation entries returned, to avoid rendering performance issues
20
26
  # https://github.com/Shopify/ruby-lsp/pull/1798
21
27
  MAX_DOCUMENTATION_ENTRIES = 10
@@ -23,7 +29,6 @@ module RubyLsp
23
29
  #: (GlobalState global_state, Hash[Symbol, untyped] item) -> void
24
30
  def initialize(global_state, item)
25
31
  super()
26
- @index = global_state.index #: RubyIndexer::Index
27
32
  @graph = global_state.graph #: Rubydex::Graph
28
33
  @item = item
29
34
  end
@@ -32,6 +37,8 @@ module RubyLsp
32
37
  #: -> Hash[Symbol, untyped]
33
38
  def perform
34
39
  return @item if @item.dig(:data, :skip_resolve)
40
+ return keyword_resolve if @item.dig(:data, :keyword)
41
+ return @item if @item[:kind] == Constant::CompletionItemKind::FILE
35
42
 
36
43
  # Based on the spec https://microsoft.github.io/language-server-protocol/specification#textDocument_completion,
37
44
  # a completion resolve request must always return the original completion item without modifying ANY fields
@@ -40,48 +47,61 @@ module RubyLsp
40
47
  #
41
48
  # For example, forgetting to return the `insertText` included in the original item will make the editor use the
42
49
  # `label` for the text edit instead
43
- label = @item[:label].dup
44
- return keyword_resolve if @item.dig(:data, :keyword)
50
+ declaration = resolve_declaration
51
+ return @item unless declaration
45
52
 
46
- entries = @index[label] || []
47
-
48
- owner_name = @item.dig(:data, :owner_name)
49
-
50
- if owner_name
51
- entries = entries.select do |entry|
52
- (entry.is_a?(RubyIndexer::Entry::Member) || entry.is_a?(RubyIndexer::Entry::InstanceVariable) ||
53
- entry.is_a?(RubyIndexer::Entry::MethodAlias) || entry.is_a?(RubyIndexer::Entry::ClassVariable)) &&
54
- entry.owner&.name == owner_name
55
- end
56
- end
57
-
58
- first_entry = entries.first #: as !nil
53
+ guessed_type = @item.dig(:data, :guessed_type)
54
+ title = @item[:label].dup
59
55
 
60
- if first_entry.is_a?(RubyIndexer::Entry::Member)
61
- label = +"#{label}#{first_entry.decorated_parameters}"
62
- label << first_entry.formatted_signatures
56
+ if declaration.is_a?(Rubydex::Method)
57
+ title << declaration.decorated_parameters
58
+ title << declaration.formatted_signatures
63
59
  end
64
60
 
65
- guessed_type = @item.dig(:data, :guessed_type)
66
-
67
61
  extra_links = if guessed_type
68
- label << "\n\nGuessed receiver: #{guessed_type}"
62
+ title << "\n\nGuessed receiver: #{guessed_type}"
69
63
  "[Learn more about guessed types](#{GUESSED_TYPES_URL})"
70
64
  end
71
65
 
72
- unless @item[:kind] == Constant::CompletionItemKind::FILE
73
- @item[:documentation] = Interface::MarkupContent.new(
74
- kind: "markdown",
75
- value: markdown_from_index_entries(label, entries, MAX_DOCUMENTATION_ENTRIES, extra_links: extra_links),
76
- )
77
- end
66
+ @item[:documentation] = Interface::MarkupContent.new(
67
+ kind: "markdown",
68
+ value: markdown_from_definitions(
69
+ title,
70
+ declaration.definitions,
71
+ MAX_DOCUMENTATION_ENTRIES,
72
+ extra_links: extra_links,
73
+ ),
74
+ )
78
75
 
79
76
  @item
80
77
  end
81
78
 
82
79
  private
83
80
 
84
- #: () -> Hash[Symbol, untyped]
81
+ # Find the Rubydex declaration that matches the completion item. Constants are looked up by their fully qualified
82
+ # name (set when the completion was produced); members (methods, instance/class variables) are resolved by walking
83
+ # the owner namespace and its ancestors so that inherited and aliased members are surfaced correctly.
84
+ #: -> Rubydex::Declaration?
85
+ def resolve_declaration
86
+ data = @item[:data] || {}
87
+
88
+ if (fully_qualified_name = data[:fully_qualified_name])
89
+ @graph[fully_qualified_name]
90
+ elsif (owner_name = data[:owner_name])
91
+ owner = @graph[owner_name]
92
+ return unless owner.is_a?(Rubydex::Namespace)
93
+
94
+ member_name = if METHOD_KINDS.include?(@item[:kind])
95
+ "#{@item[:label]}()"
96
+ else
97
+ @item[:label]
98
+ end
99
+
100
+ owner.find_member(member_name)
101
+ end
102
+ end
103
+
104
+ #: -> Hash[Symbol, untyped]
85
105
  def keyword_resolve
86
106
  keyword = @graph.keyword(@item[:label])
87
107
 
@@ -145,7 +145,7 @@ module RubyLsp
145
145
  end
146
146
  return if declarations.empty?
147
147
 
148
- collect_references(method_references_for(message), declarations, include_declarations)
148
+ collect_references(method_references_for(message, declarations), declarations, include_declarations)
149
149
  end
150
150
 
151
151
  # Handles instance and class variable references. Resolves the receiver type from the node context to locate
@@ -187,14 +187,28 @@ module RubyLsp
187
187
  declaration = owner.find_member("#{method_name}()")
188
188
  return unless declaration
189
189
 
190
- collect_references(method_references_for(method_name), [declaration], include_declarations)
190
+ collect_references(method_references_for(method_name, [declaration]), [declaration], include_declarations)
191
191
  end
192
192
 
193
- # Method references in Rubydex are not yet resolved to specific declarations, so we filter from the global
194
- # method references by name
195
- #: (String) -> Array[Rubydex::MethodReference]
196
- def method_references_for(method_name)
197
- @graph.method_references.select { |reference| reference.name == method_name }
193
+ #: (String, Array[Rubydex::Declaration]) -> Array[Rubydex::MethodReference]
194
+ def method_references_for(method_name, declarations)
195
+ target_owner_names = declarations.map do |d|
196
+ d.owner #: as Rubydex::Namespace
197
+ .name
198
+ end
199
+
200
+ @graph.method_references.select do |reference|
201
+ next false unless reference.name == method_name
202
+
203
+ receiver = reference.receiver
204
+ next true if receiver.nil? || target_owner_names.empty?
205
+
206
+ if receiver.is_a?(Rubydex::Namespace)
207
+ receiver.ancestors.any? { |ancestor| target_owner_names.include?(ancestor.name) }
208
+ else
209
+ target_owner_names.include?(receiver.name)
210
+ end
211
+ end
198
212
  end
199
213
 
200
214
  #: (Enumerable[Rubydex::Reference] references, Array[Rubydex::Declaration] declarations, bool include_declarations) -> void
@@ -54,7 +54,7 @@ module RubyLsp
54
54
 
55
55
  target = target #: as Prism::ConstantReadNode | Prism::ConstantPathNode | Prism::ConstantPathTargetNode
56
56
 
57
- name = RubyIndexer::Index.constant_name(target)
57
+ name = constant_name(target)
58
58
  return unless name
59
59
 
60
60
  declaration = @graph.resolve_constant(name, node_context.nesting)
@@ -23,7 +23,7 @@ module RubyLsp
23
23
  )
24
24
  end
25
25
 
26
- #: ((Prism::Location | RubyIndexer::Location) location) -> Interface::Range
26
+ #: ((Prism::Location | Rubydex::Location) location) -> Interface::Range
27
27
  def range_from_location(location)
28
28
  Interface::Range.new(
29
29
  start: Interface::Position.new(
@@ -77,6 +77,44 @@ module RubyLsp
77
77
  receiver.nil? || receiver.is_a?(Prism::SelfNode)
78
78
  end
79
79
 
80
+ # Returns true when a constant declaration is reachable from the call site. Private constants are only
81
+ # reachable from within the namespace where they are defined.
82
+ #
83
+ #: (Rubydex::Declaration declaration, String value, NodeContext node_context) -> bool
84
+ def constant_reachable_from_call_site?(declaration, value, node_context)
85
+ return true unless declaration.is_a?(Rubydex::Visibility) && declaration.private?
86
+
87
+ declaration.name == "#{node_context.fully_qualified_name}::#{value}"
88
+ end
89
+
90
+ # Returns true when a method is reachable from the call site, considering visibility and receiver type.
91
+ # A method is reachable when:
92
+ # - there's no concrete receiver type to compare against
93
+ # - the call site is inside the receiver's own namespace (implicit/self call)
94
+ # - it is public
95
+ # - it is protected and the call site's class is in the same hierarchy as the method's defining class
96
+ #
97
+ # The `method_decl` is duck-typed to support `Rubydex::Method`, `RubyIndexer::Entry::Member` and
98
+ # `RubyIndexer::Entry::MethodAlias`. All respond to `public?`, `private?` and `owner` (an object with a
99
+ # `name` attribute).
100
+ #
101
+ #: (Rubydex::Method method_decl, TypeInferrer::Type? receiver_type, Rubydex::Graph graph, NodeContext node_context) -> bool
102
+ def method_reachable_from_call_site?(method_decl, receiver_type, graph, node_context)
103
+ return true unless receiver_type
104
+
105
+ caller_namespace = node_context.fully_qualified_name
106
+ return true if caller_namespace == receiver_type.name
107
+
108
+ return true if method_decl.public?
109
+ return false if method_decl.private?
110
+
111
+ caller_declaration = graph[caller_namespace]
112
+ return false unless caller_declaration.is_a?(Rubydex::Namespace)
113
+
114
+ owner_name = method_decl.owner.name
115
+ caller_declaration.ancestors.any? { |ancestor| ancestor.name == owner_name }
116
+ end
117
+
80
118
  #: (String, Enumerable[Rubydex::Definition], ?Integer?) -> Hash[Symbol, String]
81
119
  def categorized_markdown_from_definitions(title, definitions, max_entries = nil)
82
120
  markdown_title = "```ruby\n#{title}\n```"
@@ -87,17 +125,22 @@ module RubyLsp
87
125
  # For Markdown links, we need 1 based display locations
88
126
  loc = definition.location.to_display
89
127
  uri = URI(loc.uri)
90
- file_name = if uri.scheme == "untitled"
128
+
129
+ file_name = case uri.scheme
130
+ when "file"
131
+ full_path = uri.full_path #: as !nil
132
+ File.basename(full_path)
133
+ when "untitled"
91
134
  uri.opaque #: as !nil
92
- else
93
- File.basename(
94
- uri.full_path, #: as !nil
95
- )
96
135
  end
97
136
 
98
- # The format for VS Code file URIs is `file:///path/to/file.rb#Lstart_line,start_column-end_line,end_column`
99
- string_uri = "#{loc.uri}#L#{loc.start_line},#{loc.start_column}-#{loc.end_line},#{loc.end_column}"
100
- file_links << "[#{file_name}](#{string_uri})"
137
+ # Omit the link for magic schemes like rubydex:built-in
138
+ if file_name
139
+ # The format for VS Code file URIs is `file:///path/to/file.rb#Lstart_line,start_column-end_line,end_column`
140
+ string_uri = "#{loc.uri}#L#{loc.start_line},#{loc.start_column}-#{loc.end_line},#{loc.end_column}"
141
+ file_links << "[#{file_name}](#{string_uri})"
142
+ end
143
+
101
144
  content << "\n\n#{definition.comments.map { |comment| comment.string.delete_prefix("# ") }.join("\n")}" unless definition.comments.empty?
102
145
  end
103
146
 
@@ -117,42 +160,9 @@ module RubyLsp
117
160
  }
118
161
  end
119
162
 
120
- #: (String title, (Array[RubyIndexer::Entry] | RubyIndexer::Entry) entries, ?Integer? max_entries) -> Hash[Symbol, String]
121
- def categorized_markdown_from_index_entries(title, entries, max_entries = nil)
122
- markdown_title = "```ruby\n#{title}\n```"
123
- definitions = []
124
- content = +""
125
- entries = Array(entries)
126
- entries_to_format = max_entries ? entries.take(max_entries) : entries
127
- entries_to_format.each do |entry|
128
- loc = entry.location
129
-
130
- # We always handle locations as zero based. However, for file links in Markdown we need them to be one
131
- # based, which is why instead of the usual subtraction of 1 to line numbers, we are actually adding 1 to
132
- # columns. The format for VS Code file URIs is
133
- # `file:///path/to/file.rb#Lstart_line,start_column-end_line,end_column`
134
- uri = "#{entry.uri}#L#{loc.start_line},#{loc.start_column + 1}-#{loc.end_line},#{loc.end_column + 1}"
135
- definitions << "[#{entry.file_name}](#{uri})"
136
- content << "\n\n#{entry.comments}" unless entry.comments.empty?
137
- end
138
-
139
- additional_entries_text = if max_entries && entries.length > max_entries
140
- additional = entries.length - max_entries
141
- " | #{additional} other#{additional > 1 ? "s" : ""}"
142
- else
143
- ""
144
- end
145
-
146
- {
147
- title: markdown_title,
148
- links: "**Definitions**: #{definitions.join(" | ")}#{additional_entries_text}",
149
- documentation: content,
150
- }
151
- end
152
-
153
- #: (String title, (Array[RubyIndexer::Entry] | RubyIndexer::Entry) entries, ?Integer? max_entries, ?extra_links: String?) -> String
154
- def markdown_from_index_entries(title, entries, max_entries = nil, extra_links: nil)
155
- categorized_markdown = categorized_markdown_from_index_entries(title, entries, max_entries)
163
+ #: (String title, Enumerable[Rubydex::Definition] definitions, ?Integer? max_entries, ?extra_links: String?) -> String
164
+ def markdown_from_definitions(title, definitions, max_entries = nil, extra_links: nil)
165
+ categorized_markdown = categorized_markdown_from_definitions(title, definitions, max_entries)
156
166
 
157
167
  markdown = +(categorized_markdown[:title] || "")
158
168
  markdown << "\n\n#{extra_links}" if extra_links
@@ -168,7 +178,20 @@ module RubyLsp
168
178
 
169
179
  #: ((Prism::ConstantPathNode | Prism::ConstantReadNode | Prism::ConstantPathTargetNode | Prism::CallNode | Prism::MissingNode) node) -> String?
170
180
  def constant_name(node)
171
- RubyIndexer::Index.constant_name(node)
181
+ Common.constant_name(node)
182
+ end
183
+
184
+ class << self
185
+ #: ((Prism::ConstantPathNode | Prism::ConstantReadNode | Prism::ConstantPathTargetNode | Prism::CallNode | Prism::MissingNode) node) -> String?
186
+ def constant_name(node)
187
+ case node
188
+ when Prism::ConstantPathNode, Prism::ConstantReadNode, Prism::ConstantPathTargetNode
189
+ node.full_name
190
+ end
191
+ rescue Prism::ConstantPathNode::DynamicPartsInConstantPathError,
192
+ Prism::ConstantPathNode::MissingNodesInConstantPathError
193
+ nil
194
+ end
172
195
  end
173
196
 
174
197
  #: ((Prism::ModuleNode | Prism::ClassNode) node) -> String?
@@ -192,28 +215,6 @@ module RubyLsp
192
215
  current = current.parent
193
216
  end
194
217
  end
195
-
196
- #: (RubyIndexer::Entry entry) -> Integer
197
- def kind_for_entry(entry)
198
- case entry
199
- when RubyIndexer::Entry::Class
200
- Constant::SymbolKind::CLASS
201
- when RubyIndexer::Entry::Module
202
- Constant::SymbolKind::NAMESPACE
203
- when RubyIndexer::Entry::Constant, RubyIndexer::Entry::UnresolvedConstantAlias, RubyIndexer::Entry::ConstantAlias
204
- Constant::SymbolKind::CONSTANT
205
- when RubyIndexer::Entry::Method, RubyIndexer::Entry::UnresolvedMethodAlias, RubyIndexer::Entry::MethodAlias
206
- entry.name == "initialize" ? Constant::SymbolKind::CONSTRUCTOR : Constant::SymbolKind::METHOD
207
- when RubyIndexer::Entry::Accessor
208
- Constant::SymbolKind::PROPERTY
209
- when RubyIndexer::Entry::InstanceVariable, RubyIndexer::Entry::ClassVariable
210
- Constant::SymbolKind::FIELD
211
- when RubyIndexer::Entry::GlobalVariable
212
- Constant::SymbolKind::VARIABLE
213
- else
214
- Constant::SymbolKind::NULL
215
- end
216
- end
217
218
  end
218
219
  end
219
220
  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