ruby-lsp 0.22.0 → 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 (49) 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 +131 -23
  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/method_test.rb +80 -0
  18. data/lib/ruby_indexer/test/rbs_indexer_test.rb +1 -1
  19. data/lib/ruby_indexer/test/reference_finder_test.rb +1 -1
  20. data/lib/ruby_indexer/test/test_case.rb +2 -2
  21. data/lib/ruby_indexer/test/uri_test.rb +72 -0
  22. data/lib/ruby_lsp/addon.rb +9 -0
  23. data/lib/ruby_lsp/base_server.rb +15 -6
  24. data/lib/ruby_lsp/document.rb +10 -1
  25. data/lib/ruby_lsp/global_state.rb +1 -1
  26. data/lib/ruby_lsp/internal.rb +1 -1
  27. data/lib/ruby_lsp/listeners/code_lens.rb +8 -4
  28. data/lib/ruby_lsp/listeners/completion.rb +73 -4
  29. data/lib/ruby_lsp/listeners/definition.rb +73 -17
  30. data/lib/ruby_lsp/listeners/document_symbol.rb +49 -5
  31. data/lib/ruby_lsp/listeners/folding_ranges.rb +1 -1
  32. data/lib/ruby_lsp/listeners/hover.rb +57 -0
  33. data/lib/ruby_lsp/requests/completion.rb +6 -0
  34. data/lib/ruby_lsp/requests/completion_resolve.rb +2 -1
  35. data/lib/ruby_lsp/requests/definition.rb +6 -0
  36. data/lib/ruby_lsp/requests/prepare_rename.rb +51 -0
  37. data/lib/ruby_lsp/requests/prepare_type_hierarchy.rb +1 -1
  38. data/lib/ruby_lsp/requests/rename.rb +14 -4
  39. data/lib/ruby_lsp/requests/support/common.rb +1 -5
  40. data/lib/ruby_lsp/requests/type_hierarchy_supertypes.rb +1 -1
  41. data/lib/ruby_lsp/requests/workspace_symbol.rb +3 -2
  42. data/lib/ruby_lsp/scripts/compose_bundle.rb +1 -1
  43. data/lib/ruby_lsp/server.rb +42 -7
  44. data/lib/ruby_lsp/setup_bundler.rb +54 -46
  45. data/lib/ruby_lsp/test_helper.rb +45 -11
  46. data/lib/ruby_lsp/type_inferrer.rb +22 -0
  47. data/lib/ruby_lsp/utils.rb +3 -0
  48. metadata +7 -8
  49. data/lib/ruby_indexer/lib/ruby_indexer/indexable_path.rb +0 -29
@@ -16,8 +16,8 @@ module RubyIndexer
16
16
  sig { returns(String) }
17
17
  attr_reader :name
18
18
 
19
- sig { returns(String) }
20
- attr_reader :file_path
19
+ sig { returns(URI::Generic) }
20
+ attr_reader :uri
21
21
 
22
22
  sig { returns(RubyIndexer::Location) }
23
23
  attr_reader :location
@@ -30,14 +30,14 @@ module RubyIndexer
30
30
  sig do
31
31
  params(
32
32
  name: String,
33
- file_path: String,
33
+ uri: URI::Generic,
34
34
  location: Location,
35
35
  comments: T.nilable(String),
36
36
  ).void
37
37
  end
38
- def initialize(name, file_path, location, comments)
38
+ def initialize(name, uri, location, comments)
39
39
  @name = name
40
- @file_path = file_path
40
+ @uri = uri
41
41
  @comments = comments
42
42
  @visibility = T.let(Visibility::PUBLIC, Visibility)
43
43
  @location = location
@@ -60,14 +60,24 @@ module RubyIndexer
60
60
 
61
61
  sig { returns(String) }
62
62
  def file_name
