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
@@ -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
@@ -13,63 +13,62 @@ module RubyIndexer
13
13
 
14
14
  def test_load_configuration_executes_configure_block
15
15
  @config.apply_config({ "excluded_patterns" => ["**/fixtures/**/*.rb"] })
16
- indexables = @config.indexables
16
+ uris = @config.indexable_uris
17
17
 
18
- assert(indexables.none? { |indexable| indexable.full_path.include?("test/fixtures") })
19
- assert(indexables.none? { |indexable| indexable.full_path.include?("minitest-reporters") })
20
- assert(indexables.none? { |indexable| indexable.full_path.include?("ansi") })
21
- assert(indexables.any? { |indexable| indexable.full_path.include?("sorbet-runtime") })
22
- assert(indexables.none? { |indexable| indexable.full_path == __FILE__ })
18
+ bundle_path = Bundler.bundle_path.join("gems")
19
+
20
+ assert(uris.none? { |uri| uri.full_path.include?("test/fixtures") })
21
+ assert(uris.none? { |uri| uri.full_path.include?(bundle_path.join("minitest-reporters").to_s) })
22
+ assert(uris.none? { |uri| uri.full_path.include?(bundle_path.join("ansi").to_s) })
23
+ assert(uris.any? { |uri| uri.full_path.include?(bundle_path.join("sorbet-runtime").to_s) })
24
+ assert(uris.none? { |uri| uri.full_path == __FILE__ })
23
25
  end
24
26
 
25
- def test_indexables_have_expanded_full_paths
27
+ def test_indexable_uris_have_expanded_full_paths
26
28
  @config.apply_config({ "included_patterns" => ["**/*.rb"] })
27
- indexables = @config.indexables
29
+ uris = @config.indexable_uris
28
30
 
29
31
  # All paths should be expanded
30
- assert(indexables.all? { |indexable| File.absolute_path?(indexable.full_path) })
32
+ assert(uris.all? { |uri| File.absolute_path?(uri.full_path) })
31
33
  end
32
34
 
33
- def test_indexables_only_includes_gem_require_paths
34
- indexables = @config.indexables
35
+ def test_indexable_uris_only_includes_gem_require_paths
36
+ uris = @config.indexable_uris
35
37
 
36
38
  Bundler.locked_gems.specs.each do |lazy_spec|
37
39
  next if lazy_spec.name == "ruby-lsp"
38
40
 
39
41
  spec = Gem::Specification.find_by_name(lazy_spec.name)
40
- assert(indexables.none? { |indexable| indexable.full_path.start_with?("#{spec.full_gem_path}/test/") })
42
+ assert(uris.none? { |uri| uri.full_path.start_with?("#{spec.full_gem_path}/test/") })
41
43
  rescue Gem::MissingSpecError
42
44
  # Transitive dependencies might be missing when running tests on Windows
43
45
  end
44
46
  end
45
47
 
46
- def test_indexables_does_not_include_default_gem_path_when_in_bundle
47
- indexables = @config.indexables
48
-
49
- assert(
50
- indexables.none? { |indexable| indexable.full_path.start_with?("#{RbConfig::CONFIG["rubylibdir"]}/psych") },
51
- )
48
+ def test_indexable_uris_does_not_include_default_gem_path_when_in_bundle
49
+ uris = @config.indexable_uris
50
+ assert(uris.none? { |uri| uri.full_path.start_with?("#{RbConfig::CONFIG["rubylibdir"]}/psych") })
52
51
  end
53
52
 
54
- def test_indexables_includes_default_gems
55
- indexables = @config.indexables.map(&:full_path)
53
+ def test_indexable_uris_includes_default_gems
54
+ paths = @config.indexable_uris.map(&:full_path)
56
55
 
57
- assert_includes(indexables, "#{RbConfig::CONFIG["rubylibdir"]}/pathname.rb")
58
- assert_includes(indexables, "#{RbConfig::CONFIG["rubylibdir"]}/ipaddr.rb")
59
- assert_includes(indexables, "#{RbConfig::CONFIG["rubylibdir"]}/erb.rb")
56
+ assert_includes(paths, "#{RbConfig::CONFIG["rubylibdir"]}/pathname.rb")
57
+ assert_includes(paths, "#{RbConfig::CONFIG["rubylibdir"]}/ipaddr.rb")
58
+ assert_includes(paths, "#{RbConfig::CONFIG["rubylibdir"]}/erb.rb")
60
59
  end
61
60
 
