ruby-lsp 0.22.1 → 0.23.0

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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/exe/ruby-lsp +10 -9
  4. data/exe/ruby-lsp-check +5 -5
  5. data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +26 -20
  6. data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +88 -22
  7. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +60 -30
  8. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +73 -55
  9. data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +16 -14
  10. data/lib/{core_ext → ruby_indexer/lib/ruby_indexer}/uri.rb +29 -3
  11. data/lib/ruby_indexer/ruby_indexer.rb +1 -1
  12. data/lib/ruby_indexer/test/class_variables_test.rb +140 -0
  13. data/lib/ruby_indexer/test/classes_and_modules_test.rb +11 -6
  14. data/lib/ruby_indexer/test/configuration_test.rb +116 -51
  15. data/lib/ruby_indexer/test/enhancements_test.rb +2 -2
  16. data/lib/ruby_indexer/test/index_test.rb +72 -43
  17. data/lib/ruby_indexer/test/rbs_indexer_test.rb +1 -1
  18. data/lib/ruby_indexer/test/reference_finder_test.rb +1 -1
  19. data/lib/ruby_indexer/test/test_case.rb +2 -2
  20. data/lib/ruby_indexer/test/uri_test.rb +72 -0
  21. data/lib/ruby_lsp/addon.rb +9 -0
  22. data/lib/ruby_lsp/base_server.rb +15 -6
  23. data/lib/ruby_lsp/document.rb +10 -1
  24. data/lib/ruby_lsp/internal.rb +1 -1
  25. data/lib/ruby_lsp/listeners/code_lens.rb +8 -4
  26. data/lib/ruby_lsp/listeners/completion.rb +73 -4
  27. data/lib/ruby_lsp/listeners/definition.rb +73 -17
  28. data/lib/ruby_lsp/listeners/document_symbol.rb +12 -1
  29. data/lib/ruby_lsp/listeners/folding_ranges.rb +1 -1
  30. data/lib/ruby_lsp/listeners/hover.rb +57 -0
  31. data/lib/ruby_lsp/requests/completion.rb +6 -0
  32. data/lib/ruby_lsp/requests/completion_resolve.rb +2 -1
  33. data/lib/ruby_lsp/requests/definition.rb +6 -0
  34. data/lib/ruby_lsp/requests/prepare_rename.rb +51 -0
  35. data/lib/ruby_lsp/requests/prepare_type_hierarchy.rb +1 -1
  36. data/lib/ruby_lsp/requests/rename.rb +14 -4
  37. data/lib/ruby_lsp/requests/support/common.rb +1 -5
  38. data/lib/ruby_lsp/requests/type_hierarchy_supertypes.rb +1 -1
  39. data/lib/ruby_lsp/requests/workspace_symbol.rb +3 -2
  40. data/lib/ruby_lsp/scripts/compose_bundle.rb +1 -1
  41. data/lib/ruby_lsp/server.rb +42 -7
  42. data/lib/ruby_lsp/setup_bundler.rb +31 -41
  43. data/lib/ruby_lsp/test_helper.rb +45 -11
  44. data/lib/ruby_lsp/type_inferrer.rb +22 -0
  45. data/lib/ruby_lsp/utils.rb +3 -0
  46. metadata +7 -8
  47. data/lib/ruby_indexer/lib/ruby_indexer/indexable_path.rb +0 -29
@@ -29,13 +29,14 @@ module RubyIndexer
29
29
 
30
30
  # Holds references to where entries where discovered so that we can easily delete them
31
31
  # {
32
- # "/my/project/foo.rb" => [#<Entry::Class>, #<Entry::Class>],
33
- # "/my/project/bar.rb" => [#<Entry::Class>],
32
+ # "file:///my/project/foo.rb" => [#<Entry::Class>, #<Entry::Class>],
33
+ # "file:///my/project/bar.rb" => [#<Entry::Class>],
34
+ # "untitled:Untitled-1" => [#<Entry::Class>],
34
35
  # }
