ruby-lsp 0.22.1 → 0.23.10

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 (77) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/VERSION +1 -1
  4. data/exe/ruby-lsp +12 -11
  5. data/exe/ruby-lsp-check +5 -5
  6. data/exe/ruby-lsp-launcher +41 -15
  7. data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +26 -20
  8. data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +191 -100
  9. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +60 -30
  10. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +174 -61
  11. data/lib/ruby_indexer/lib/ruby_indexer/location.rb +12 -0
  12. data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +16 -14
  13. data/lib/ruby_indexer/lib/ruby_indexer/reference_finder.rb +82 -61
  14. data/lib/{core_ext → ruby_indexer/lib/ruby_indexer}/uri.rb +29 -3
  15. data/lib/ruby_indexer/lib/ruby_indexer/visibility_scope.rb +36 -0
  16. data/lib/ruby_indexer/ruby_indexer.rb +2 -1
  17. data/lib/ruby_indexer/test/class_variables_test.rb +140 -0
  18. data/lib/ruby_indexer/test/classes_and_modules_test.rb +30 -6
  19. data/lib/ruby_indexer/test/configuration_test.rb +116 -51
  20. data/lib/ruby_indexer/test/enhancements_test.rb +2 -2
  21. data/lib/ruby_indexer/test/index_test.rb +143 -44
  22. data/lib/ruby_indexer/test/instance_variables_test.rb +20 -0
  23. data/lib/ruby_indexer/test/method_test.rb +86 -8
  24. data/lib/ruby_indexer/test/rbs_indexer_test.rb +1 -1
  25. data/lib/ruby_indexer/test/reference_finder_test.rb +90 -2
  26. data/lib/ruby_indexer/test/test_case.rb +2 -2
  27. data/lib/ruby_indexer/test/uri_test.rb +72 -0
  28. data/lib/ruby_lsp/addon.rb +9 -0
  29. data/lib/ruby_lsp/base_server.rb +17 -18
  30. data/lib/ruby_lsp/client_capabilities.rb +7 -1
  31. data/lib/ruby_lsp/document.rb +72 -10
  32. data/lib/ruby_lsp/erb_document.rb +5 -3
  33. data/lib/ruby_lsp/global_state.rb +42 -3
  34. data/lib/ruby_lsp/internal.rb +3 -1
  35. data/lib/ruby_lsp/listeners/code_lens.rb +9 -5
  36. data/lib/ruby_lsp/listeners/completion.rb +78 -6
  37. data/lib/ruby_lsp/listeners/definition.rb +80 -19
  38. data/lib/ruby_lsp/listeners/document_highlight.rb +3 -2
  39. data/lib/ruby_lsp/listeners/document_link.rb +21 -3
  40. data/lib/ruby_lsp/listeners/document_symbol.rb +12 -1
  41. data/lib/ruby_lsp/listeners/folding_ranges.rb +1 -1
  42. data/lib/ruby_lsp/listeners/hover.rb +59 -2
  43. data/lib/ruby_lsp/load_sorbet.rb +3 -3
  44. data/lib/ruby_lsp/rbs_document.rb +2 -2
  45. data/lib/ruby_lsp/requests/code_action_resolve.rb +90 -6
  46. data/lib/ruby_lsp/requests/code_actions.rb +57 -1
  47. data/lib/ruby_lsp/requests/completion.rb +8 -1
  48. data/lib/ruby_lsp/requests/completion_resolve.rb +2 -1
  49. data/lib/ruby_lsp/requests/definition.rb +7 -1
  50. data/lib/ruby_lsp/requests/diagnostics.rb +1 -1
  51. data/lib/ruby_lsp/requests/document_highlight.rb +1 -1
  52. data/lib/ruby_lsp/requests/folding_ranges.rb +2 -6
  53. data/lib/ruby_lsp/requests/formatting.rb +2 -6
  54. data/lib/ruby_lsp/requests/hover.rb +1 -1
  55. data/lib/ruby_lsp/requests/on_type_formatting.rb +2 -2
  56. data/lib/ruby_lsp/requests/prepare_rename.rb +51 -0
  57. data/lib/ruby_lsp/requests/prepare_type_hierarchy.rb +1 -1
  58. data/lib/ruby_lsp/requests/references.rb +29 -2
  59. data/lib/ruby_lsp/requests/rename.rb +17 -7
  60. data/lib/ruby_lsp/requests/semantic_highlighting.rb +1 -1
  61. data/lib/ruby_lsp/requests/show_syntax_tree.rb +1 -4
  62. data/lib/ruby_lsp/requests/signature_help.rb +1 -1
  63. data/lib/ruby_lsp/requests/support/common.rb +2 -9
  64. data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +3 -3
  65. data/lib/ruby_lsp/requests/support/rubocop_runner.rb +13 -13
  66. data/lib/ruby_lsp/requests/type_hierarchy_supertypes.rb +1 -1
  67. data/lib/ruby_lsp/requests/workspace_symbol.rb +4 -3
  68. data/lib/ruby_lsp/ruby_document.rb +80 -6
  69. data/lib/ruby_lsp/scripts/compose_bundle.rb +1 -1
  70. data/lib/ruby_lsp/server.rb +205 -61
  71. data/lib/ruby_lsp/setup_bundler.rb +50 -43
  72. data/lib/ruby_lsp/store.rb +7 -7
  73. data/lib/ruby_lsp/test_helper.rb +45 -11
  74. data/lib/ruby_lsp/type_inferrer.rb +60 -31
  75. data/lib/ruby_lsp/utils.rb +63 -3
  76. metadata +8 -8
  77. data/lib/ruby_indexer/lib/ruby_indexer/indexable_path.rb +0 -29