62
- def test_indexables_includes_project_files
63
- indexables = @config.indexables.map(&:full_path)
61
+ def test_indexable_uris_includes_project_files
62
+ paths = @config.indexable_uris.map(&:full_path)
64
63
 
65
64
  Dir.glob("#{Dir.pwd}/lib/**/*.rb").each do |path|
66
65
  next if path.end_with?("_test.rb")
67
66
 
68
- assert_includes(indexables, path)
67
+ assert_includes(paths, path)
69
68
  end
70
69
  end
71
70
 
72
- def test_indexables_avoids_duplicates_if_bundle_path_is_inside_project
71
+ def test_indexable_uris_avoids_duplicates_if_bundle_path_is_inside_project
73
72
  Bundler.settings.temporary(path: "vendor/bundle") do
74
73
  config = Configuration.new
75
74
 
@@ -77,33 +76,32 @@ module RubyIndexer
77
76
  end
78
77
  end
79
78
 
80
- def test_indexables_does_not_include_gems_own_installed_files
81
- indexables = @config.indexables
82
- indexables_inside_bundled_lsp = indexables.select do |indexable|
83
- indexable.full_path.start_with?(Bundler.bundle_path.join("gems", "ruby-lsp").to_s)
79
+ def test_indexable_uris_does_not_include_gems_own_installed_files
80
+ uris = @config.indexable_uris
81
+ uris_inside_bundled_lsp = uris.select do |uri|
82
+ uri.full_path.start_with?(Bundler.bundle_path.join("gems", "ruby-lsp").to_s)
84
83
  end
85
84
 
86
85
  assert_empty(
87
- indexables_inside_bundled_lsp,
88
- "Indexables should not include files from the gem currently being worked on. " \
89
- "Included: #{indexables_inside_bundled_lsp.map(&:full_path)}",
86
+ uris_inside_bundled_lsp,
87
+ "Indexable URIs should not include files from the gem currently being worked on. " \
88
+ "Included: #{uris_inside_bundled_lsp.map(&:full_path)}",
90
89
  )
91
90
  end
92
91
 
93
- def test_indexables_does_not_include_non_ruby_files_inside_rubylibdir
92
+ def test_indexable_uris_does_not_include_non_ruby_files_inside_rubylibdir
94
93
  path = Pathname.new(RbConfig::CONFIG["rubylibdir"]).join("extra_file.txt").to_s
95
94
  FileUtils.touch(path)
96
- indexables = @config.indexables
97
95
 
98
- assert(indexables.none? { |indexable| indexable.full_path == path })
96
+ uris = @config.indexable_uris
97
+ assert(uris.none? { |uri| uri.full_path == path })
99
98
  ensure
100
99
  FileUtils.rm(T.must(path))
101
100
  end
102
101
 
103
102
  def test_paths_are_unique
104
- indexables = @config.indexables
105
-
106
- assert_equal(indexables.uniq.length, indexables.length)
103
+ uris = @config.indexable_uris
104
+ assert_equal(uris.uniq.length, uris.length)
107
105
  end
108
106
 
109
107
  def test_configuration_raises_for_unknown_keys
@@ -132,7 +130,7 @@ module RubyIndexer
132
130
  end
133
131
  end
134
132
 
135
- def test_indexables_respect_given_workspace_path
133
+ def test_indexable_uris_respect_given_workspace_path
136
134
  Dir.mktmpdir do |dir|
137
135
  FileUtils.mkdir(File.join(dir, "ignore"))
138
136
  FileUtils.touch(File.join(dir, "ignore", "file0.rb"))
@@ -141,21 +139,21 @@ module RubyIndexer
141
139
 
142
140
  @config.apply_config({ "excluded_patterns" => ["ignore/**/*.rb"] })
143
141
  @config.workspace_path = dir
144
- indexables = @config.indexables
145
142
 
146
- assert(indexables.none? { |indexable| indexable.full_path.start_with?(File.join(dir, "ignore")) })
143
+ uris = @config.indexable_uris
144
+ assert(uris.none? { |uri| uri.full_path.start_with?(File.join(dir, "ignore")) })
147
145
 
148
- # After switching the workspace path, all indexables will be found in one of these places:
146
+ # After switching the workspace path, all indexable URIs will be found in one of these places:
149
147
  # - The new workspace path
150
148
  # - The Ruby LSP's own code (because Bundler is requiring the dependency from source)
151
149
  # - Bundled gems
152
150
  # - Default gems
