ruby-lsp 0.17.2 → 0.17.4

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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -0
  3. data/VERSION +1 -1
  4. data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +280 -74
  5. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +102 -102
  6. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +234 -56
  7. data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +147 -0
  8. data/lib/ruby_indexer/ruby_indexer.rb +1 -0
  9. data/lib/ruby_indexer/test/classes_and_modules_test.rb +49 -2
  10. data/lib/ruby_indexer/test/configuration_test.rb +1 -1
  11. data/lib/ruby_indexer/test/constant_test.rb +1 -1
  12. data/lib/ruby_indexer/test/index_test.rb +702 -71
  13. data/lib/ruby_indexer/test/instance_variables_test.rb +84 -7
  14. data/lib/ruby_indexer/test/method_test.rb +74 -24
  15. data/lib/ruby_indexer/test/rbs_indexer_test.rb +67 -0
  16. data/lib/ruby_indexer/test/test_case.rb +7 -0
  17. data/lib/ruby_lsp/document.rb +37 -8
  18. data/lib/ruby_lsp/global_state.rb +43 -18
  19. data/lib/ruby_lsp/internal.rb +2 -0
  20. data/lib/ruby_lsp/listeners/code_lens.rb +2 -2
  21. data/lib/ruby_lsp/listeners/completion.rb +53 -14
  22. data/lib/ruby_lsp/listeners/definition.rb +11 -7
  23. data/lib/ruby_lsp/listeners/hover.rb +14 -7
  24. data/lib/ruby_lsp/listeners/signature_help.rb +5 -2
  25. data/lib/ruby_lsp/node_context.rb +6 -1
  26. data/lib/ruby_lsp/requests/completion.rb +5 -4
  27. data/lib/ruby_lsp/requests/completion_resolve.rb +8 -0
  28. data/lib/ruby_lsp/requests/prepare_type_hierarchy.rb +88 -0
  29. data/lib/ruby_lsp/requests/support/common.rb +19 -1
  30. data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +12 -4
  31. data/lib/ruby_lsp/requests/type_hierarchy_supertypes.rb +91 -0
  32. data/lib/ruby_lsp/requests/workspace_symbol.rb +1 -21
  33. data/lib/ruby_lsp/requests.rb +2 -0
  34. data/lib/ruby_lsp/server.rb +54 -4
  35. data/lib/ruby_lsp/test_helper.rb +1 -1
  36. data/lib/ruby_lsp/type_inferrer.rb +86 -0
  37. metadata +29 -4