@@ -43,7 +43,7 @@ module RubyIndexer
43
43
  handle_class_or_module_declaration(declaration, pathname)
44
44
  when RBS::AST::Declarations::Constant
45
45
  namespace_nesting = declaration.name.namespace.path.map(&:to_s)
46
- handle_constant(declaration, namespace_nesting, pathname.to_s)
46
+ handle_constant(declaration, namespace_nesting, URI::Generic.from_path(path: pathname.to_s))
47
47
  when RBS::AST::Declarations::Global
48
48
  handle_global_variable(declaration, pathname)
49
49
  else # rubocop:disable Style/EmptyElse
@@ -56,23 +56,25 @@ module RubyIndexer
56
56
  end
57
57
  def handle_class_or_module_declaration(declaration, pathname)
58
58
  nesting = [declaration.name.name.to_s]
59
- file_path = pathname.to_s
59
+ uri = URI::Generic.from_path(path: pathname.to_s)
60
60
  location = to_ruby_indexer_location(declaration.location)
61
61
  comments = comments_to_string(declaration)
62
62
  entry = if declaration.is_a?(RBS::AST::Declarations::Class)
63
63
  parent_class = declaration.super_class&.name&.name&.to_s
64
- Entry::Class.new(nesting, file_path, location, location, comments, parent_class)
64
+ Entry::Class.new(nesting, uri, location, location, comments, parent_class)
65
65
  else
66
- Entry::Module.new(nesting, file_path, location, location, comments)
66
+ Entry::Module.new(nesting, uri, location, location, comments)
67
67
  end
68
+
68
69
  add_declaration_mixins_to_entry(declaration, entry)
69
70
  @index.add(entry)
71
+
70
72
  declaration.members.each do |member|
71
73
  case member
72
74
  when RBS::AST::Members::MethodDefinition
73
75
  handle_method(member, entry)
74
76
  when RBS::AST::Declarations::Constant
75
- handle_constant(member, nesting, file_path)
77
+ handle_constant(member, nesting, uri)
76
78
  when RBS::AST::Members::Alias
77
79
  # In RBS, an alias means that two methods have the same signature.
78
80
  # It does not mean the same thing as a Ruby alias.
@@ -115,7 +117,7 @@ module RubyIndexer
115
117
  sig { params(member: RBS::AST::Members::MethodDefinition, owner: Entry::Namespace).void }