35
- @files_to_entries = T.let({}, T::Hash[String, T::Array[Entry]])
36
+ @uris_to_entries = T.let({}, T::Hash[String, T::Array[Entry]])
36
37
 
37
38
  # Holds all require paths for every indexed item so that we can provide autocomplete for requires
38
- @require_paths_tree = T.let(PrefixTree[IndexablePath].new, PrefixTree[IndexablePath])
39
+ @require_paths_tree = T.let(PrefixTree[URI::Generic].new, PrefixTree[URI::Generic])
39
40
 
40
41
  # Holds the linearized ancestors list for every namespace
41
42
  @ancestors = T.let({}, T::Hash[String, T::Array[String]])
@@ -55,11 +56,12 @@ module RubyIndexer
55
56
  (@included_hooks[module_name] ||= []) << hook
56
57
  end
57
58
 
58
- sig { params(indexable: IndexablePath).void }
59
- def delete(indexable)
59
+ sig { params(uri: URI::Generic).void }
60
+ def delete(uri)
61
+ key = uri.to_s
60
62
  # For each constant discovered in `path`, delete the associated entry from the index. If there are no entries
61
63
  # left, delete the constant from the index.
62
- @files_to_entries[indexable.full_path]&.each do |entry|
64
+ @uris_to_entries[key]&.each do |entry|
63
65
  name = entry.name
64
66
  entries = @entries[name]
65
67
  next unless entries
@@ -77,9 +79,9 @@ module RubyIndexer
77
79
  end
78
80
  end
79
81
 
80
- @files_to_entries.delete(indexable.full_path)
82
+ @uris_to_entries.delete(key)
81
83
 
82
- require_path = indexable.require_path
84
+ require_path = uri.require_path
83
85
  @require_paths_tree.delete(require_path) if require_path
84
86
  end
85
87
 
@@ -88,7 +90,7 @@ module RubyIndexer
88
90
  name = entry.name
89
91
 
90
92
  (@entries[name] ||= []) << entry
91
- (@files_to_entries[entry.file_path] ||= []) << entry
93
+ (@uris_to_entries[entry.uri.to_s] ||= []) << entry
92
94
  @entries_tree.insert(name, T.must(@entries[name])) unless skip_prefix_tree
93
95
  end
94
96
 
@@ -97,7 +99,7 @@ module RubyIndexer
97
99
  @entries[fully_qualified_name.delete_prefix("::")]
98
100
  end
99
101
 
100
- sig { params(query: String).returns(T::Array[IndexablePath]) }
102
+ sig { params(query: String).returns(T::Array[URI::Generic]) }
101
103
  def search_require_paths(query)
102
104
  @require_paths_tree.search(query)
103
105
  end
@@ -342,16 +344,16 @@ module RubyIndexer
342
344
  nil
343
345
  end
344
346
 
345
- # Index all files for the given indexable paths, which defaults to what is configured. A block can be used to track
346
- # and control indexing progress. That block is invoked with the current progress percentage and should return `true`
347
- # to continue indexing or `false` to stop indexing.
347
+ # Index all files for the given URIs, which defaults to what is configured. A block can be used to track and control
348
+ # indexing progress. That block is invoked with the current progress percentage and should return `true` to continue
349
+ # indexing or `false` to stop indexing.
348
350
  sig do
349
351
  params(
350
- indexable_paths: T::Array[IndexablePath],
352
+ uris: T::Array[URI::Generic],
351
353
  block: T.nilable(T.proc.params(progress: Integer).returns(T::Boolean)),
352
354
  ).void
353
355
  end
354
- def index_all(indexable_paths: @configuration.indexables, &block)
356
+ def index_all(uris: @configuration.indexable_uris, &block)
355
357
  # When troubleshooting an indexing issue, e.g. through irb, it's not obvious that `index_all` will augment the