63
- File.basename(@file_path)
63
+ if @uri.scheme == "untitled"
64
+ T.must(@uri.opaque)
65
+ else
66
+ File.basename(T.must(file_path))
67
+ end
68
+ end
69
+
70
+ sig { returns(T.nilable(String)) }
71
+ def file_path
72
+ @uri.full_path
64
73
  end
65
74
 
66
75
  sig { returns(String) }
67
76
  def comments
68
77
  @comments ||= begin
69
78
  # Parse only the comments based on the file path, which is much faster than parsing the entire file
70
- parsed_comments = Prism.parse_file_comments(@file_path)
79
+ path = file_path
80
+ parsed_comments = path ? Prism.parse_file_comments(path) : []
71
81
 
72
82
  # Group comments based on whether they belong to a single block of comments
73
83
  grouped = parsed_comments.slice_when do |left, right|
@@ -137,18 +147,18 @@ module RubyIndexer
137
147
  sig do
138
148
  params(
139
149
  nesting: T::Array[String],
140
- file_path: String,
150
+ uri: URI::Generic,
141
151
  location: Location,
142
152
  name_location: Location,
143
153
  comments: T.nilable(String),
144
154
  ).void
145
155
  end
146
- def initialize(nesting, file_path, location, name_location, comments)
156
+ def initialize(nesting, uri, location, name_location, comments)
147
157
  @name = T.let(nesting.join("::"), String)
148
158
  # The original nesting where this namespace was discovered
149
159
  @nesting = nesting
150
160
 
151
- super(@name, file_path, location, comments)
161
+ super(@name, uri, location, comments)
152
162
 
153
163
  @name_location = name_location
154
164
  end
@@ -186,15 +196,15 @@ module RubyIndexer
186
196
  sig do
187
197
  params(
188
198
  nesting: T::Array[String],
189
- file_path: String,
199
+ uri: URI::Generic,
190
200
  location: Location,
191
201
  name_location: Location,
192
202
  comments: T.nilable(String),
193
203
  parent_class: T.nilable(String),
194
204
  ).void
195
205
  end
196
- def initialize(nesting, file_path, location, name_location, comments, parent_class) # rubocop:disable Metrics/ParameterLists
197
- super(nesting, file_path, location, name_location, comments)
206
+ def initialize(nesting, uri, location, name_location, comments, parent_class) # rubocop:disable Metrics/ParameterLists
207
+ super(nesting, uri, location, name_location, comments)
198
208
  @parent_class = parent_class
199
209
  end
200
210
 
@@ -332,15 +342,15 @@ module RubyIndexer
332
342
  sig do
333
343
  params(
334
344
  name: String,
335
- file_path: String,
345
+ uri: URI::Generic,
336
346
  location: Location,
337
347
  comments: T.nilable(String),
338
348
  visibility: Visibility,
339
349
  owner: T.nilable(Entry::Namespace),
340
350
  ).void
341
351
  end
342
- def initialize(name, file_path, location, comments, visibility, owner) # rubocop:disable Metrics/ParameterLists
343
- super(name, file_path, location, comments)
352
+ def initialize(name, uri, location, comments, visibility, owner) # rubocop:disable Metrics/ParameterLists
353
+ super(name, uri, location, comments)
344
354
  @visibility = visibility
345
355
  @owner = owner
346
356
  end
@@ -399,7 +409,7 @@ module RubyIndexer
399
409
  sig do
400
410
  params(
401
411
  name: String,
402
- file_path: String,
412
+ uri: URI::Generic,
403
413
  location: Location,
404
414
  name_location: Location,
405
415
  comments: T.nilable(String),
@@ -408,8 +418,8 @@ module RubyIndexer
408
418
  owner: T.nilable(Entry::Namespace),
409
419
  ).void
410
420
  end
411
- def initialize(name, file_path, location, name_location, comments, signatures, visibility, owner) # rubocop:disable Metrics/ParameterLists
412
- super(name, file_path, location, comments, visibility, owner)
421
+ def initialize(name, uri, location, name_location, comments, signatures, visibility, owner) # rubocop:disable Metrics/ParameterLists
422
+ super(name, uri, location, comments, visibility, owner)
413
423
  @signatures = signatures