116
118
  def handle_method(member, owner)
117
119
  name = member.name.name
118
- file_path = member.location.buffer.name
120
+ uri = URI::Generic.from_path(path: member.location.buffer.name)
119
121
  location = to_ruby_indexer_location(member.location)
120
122
  comments = comments_to_string(member)
121
123
 
@@ -132,7 +134,7 @@ module RubyIndexer
132
134
  signatures = signatures(member)
133
135
  @index.add(Entry::Method.new(
134
136
  name,
135
- file_path,
137
+ uri,
136
138
  location,
137
139
  location,
138
140
  comments,
@@ -260,12 +262,12 @@ module RubyIndexer
260
262
  # Complex::I = ... # Complex::I is a top-level constant
261
263
  #
262
264
  # And we need to handle their nesting differently.
263
- sig { params(declaration: RBS::AST::Declarations::Constant, nesting: T::Array[String], file_path: String).void }
264
- def handle_constant(declaration, nesting, file_path)
265
+ sig { params(declaration: RBS::AST::Declarations::Constant, nesting: T::Array[String], uri: URI::Generic).void }
266
+ def handle_constant(declaration, nesting, uri)
265
267
  fully_qualified_name = [*nesting, declaration.name.name.to_s].join("::")
266
268
  @index.add(Entry::Constant.new(
267
269
  fully_qualified_name,
268
- file_path,
270
+ uri,
269
271
  to_ruby_indexer_location(declaration.location),
270
272
  comments_to_string(declaration),
271
273
  ))
@@ -274,13 +276,13 @@ module RubyIndexer
274
276
  sig { params(declaration: RBS::AST::Declarations::Global, pathname: Pathname).void }
275
277
  def handle_global_variable(declaration, pathname)
276
278
  name = declaration.name.to_s
277
- file_path = pathname.to_s
279
+ uri = URI::Generic.from_path(path: pathname.to_s)
278
280
  location = to_ruby_indexer_location(declaration.location)
279
281
  comments = comments_to_string(declaration)
280
282
 
281
283
  @index.add(Entry::GlobalVariable.new(
282
284
  name,
283
- file_path,
285
+ uri,
284
286
  location,
285
287
  comments,
286
288
  ))
@@ -288,14 +290,14 @@ module RubyIndexer
288
290
 
289
291
  sig { params(member: RBS::AST::Members::Alias, owner_entry: Entry::Namespace).void }
290
292
  def handle_signature_alias(member, owner_entry)
291
- file_path = member.location.buffer.name
293
+ uri = URI::Generic.from_path(path: member.location.buffer.name)
292
294
  comments = comments_to_string(member)
293
295
 
294
296
  entry = Entry::UnresolvedMethodAlias.new(
295
297
  member.new_name.to_s,
296
298
  member.old_name.to_s,
297
299
  owner_entry,
298
- file_path,
300
+ uri,
299
301
  to_ruby_indexer_location(member.location),
300
302
  comments,
301
303
  )
@@ -37,6 +37,19 @@ module RubyIndexer
37
37
  end
38
38
  end
39
39
 
40
+ class InstanceVariableTarget < Target
41
+ extend T::Sig
42
+
43
+ sig { returns(String) }
44
+ attr_reader :name
45
+
46
+ sig { params(name: String).void }
47
+ def initialize(name)
48
+ super()
49
+ @name = name
50
+ end
51
+ end
52
+
40
53
  class Reference
41
54
  extend T::Sig
42
55
 
@@ -62,12 +75,14 @@ module RubyIndexer
62
75
  target: Target,
63
76
  index: RubyIndexer::Index,
64
77
  dispatcher: Prism::Dispatcher,
78
+ uri: URI::Generic,
65
79
  include_declarations: T::Boolean,
66
80
  ).void
67
81
  end
68
- def initialize(target, index, dispatcher, include_declarations: true)
82
+ def initialize(target, index, dispatcher, uri, include_declarations: true)
69
83
  @target = target
70
84
  @index = index
85
+ @uri = uri
71
86
  @include_declarations = include_declarations
72
87
  @stack = T.let([], T::Array[String])
73
88
  @references = T.let([], T::Array[Reference])
@@ -94,6 +109,12 @@ module RubyIndexer
94
109
  :on_constant_or_write_node_enter,
95
110
  :on_constant_and_write_node_enter,
96
111
  :on_constant_operator_write_node_enter,
112
+ :on_instance_variable_read_node_enter,
113
+ :on_instance_variable_write_node_enter,
114
+ :on_instance_variable_and_write_node_enter,
115
+ :on_instance_variable_operator_write_node_enter,
116
+ :on_instance_variable_or_write_node_enter,
117
+ :on_instance_variable_target_node_enter,
97
118
  :on_call_node_enter,
98
119
  )