356
358
  # existing index values, meaning it may contain 'stale' entries. This check ensures that the user is aware of this
357
359
  # behavior and can take appropriate action.
@@ -363,54 +365,48 @@ module RubyIndexer
363
365
 
364
366
  RBSIndexer.new(self).index_ruby_core
365
367
  # Calculate how many paths are worth 1% of progress
366
- progress_step = (indexable_paths.length / 100.0).ceil
368
+ progress_step = (uris.length / 100.0).ceil
367
369
 
368
- indexable_paths.each_with_index do |path, index|
370
+ uris.each_with_index do |uri, index|
369
371
  if block && index % progress_step == 0
370
372
  progress = (index / progress_step) + 1
371
373
  break unless block.call(progress)
372
374
  end
373
375
 
374
- index_single(path, collect_comments: false)
376
+ index_file(uri, collect_comments: false)
375
377
  end
376
378
  end
377
379
 
378
- sig { params(indexable_path: IndexablePath, source: T.nilable(String), collect_comments: T::Boolean).void }
379
- def index_single(indexable_path, source = nil, collect_comments: true)
380
- content = source || File.read(indexable_path.full_path)
380
+ sig { params(uri: URI::Generic, source: String, collect_comments: T::Boolean).void }
381
+ def index_single(uri, source, collect_comments: true)
381
382
  dispatcher = Prism::Dispatcher.new
382
383
 
383
- result = Prism.parse(content)
384
- listener = DeclarationListener.new(
385
- self,
386
- dispatcher,
387
- result,
388
- indexable_path.full_path,
389
- collect_comments: collect_comments,
390
- )
384
+ result = Prism.parse(source)
385
+ listener = DeclarationListener.new(self, dispatcher, result, uri, collect_comments: collect_comments)
391
386
  dispatcher.dispatch(result.value)
392
387
 
393
- indexing_errors = listener.indexing_errors.uniq
394
-
395
- require_path = indexable_path.require_path
396
- @require_paths_tree.insert(require_path, indexable_path) if require_path
388
+ require_path = uri.require_path
389
+ @require_paths_tree.insert(require_path, uri) if require_path
397
390
 
398
- if indexing_errors.any?
399
- indexing_errors.each do |error|
400
- $stderr.puts error
401
- end
402
- end
403
- rescue Errno::EISDIR, Errno::ENOENT
404
- # If `path` is a directory, just ignore it and continue indexing. If the file doesn't exist, then we also ignore
405
- # it
391
+ indexing_errors = listener.indexing_errors.uniq
392
+ indexing_errors.each { |error| $stderr.puts(error) } if indexing_errors.any?
406
393
  rescue SystemStackError => e
407
394
  if e.backtrace&.first&.include?("prism")
408
- $stderr.puts "Prism error indexing #{indexable_path.full_path}: #{e.message}"
395
+ $stderr.puts "Prism error indexing #{uri}: #{e.message}"
409
396
  else
410
397
  raise
411
398
  end
412
399
  end
413
400
 
401
+ # Indexes a File URI by reading the contents from disk
402
+ sig { params(uri: URI::Generic, collect_comments: T::Boolean).void }
403
+ def index_file(uri, collect_comments: true)
404
+ index_single(uri, File.read(T.must(uri.full_path)), collect_comments: collect_comments)
405
+ rescue Errno::EISDIR, Errno::ENOENT
406
+ # If `path` is a directory, just ignore it and continue indexing. If the file doesn't exist, then we also ignore
407
+ # it
408
+ end
409
+
414
410
  # Follows aliases in a namespace. The algorithm keeps checking if the name is an alias and then recursively follows
415
411
  # it. The idea is that we test the name in parts starting from the complete name to the first namespace. For
416
412
  # `Foo::Bar::Baz`, we would test:
@@ -588,6 +584,17 @@ module RubyIndexer
588
584
  entries.select { |e| ancestors.include?(e.owner&.name) }
