ruby-lsp 0.17.2 → 0.17.4

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