@@ -0,0 +1,147 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyIndexer
5
+ class RBSIndexer
6
+ extend T::Sig
7
+
8
+ sig { params(index: Index).void }
9
+ def initialize(index)
10
+ @index = index
11
+ end
12
+
13
+ sig { void }
14
+ def index_ruby_core
15
+ loader = RBS::EnvironmentLoader.new
16
+ RBS::Environment.from_loader(loader).resolve_type_names
17
+
18
+ loader.each_signature do |source, pathname, _buffer, declarations, _directives|
19
+ process_signature(source, pathname, declarations)
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ sig { params(source: T.untyped, pathname: Pathname, declarations: T::Array[RBS::AST::Declarations::Base]).void }
26
+ def process_signature(source, pathname, declarations)
27
+ declarations.each do |declaration|
28
+ process_declaration(declaration, pathname)
29
+ end
30
+ end
31
+
32
+ sig { params(declaration: RBS::AST::Declarations::Base, pathname: Pathname).void }
33
+ def process_declaration(declaration, pathname)
34
+ case declaration
35
+ when RBS::AST::Declarations::Class
36
+ handle_class_declaration(declaration, pathname)
37
+ when RBS::AST::Declarations::Module
38
+ handle_module_declaration(declaration, pathname)
39
+ else # rubocop:disable Style/EmptyElse
40
+ # Other kinds not yet handled
41
+ end
42
+ end
43
+
44
+ sig { params(declaration: RBS::AST::Declarations::Class, pathname: Pathname).void }
45
+ def handle_class_declaration(declaration, pathname)
46
+ nesting = [declaration.name.name.to_s]
47
+ file_path = pathname.to_s
48
+ location = to_ruby_indexer_location(declaration.location)
49
+ comments = Array(declaration.comment&.string)
50
+ parent_class = declaration.super_class&.name&.name&.to_s
51
+ class_entry = Entry::Class.new(nesting, file_path, location, comments, parent_class)
52
+ add_declaration_mixins_to_entry(declaration, class_entry)
53
+ @index.add(class_entry)
54
+ declaration.members.each do |member|
55
+ next unless member.is_a?(RBS::AST::Members::MethodDefinition)
56
+
57
+ handle_method(member, class_entry)
58
+ end
59
+ end
60
+
61
+ sig { params(declaration: RBS::AST::Declarations::Module, pathname: Pathname).void }
62
+ def handle_module_declaration(declaration, pathname)
63
+ nesting = [declaration.name.name.to_s]
64
+ file_path = pathname.to_s
65
+ location = to_ruby_indexer_location(declaration.location)
66
+ comments = Array(declaration.comment&.string)
67
+ module_entry = Entry::Module.new(nesting, file_path, location, comments)
68
+ add_declaration_mixins_to_entry(declaration, module_entry)
69
+ @index.add(module_entry)
70
+ declaration.members.each do |member|
71
+ next unless member.is_a?(RBS::AST::Members::MethodDefinition)
72
+
73
+ handle_method(member, module_entry)
74
+ end
75
+ end
76
+
77
+ sig { params(rbs_location: RBS::Location).returns(RubyIndexer::Location) }
78
+ def to_ruby_indexer_location(rbs_location)
79
+ RubyIndexer::Location.new(
80
+ rbs_location.start_line,
81
+ rbs_location.end_line,
82
+ rbs_location.start_column,
83
+ rbs_location.end_column,
84
+ )
85
+ end
86
+
87
+ sig do
88
+ params(
89
+ declaration: T.any(RBS::AST::Declarations::Class, RBS::AST::Declarations::Module),
90
+ entry: Entry::Namespace,
91
+ ).void
92
+ end
93
+ def add_declaration_mixins_to_entry(declaration, entry)
94
+ declaration.each_mixin do |mixin|
95
+ name = mixin.name.name.to_s
96
+ mixin_operation =
97
+ case mixin
98
+ when RBS::AST::Members::Include
99
+ Entry::Include.new(name)
100
+ when RBS::AST::Members::Extend
101
+ Entry::Extend.new(name)
102
+ when RBS::AST::Members::Prepend
103
+ Entry::Prepend.new(name)
104
+ end
105
+ entry.mixin_operations << mixin_operation if mixin_operation
106
+ end
107
+ end
108
+
109
+ sig { params(member: RBS::AST::Members::MethodDefinition, owner: Entry::Namespace).void }
110
+ def handle_method(member, owner)
111
+ name = member.name.name
112
+ file_path = member.location.buffer.name
113
+ location = to_ruby_indexer_location(member.location)
114
+ comments = Array(member.comment&.string)
115
+
116
+ visibility = case member.visibility
117
+ when :private
118
+ Entry::Visibility::PRIVATE
119
+ when :protected
120
+ Entry::Visibility::PROTECTED
121
+ else
122
+ Entry::Visibility::PUBLIC
123
+ end
124
+
125
+ real_owner = member.singleton? ? existing_or_new_singleton_klass(owner) : owner
126
+ @index.add(Entry::Method.new(name, file_path, location, comments, [], visibility, real_owner))
127
+ end
128
+
129
+ sig { params(owner: Entry::Namespace).returns(T.nilable(Entry::Class)) }
130
+ def existing_or_new_singleton_klass(owner)
131
+ *_parts, name = owner.name.split("::")
132
+
133
+ # Return the existing singleton class if available
134
+ singleton_entries = T.cast(
135
+ @index["#{owner.name}::<Class:#{name}>"],
136
+ T.nilable(T::Array[Entry::SingletonClass]),
137
+ )
138
+ return singleton_entries.first if singleton_entries
139
+
140
+ # If not available, create the singleton class lazily
141
+ nesting = owner.nesting + ["<Class:#{name}>"]
142
+ entry = Entry::SingletonClass.new(nesting, owner.file_path, owner.location, [], nil)
143
+ @index.add(entry, skip_prefix_tree: true)
144
+ entry
145
+ end
146
+ end
147
+ end
@@ -11,6 +11,7 @@ require "ruby_indexer/lib/ruby_indexer/entry"
11
11
  require "ruby_indexer/lib/ruby_indexer/configuration"