99
120
  end
@@ -107,15 +128,7 @@ module RubyIndexer
107
128
 
108
129
  sig { params(node: Prism::ClassNode).void }
109
130
  def on_class_node_enter(node)
110
- constant_path = node.constant_path
111
- name = constant_path.slice
112
- nesting = actual_nesting(name)
113
-
114
- if @target.is_a?(ConstTarget) && nesting.join("::") == @target.fully_qualified_name
115
- @references << Reference.new(name, constant_path.location, declaration: true)
116
- end
117
-
118
- @stack << name
131
+ @stack << node.constant_path.slice
119
132
  end
120
133
 
121
134
  sig { params(node: Prism::ClassNode).void }
@@ -125,15 +138,7 @@ module RubyIndexer
125
138
 
126
139
  sig { params(node: Prism::ModuleNode).void }
127
140
  def on_module_node_enter(node)
128
- constant_path = node.constant_path
129
- name = constant_path.slice
130
- nesting = actual_nesting(name)
131
-
132
- if @target.is_a?(ConstTarget) && nesting.join("::") == @target.fully_qualified_name
133
- @references << Reference.new(name, constant_path.location, declaration: true)
134
- end
135
-
136
- @stack << name
141
+ @stack << node.constant_path.slice
137
142
  end
138
143
 
139
144
  sig { params(node: Prism::ModuleNode).void }
@@ -156,7 +161,7 @@ module RubyIndexer
156
161
 
157
162
  sig { params(node: Prism::ConstantPathNode).void }
158
163
  def on_constant_path_node_enter(node)
159
- name = constant_name(node)
164
+ name = Index.constant_name(node)
160
165
  return unless name
161
166
 
162
167
  collect_constant_references(name, node.location)
@@ -164,7 +169,7 @@ module RubyIndexer
164
169
 
165
170
  sig { params(node: Prism::ConstantReadNode).void }
166
171
  def on_constant_read_node_enter(node)
167
- name = constant_name(node)
172
+ name = Index.constant_name(node)
168
173
  return unless name
169
174
 
170
175
  collect_constant_references(name, node.location)
@@ -185,7 +190,7 @@ module RubyIndexer
185
190
  target = node.target
186
191
  return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode)
187
192
 
188
- name = constant_name(target)
193
+ name = Index.constant_name(target)
189
194
  return unless name
190
195
 
191
196
  collect_constant_references(name, target.location)
@@ -196,7 +201,7 @@ module RubyIndexer
196
201
  target = node.target
197
202
  return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode)
198
203
 
199
- name = constant_name(target)
204
+ name = Index.constant_name(target)
200
205
  return unless name
201
206
 
202
207
  collect_constant_references(name, target.location)
@@ -207,7 +212,7 @@ module RubyIndexer
207
212
  target = node.target
208
213
  return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode)
209
214
 
210
- name = constant_name(target)
215
+ name = Index.constant_name(target)
211
216
  return unless name
212
217
 
213
218
  collect_constant_references(name, target.location)
@@ -218,7 +223,7 @@ module RubyIndexer
218
223
  target = node.target
219
224
  return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode)
220
225
 
221
- name = constant_name(target)
226
+ name = Index.constant_name(target)
222
227
  return unless name
223
228
 
224
229
  collect_constant_references(name, target.location)
@@ -262,6 +267,36 @@ module RubyIndexer
262
267
  end