414
424
  @name_location = name_location
415
425
  end
@@ -439,13 +449,13 @@ module RubyIndexer
439
449
  target: String,
440
450
  nesting: T::Array[String],
441
451
  name: String,
442
- file_path: String,
452
+ uri: URI::Generic,
443
453
  location: Location,
444
454
  comments: T.nilable(String),
445
455
  ).void
446
456
  end
447
- def initialize(target, nesting, name, file_path, location, comments) # rubocop:disable Metrics/ParameterLists
448
- super(name, file_path, location, comments)
457
+ def initialize(target, nesting, name, uri, location, comments) # rubocop:disable Metrics/ParameterLists
458
+ super(name, uri, location, comments)
449
459
 
450
460
  @target = target
451
461
  @nesting = nesting
@@ -463,7 +473,7 @@ module RubyIndexer
463
473
  def initialize(target, unresolved_alias)
464
474
  super(
465
475
  unresolved_alias.name,
466
- unresolved_alias.file_path,
476
+ unresolved_alias.uri,
467
477
  unresolved_alias.location,
468
478
  unresolved_alias.comments,
469
479
  )
@@ -476,6 +486,26 @@ module RubyIndexer
476
486
  # Represents a global variable e.g.: $DEBUG
477
487
  class GlobalVariable < Entry; end
478
488
 
489
+ # Represents a class variable e.g.: @@a = 1
490
+ class ClassVariable < Entry
491
+ sig { returns(T.nilable(Entry::Namespace)) }
492
+ attr_reader :owner
493
+
494
+ sig do
495
+ params(
496
+ name: String,
497
+ uri: URI::Generic,
498
+ location: Location,
499
+ comments: T.nilable(String),
500
+ owner: T.nilable(Entry::Namespace),
501
+ ).void
502
+ end
503
+ def initialize(name, uri, location, comments, owner)
504
+ super(name, uri, location, comments)
505
+ @owner = owner
506
+ end
507
+ end
508
+
479
509
  # Represents an instance variable e.g.: @a = 1
480
510
  class InstanceVariable < Entry
481
511
  sig { returns(T.nilable(Entry::Namespace)) }
@@ -484,14 +514,14 @@ module RubyIndexer
484
514
  sig do
485
515
  params(
486
516
  name: String,
487
- file_path: String,
517
+ uri: URI::Generic,
488
518
  location: Location,
489
519
  comments: T.nilable(String),
490
520
  owner: T.nilable(Entry::Namespace),
491
521
  ).void
492
522
  end
493
- def initialize(name, file_path, location, comments, owner)
494
- super(name, file_path, location, comments)
523
+ def initialize(name, uri, location, comments, owner)
524
+ super(name, uri, location, comments)
495
525
  @owner = owner
496
526
  end
497
527
  end
@@ -513,13 +543,13 @@ module RubyIndexer
513
543
  new_name: String,
514
544
  old_name: String,
515
545
  owner: T.nilable(Entry::Namespace),
516
- file_path: String,
546
+ uri: URI::Generic,
517
547
  location: Location,
518
548
  comments: T.nilable(String),
519
549
  ).void
520
550
  end
521
- def initialize(new_name, old_name, owner, file_path, location, comments) # rubocop:disable Metrics/ParameterLists
522
- super(new_name, file_path, location, comments)
551
+ def initialize(new_name, old_name, owner, uri, location, comments) # rubocop:disable Metrics/ParameterLists
552
+ super(new_name, uri, location, comments)
523
553
 
524
554
  @new_name = new_name
525
555
  @old_name = old_name
@@ -547,7 +577,7 @@ module RubyIndexer
547
577
 
548
578
  super(
549
579
  unresolved_alias.new_name,
550
- unresolved_alias.file_path,
580
+ unresolved_alias.uri,
551
581
  unresolved_alias.location,
552
582
  full_comments,
553
583
  )
@@ -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"