12
12
  require "ruby_indexer/lib/ruby_indexer/prefix_tree"
13
13
  require "ruby_indexer/lib/ruby_indexer/location"
14
+ require "ruby_indexer/lib/ruby_indexer/rbs_indexer"
14
15
 
15
16
  module RubyIndexer
16
17
  @configuration = T.let(Configuration.new, Configuration)
@@ -191,7 +191,8 @@ module RubyIndexer
191
191
 
192
192
  @index.delete(IndexablePath.new(nil, "/fake/path/foo.rb"))
193
193
  refute_entry("Foo")
194
- assert_empty(@index.instance_variable_get(:@files_to_entries))
194
+
195
+ assert_no_indexed_entries
195
196
  end
196
197
 
197
198
  def test_comments_can_be_attached_to_a_class
@@ -323,7 +324,7 @@ module RubyIndexer
323
324
  assert_equal("Bar", foo.parent_class)
324
325
 
325
326
  baz = T.must(@index["Baz"].first)
326
- assert_nil(baz.parent_class)
327
+ assert_equal("::Object", baz.parent_class)
327
328
 
328
329
  qux = T.must(@index["Something::Qux"].first)
329
330
  assert_equal("::Baz", qux.parent_class)
@@ -469,5 +470,51 @@ module RubyIndexer
469
470
  constant_path_references = T.must(@index["ConstantPathReferences"][0])
470
471
  assert_equal(["Foo::Bar", "Foo::Bar2"], constant_path_references.mixin_operation_module_names)
471
472
  end
473
+
474
+ def test_tracking_singleton_classes
475
+ index(<<~RUBY)
476
+ class Foo; end
477
+ class Foo
478
+ # Some extra comments
479
+ class << self
480
+ end
481
+ end
482
+ RUBY
483
+
484
+ foo = T.must(@index["Foo::<Class:Foo>"].first)
485
+ assert_equal(4, foo.location.start_line)
486
+ assert_equal("Some extra comments", foo.comments.join("\n"))
487
+ end
488
+
489
+ def test_dynamic_singleton_class_blocks
490
+ index(<<~RUBY)
491
+ class Foo
492
+ # Some extra comments
493
+ class << bar
494
+ end
495
+ end
496
+ RUBY
497
+
498
+ singleton = T.must(@index["Foo::<Class:bar>"].first)
499
+
500
+ # Even though this is not correct, we consider any dynamic singleton class block as a regular singleton class.
501
+ # That pattern cannot be properly analyzed statically and assuming that it's always a regular singleton simplifies
502
+ # the implementation considerably.
503
+ assert_equal(3, singleton.location.start_line)
504
+ assert_equal("Some extra comments", singleton.comments.join("\n"))
505
+ end
506
+
507
+ def test_namespaces_inside_singleton_blocks
508
+ index(<<~RUBY)
509
+ class Foo
510
+ class << self
511
+ class Bar
512
+ end
513
+ end
514
+ end
515
+ RUBY
516
+
517
+ assert_entry("Foo::<Class:Foo>::Bar", Entry::Class, "/fake/path/foo.rb:2-4:3-7")
518
+ end
472
519
  end
473
520
  end
@@ -54,7 +54,7 @@ module RubyIndexer
54
54
 
55
55
  assert_includes(indexables, "#{RbConfig::CONFIG["rubylibdir"]}/pathname.rb")
56
56
  assert_includes(indexables, "#{RbConfig::CONFIG["rubylibdir"]}/ipaddr.rb")
57
- assert_includes(indexables, "#{RbConfig::CONFIG["rubylibdir"]}/abbrev.rb")
57
+ assert_includes(indexables, "#{RbConfig::CONFIG["rubylibdir"]}/erb.rb")
58
58
  end
59
59
 
60
60
  def test_indexables_includes_project_files
@@ -105,7 +105,7 @@ module RubyIndexer
105
105
  self.class::FOO = 1
106
106
  RUBY
107
107
 
108
- assert_no_entries
108
+ assert_no_indexed_entries
109
109
  end
110
110
 
111
111
  def test_private_constant_indexing