589
585
  end
590
586
 
587
+ sig { params(variable_name: String, owner_name: String).returns(T.nilable(T::Array[Entry::ClassVariable])) }
588
+ def resolve_class_variable(variable_name, owner_name)
589
+ entries = self[variable_name]&.grep(Entry::ClassVariable)
590
+ return unless entries&.any?
591
+
592
+ ancestors = linearized_ancestors_of(owner_name)
593
+ return if ancestors.empty?
594
+
595
+ entries.select { |e| ancestors.include?(e.owner&.name) }
596
+ end
597
+
591
598
  # Returns a list of possible candidates for completion of instance variables for a given owner name. The name must
592
599
  # include the `@` prefix
593
600
  sig { params(name: String, owner_name: String).returns(T::Array[Entry::InstanceVariable]) }
@@ -600,16 +607,27 @@ module RubyIndexer
600
607
  variables
601
608
  end
602
609
 
603
- # Synchronizes a change made to the given indexable path. This method will ensure that new declarations are indexed,
604
- # removed declarations removed and that the ancestor linearization cache is cleared if necessary
605
- sig { params(indexable: IndexablePath).void }
606
- def handle_change(indexable)
607
- original_entries = @files_to_entries[indexable.full_path]
610
+ sig { params(name: String, owner_name: String).returns(T::Array[Entry::ClassVariable]) }
611
+ def class_variable_completion_candidates(name, owner_name)
612
+ entries = T.cast(prefix_search(name).flatten, T::Array[Entry::ClassVariable])
613
+ ancestors = linearized_ancestors_of(owner_name)
614
+
615
+ variables = entries.select { |e| ancestors.any?(e.owner&.name) }
616
+ variables.uniq!(&:name)
617
+ variables
618
+ end
619
+
620
+ # Synchronizes a change made to the given URI. This method will ensure that new declarations are indexed, removed
621
+ # declarations removed and that the ancestor linearization cache is cleared if necessary
622
+ sig { params(uri: URI::Generic, source: String).void }
623
+ def handle_change(uri, source)
624
+ key = uri.to_s
625
+ original_entries = @uris_to_entries[key]
608
626
 
609
- delete(indexable)
610
- index_single(indexable)
627
+ delete(uri)
628
+ index_single(uri, source)
611
629
 
612
- updated_entries = @files_to_entries[indexable.full_path]
630
+ updated_entries = @uris_to_entries[key]
613
631
 
614
632
  return unless original_entries && updated_entries
615
633
 
@@ -661,7 +679,7 @@ module RubyIndexer
661
679
 
662
680
  singleton = Entry::SingletonClass.new(
663
681
  [full_singleton_name],
664
- attached_ancestor.file_path,
682
+ attached_ancestor.uri,
665
683
  attached_ancestor.location,
666
684
  attached_ancestor.name_location,
667
685
  nil,
@@ -675,12 +693,12 @@ module RubyIndexer
675
693
 
676
694
  sig do
677
695
  type_parameters(:T).params(
678
- path: String,
696
+ uri: String,
679
697
  type: T.nilable(T::Class[T.all(T.type_parameter(:T), Entry)]),
680
698
  ).returns(T.nilable(T.any(T::Array[Entry], T::Array[T.type_parameter(:T)])))
681
699
  end
682
- def entries_for(path, type = nil)
683
- entries = @files_to_entries[path]
700
+ def entries_for(uri, type = nil)
701
+ entries = @uris_to_entries[uri.to_s]
684
702
  return entries unless type
685
703
 
686
704
  entries&.grep(type)
@@ -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
  )
@@ -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
@@ -4,7 +4,7 @@
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
8
  require "ruby_indexer/lib/ruby_indexer/declaration_listener"
9
9
  require "ruby_indexer/lib/ruby_indexer/reference_finder"
10
10
  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