263
268
  end
264
269
 
270
+ sig { params(node: Prism::InstanceVariableReadNode).void }
271
+ def on_instance_variable_read_node_enter(node)
272
+ collect_instance_variable_references(node.name.to_s, node.location, false)
273
+ end
274
+
275
+ sig { params(node: Prism::InstanceVariableWriteNode).void }
276
+ def on_instance_variable_write_node_enter(node)
277
+ collect_instance_variable_references(node.name.to_s, node.name_loc, true)
278
+ end
279
+
280
+ sig { params(node: Prism::InstanceVariableAndWriteNode).void }
281
+ def on_instance_variable_and_write_node_enter(node)
282
+ collect_instance_variable_references(node.name.to_s, node.name_loc, true)
283
+ end
284
+
285
+ sig { params(node: Prism::InstanceVariableOperatorWriteNode).void }
286
+ def on_instance_variable_operator_write_node_enter(node)
287
+ collect_instance_variable_references(node.name.to_s, node.name_loc, true)
288
+ end
289
+
290
+ sig { params(node: Prism::InstanceVariableOrWriteNode).void }
291
+ def on_instance_variable_or_write_node_enter(node)
292
+ collect_instance_variable_references(node.name.to_s, node.name_loc, true)
293
+ end
294
+
295
+ sig { params(node: Prism::InstanceVariableTargetNode).void }
296
+ def on_instance_variable_target_node_enter(node)
297
+ collect_instance_variable_references(node.name.to_s, node.location, true)
298
+ end
299
+
265
300
  sig { params(node: Prism::CallNode).void }
266
301
  def on_call_node_enter(node)
267
302
  if @target.is_a?(MethodTarget) && (name = node.name.to_s) == @target.method_name
@@ -271,20 +306,6 @@ module RubyIndexer
271
306
 
272
307
  private
273
308
 
274
- sig { params(name: String).returns(T::Array[String]) }
275
- def actual_nesting(name)
276
- nesting = @stack + [name]
277
- corrected_nesting = []
278
-
279
- nesting.reverse_each do |name|
280
- corrected_nesting.prepend(name.delete_prefix("::"))
281
-
282
- break if name.start_with?("::")
283
- end
284
-
285
- corrected_nesting
286
- end
287
-
288
309
  sig { params(name: String, location: Prism::Location).void }
289
310
  def collect_constant_references(name, location)
290
311
  return unless @target.is_a?(ConstTarget)
@@ -292,33 +313,33 @@ module RubyIndexer
292
313
  entries = @index.resolve(name, @stack)
293
314
  return unless entries
294
315
 
295
- previous_reference = @references.last
296
-
297
- entries.each do |entry|
298
- next unless entry.name == @target.fully_qualified_name
316
+ # Filter down to all constant declarations that match the expected name and type
317
+ matching_entries = entries.select do |e|
318
+ [
319
+ Entry::Namespace,
320
+ Entry::Constant,
321
+ Entry::ConstantAlias,
322
+ Entry::UnresolvedConstantAlias,
323
+ ].any? { |klass| e.is_a?(klass) } &&
324
+ e.name == @target.fully_qualified_name
325
+ end
299
326
 
300
- # When processing a class/module declaration, we eagerly handle the constant reference. To avoid duplicates,
301
- # when we find the constant node defining the namespace, then we have to check if it wasn't already added
302
- next if previous_reference&.location == location
327
+ return if matching_entries.empty?
303
328
 
304
- @references << Reference.new(name, location, declaration: false)
329
+ # If any of the matching entries have the same location as the constant and were
330
+ # defined in the same file, then it is that constant's declaration
331
+ declaration = matching_entries.any? do |e|
332
+ e.uri == @uri && e.name_location == location
305
333
  end
334
+
335
+ @references << Reference.new(name, location, declaration: declaration)
306
336
  end
307
337
 
