ruby-lsp 0.22.1 → 0.23.0

Sign up to get free protection for your applications and to get access to all the features.
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