153
151
  assert(
154
- indexables.all? do |i|
155
- i.full_path.start_with?(dir) ||
156
- i.full_path.start_with?(File.join(Dir.pwd, "lib")) ||
157
- i.full_path.start_with?(Bundler.bundle_path.to_s) ||
158
- i.full_path.start_with?(RbConfig::CONFIG["rubylibdir"])
152
+ uris.all? do |u|
153
+ u.full_path.start_with?(dir) ||
154
+ u.full_path.start_with?(File.join(Dir.pwd, "lib")) ||
155
+ u.full_path.start_with?(Bundler.bundle_path.to_s) ||
156
+ u.full_path.start_with?(RbConfig::CONFIG["rubylibdir"])
159
157
  end,
160
158
  )
161
159
  end
@@ -166,8 +164,75 @@ module RubyIndexer
166
164
  FileUtils.touch(File.join(dir, "find_me.rb"))
167
165
  @config.workspace_path = dir
168
166
 
169
- indexables = @config.indexables
170
- assert(indexables.find { |i| File.basename(i.full_path) == "find_me.rb" })
167
+ uris = @config.indexable_uris
168
+ assert(uris.find { |u| File.basename(u.full_path) == "find_me.rb" })
169
+ end
170
+ end
171
+
172
+ def test_transitive_dependencies_for_non_dev_gems_are_not_excluded
173
+ Dir.mktmpdir do |dir|
174
+ Dir.chdir(dir) do
175
+ # Both IRB and debug depend on reline. Since IRB is in the default group, reline should not be excluded
176
+ File.write(File.join(dir, "Gemfile"), <<~RUBY)
177
+ source "https://rubygems.org"
178
+ gem "irb"
179
+ gem "ruby-lsp", path: "#{Bundler.root}"
180
+
181
+ group :development do
182
+ gem "debug"
183
+ end
184
+ RUBY
185
+
186
+ Bundler.with_unbundled_env do
187
+ capture_subprocess_io do
188
+ system("bundle install")
189
+ end
190
+
191
+ stdout, _stderr = capture_subprocess_io do
192
+ script = [
193
+ "require \"ruby_lsp/internal\"",
194
+ "print RubyIndexer::Configuration.new.instance_variable_get(:@excluded_gems).join(\",\")",
195
+ ].join(";")
196
+ system("bundle exec ruby -e '#{script}'")
197
+ end
198
+
199
+ excluded_gems = stdout.split(",")
200
+ assert_includes(excluded_gems, "debug")
201
+ refute_includes(excluded_gems, "reline")
202
+ refute_includes(excluded_gems, "irb")
203
+ end
204
+ end
205
+ end
206
+ end
207
+
208
+ def test_does_not_fail_if_there_are_missing_specs_due_to_platform_constraints
209
+ Dir.mktmpdir do |dir|
210
+ Dir.chdir(dir) do
211
+ File.write(File.join(dir, "Gemfile"), <<~RUBY)
212
+ source "https://rubygems.org"
213
+ gem "ruby-lsp", path: "#{Bundler.root}"
214
+
215
+ platforms :windows do
216
+ gem "tzinfo"
217
+ gem "tzinfo-data"
218
+ end
219
+ RUBY
220
+
221
+ Bundler.with_unbundled_env do
222
+ capture_subprocess_io { system("bundle install") }
223
+
224
+ _stdout, stderr = capture_subprocess_io do
225
+ script = [
226
+ "require \"ruby_lsp/internal\"",
227
+ "RubyIndexer::Configuration.new.indexable_uris",
228
+ ].join(";")
229
+
230
+ system("bundle exec ruby -e '#{script}'")
231
+ end
232
+
233
+ assert_empty(stderr)
234
+ end
235
+ end
171
236
  end
172
237
  end
173
238
  end
@@ -172,7 +172,7 @@ module RubyIndexer
172
172
  end
173
173
 
174
174
  assert_match(
175
- %r{Indexing error in /fake/path/foo\.rb with 'TestEnhancement' on call node enter enhancement},
175
+ %r{Indexing error in file:///fake/path/foo\.rb with 'TestEnhancement' on call node enter enhancement},
176
176
  stderr,
177
177
  )
178
178
  # The module should still be indexed
@@ -205,7 +205,7 @@ module RubyIndexer
205
205
  end
206
206
 
207
207
  assert_match(
208
- %r{Indexing error in /fake/path/foo\.rb with 'TestEnhancement' on call node leave enhancement},
208
+ %r{Indexing error in file:///fake/path/foo\.rb with 'TestEnhancement' on call node leave enhancement},
209
209
  stderr,
210
210
  )
211
211
  # The module should still be indexed