308
- sig do
309
- params(
310
- node: T.any(
311
- Prism::ConstantPathNode,
312
- Prism::ConstantReadNode,
313
- Prism::ConstantPathTargetNode,
314
- ),
315
- ).returns(T.nilable(String))
316
- end
317
- def constant_name(node)
318
- node.full_name
319
- rescue Prism::ConstantPathNode::DynamicPartsInConstantPathError,
320
- Prism::ConstantPathNode::MissingNodesInConstantPathError
321
- nil
338
+ sig { params(name: String, location: Prism::Location, declaration: T::Boolean).void }
339
+ def collect_instance_variable_references(name, location, declaration)
340
+ return unless @target.is_a?(InstanceVariableTarget) && name == @target.name
341
+
342
+ @references << Reference.new(name, location, declaration: declaration)
322
343
  end
323
344
  end
324
345
  end
@@ -13,8 +13,15 @@ module URI
13
13
  class << self
14
14
  extend T::Sig
15
15
 
16
- sig { params(path: String, fragment: T.nilable(String), scheme: String).returns(URI::Generic) }
17
- def from_path(path:, fragment: nil, scheme: "file")
16
+ sig do
17
+ params(
18
+ path: String,
19
+ fragment: T.nilable(String),
20
+ scheme: String,
21
+ load_path_entry: T.nilable(String),
22
+ ).returns(URI::Generic)
23
+ end
24
+ def from_path(path:, fragment: nil, scheme: "file", load_path_entry: nil)
18
25
  # On Windows, if the path begins with the disk name, we need to add a leading slash to make it a valid URI
19
26
  escaped_path = if /^[A-Z]:/i.match?(path)
20
27
  PARSER.escape("/#{path}")
@@ -25,10 +32,27 @@ module URI
25
32
  PARSER.escape(path)
26
33
  end
27
34
 
28
- build(scheme: scheme, path: escaped_path, fragment: fragment)
35
+ uri = build(scheme: scheme, path: escaped_path, fragment: fragment)
36
+
37
+ if load_path_entry
38
+ uri.require_path = path.delete_prefix("#{load_path_entry}/").delete_suffix(".rb")
39
+ end
40
+
41
+ uri
29
42
  end
30
43
  end
31
44
 
45
+ sig { returns(T.nilable(String)) }
46
+ attr_accessor :require_path
47
+
48
+ sig { params(load_path_entry: String).void }
49
+ def add_require_path_from_load_entry(load_path_entry)
50
+ path = to_standardized_path
51
+ return unless path
52
+
53
+ self.require_path = path.delete_prefix("#{load_path_entry}/").delete_suffix(".rb")
54
+ end
55
+
32
56
  sig { returns(T.nilable(String)) }
33
57
  def to_standardized_path
34
58
  parsed_path = path
@@ -44,5 +68,7 @@ module URI
44
68
  unescaped_path
45
69
  end
46
70
  end
71
+
72
+ alias_method :full_path, :to_standardized_path
47
73
  end
48
74
  end
@@ -0,0 +1,36 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyIndexer
5
+ # Represents the visibility scope in a Ruby namespace. This keeps track of whether methods are in a public, private or
6
+ # protected section, and whether they are module functions.
7
+ class VisibilityScope
8
+ extend T::Sig
9
+
10
+ class << self
11
+ extend T::Sig
12
+
13
+ sig { returns(T.attached_class) }
14
+ def module_function_scope
15
+ new(module_func: true, visibility: Entry::Visibility::PRIVATE)
16
+ end
17
+
18
+ sig { returns(T.attached_class) }
19
+ def public_scope
20
+ new
21
+ end
22
+ end
23
+
24
+ sig { returns(Entry::Visibility) }
25
+ attr_reader :visibility
26
+
27
+ sig { returns(T::Boolean) }
28
+ attr_reader :module_func
29
+
30
+ sig { params(visibility: Entry::Visibility, module_func: T::Boolean).void }
31
+ def initialize(visibility: Entry::Visibility::PUBLIC, module_func: false)
32
+ @visibility = visibility
33
+ @module_func = module_func
34
+ end
35
+ end
36
+ end
@@ -4,7 +4,8 @@
4
4
  require "yaml"
