ruby-lsp 0.22.0 → 0.23.0

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