5
5
  require "did_you_mean"
6
6
 
7
- require "ruby_indexer/lib/ruby_indexer/indexable_path"
7
+ require "ruby_indexer/lib/ruby_indexer/uri"
8
+ require "ruby_indexer/lib/ruby_indexer/visibility_scope"
8
9
  require "ruby_indexer/lib/ruby_indexer/declaration_listener"
9
10
  require "ruby_indexer/lib/ruby_indexer/reference_finder"
10
11
  require "ruby_indexer/lib/ruby_indexer/enhancement"
@@ -0,0 +1,140 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "test_case"
5
+
6
+ module RubyIndexer
7
+ class ClassVariableTest < TestCase
8
+ def test_class_variable_and_write
9
+ index(<<~RUBY)
10
+ class Foo
11
+ @@bar &&= 1
12
+ end
13
+ RUBY
14
+
15
+ assert_entry("@@bar", Entry::ClassVariable, "/fake/path/foo.rb:1-2:1-7")
16
+
17
+ entry = T.must(@index["@@bar"]&.first)
18
+ owner = T.must(entry.owner)
19
+ assert_instance_of(Entry::Class, owner)
20
+ assert_equal("Foo", owner.name)
21
+ end
22
+
23
+ def test_class_variable_operator_write
24
+ index(<<~RUBY)
25
+ class Foo
26
+ @@bar += 1
27
+ end
28
+ RUBY
29
+
30
+ assert_entry("@@bar", Entry::ClassVariable, "/fake/path/foo.rb:1-2:1-7")
31
+ end
32
+
33
+ def test_class_variable_or_write
34
+ index(<<~RUBY)
35
+ class Foo
36
+ @@bar ||= 1
37
+ end
38
+ RUBY
39
+
40
+ assert_entry("@@bar", Entry::ClassVariable, "/fake/path/foo.rb:1-2:1-7")
41
+ end
42
+
43
+ def test_class_variable_target_node
44
+ index(<<~RUBY)
45
+ class Foo
46
+ @@foo, @@bar = 1
47
+ end
48
+ RUBY
49
+
50
+ assert_entry("@@foo", Entry::ClassVariable, "/fake/path/foo.rb:1-2:1-7")
51
+ assert_entry("@@bar", Entry::ClassVariable, "/fake/path/foo.rb:1-9:1-14")
52
+
53
+ entry = T.must(@index["@@foo"]&.first)
54
+ owner = T.must(entry.owner)
55
+ assert_instance_of(Entry::Class, owner)
56
+ assert_equal("Foo", owner.name)
57
+
58
+ entry = T.must(@index["@@bar"]&.first)
59
+ owner = T.must(entry.owner)
60
+ assert_instance_of(Entry::Class, owner)
61
+ assert_equal("Foo", owner.name)
62
+ end
63
+
64
+ def test_class_variable_write
65
+ index(<<~RUBY)
66
+ class Foo
67
+ @@bar = 1
68
+ end
69
+ RUBY
70
+
71
+ assert_entry("@@bar", Entry::ClassVariable, "/fake/path/foo.rb:1-2:1-7")
72
+ end
73
+
74
+ def test_empty_name_class_variable
75
+ index(<<~RUBY)
76
+ module Foo
77
+ @@ = 1
78
+ end
79
+ RUBY
80
+
81
+ refute_entry("@@")
82
+ end
83
+
84
+ def test_top_level_class_variable
85
+ index(<<~RUBY)
86
+ @foo = 123
87
+ RUBY
88
+
89
+ entry = T.must(@index["@foo"]&.first)
90
+ assert_nil(entry.owner)
91
+ end
92
+
93
+ def test_class_variable_inside_self_method
94
+ index(<<~RUBY)
95
+ class Foo
96
+ def self.bar
97
+ @@bar = 123
98
+ end
99
+ end
100
+ RUBY
101
+
102
+ entry = T.must(@index["@@bar"]&.first)
103
+ owner = T.must(entry.owner)
104
+ assert_instance_of(Entry::Class, owner)
105
+ assert_equal("Foo", owner.name)
106
+ end
107
+
108
+ def test_class_variable_inside_singleton_class
109
+ index(<<~RUBY)
110
+ class Foo
111
+ class << self
112
+ @@bar = 123
113
+ end
114
+ end
115
+ RUBY
116
+
117
+ entry = T.must(@index["@@bar"]&.first)
118
+ owner = T.must(entry.owner)
119
+ assert_instance_of(Entry::Class, owner)
120
+ assert_equal("Foo", owner.name)
121
+ end
122
+
123
+ def test_class_variable_in_singleton_class_method
124
+ index(<<~RUBY)
125
+ class Foo
126
+ class << self
127
+ def self.bar
128
+ @@bar = 123
129
+ end
130
+ end
131
+ end
132
+ RUBY
133
+
134
+ entry = T.must(@index["@@bar"]&.first)
135
+ owner = T.must(entry.owner)
136
+ assert_instance_of(Entry::Class, owner)
137
+ assert_equal("Foo", owner.name)
138
+ end
139
+ end
140
+ end
@@ -200,7 +200,7 @@ module RubyIndexer
200
200
 
201
201
  assert_entry("Foo", Entry::Class, "/fake/path/foo.rb:0-0:1-3")
202
202
 
203
- @index.delete(IndexablePath.new(nil, "/fake/path/foo.rb"))
203
+ @index.delete(URI::Generic.from_path(path: "/fake/path/foo.rb"))
204
204
  refute_entry("Foo")
205
205
 
206
206
  assert_no_indexed_entries
@@ -618,10 +618,12 @@ module RubyIndexer
618
618
  end
619
619
 
620
620
  def test_lazy_comment_fetching_uses_correct_line_breaks_for_rendering
621
- path = "lib/ruby_lsp/node_context.rb"
622
- indexable = IndexablePath.new("#{Dir.pwd}/lib", path)
621
+ uri = URI::Generic.from_path(
622
+ load_path_entry: "#{Dir.pwd}/lib",
623
+ path: "#{Dir.pwd}/lib/ruby_lsp/node_context.rb",
624
+ )
623
625
 
624
- @index.index_single(indexable, collect_comments: false)
626
+ @index.index_file(uri, collect_comments: false)
625
627
 
626
628
  entry = @index["RubyLsp::NodeContext"].first
627
629
 
@@ -632,9 +634,12 @@ module RubyIndexer
632
634
  end
633
635
 
634
636
  def test_lazy_comment_fetching_does_not_fail_if_file_gets_deleted
635
- indexable = IndexablePath.new("#{Dir.pwd}/lib", "lib/ruby_lsp/does_not_exist.rb")
637
+ uri = URI::Generic.from_path(
638
+ load_path_entry: "#{Dir.pwd}/lib",
639
+ path: "#{Dir.pwd}/lib/ruby_lsp/does_not_exist.rb",
640
+ )
636
641
 
637
- @index.index_single(indexable, <<~RUBY, collect_comments: false)
642
+ @index.index_single(uri, <<~RUBY, collect_comments: false)
638
643
  class Foo
639
644
  end
640
645
  RUBY
@@ -642,5 +647,24 @@ module RubyIndexer
642
647
  entry = @index["Foo"].first
643
648
  assert_empty(entry.comments)
644
649
  end
650
+
651
+ def test_singleton_inside_compact_namespace
652
+ index(<<~RUBY)
653
+ module Foo::Bar
654
+ class << self
655
+ def baz; end
656
+ end
657
+ end
658
+ RUBY
659
+
660
+ # Verify we didn't index the incorrect name
661
+ assert_nil(@index["Foo::Bar::<Class:Foo::Bar>"])
662
+
663
+ # Verify we indexed the correct name
664
+ assert_entry("Foo::Bar::<Class:Bar>", Entry::SingletonClass, "/fake/path/foo.rb:1-2:3-5")
665
+
666
+ method = @index["baz"]&.first
667
+ assert_equal("Foo::Bar::<Class:Bar>", method.owner.name)
668
+ end
645
669
  end
646
670
  end