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.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/exe/ruby-lsp +10 -9
- data/exe/ruby-lsp-check +5 -5
- data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +26 -20
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +131 -23
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +60 -30
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +73 -55
- data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +16 -14
- data/lib/{core_ext → ruby_indexer/lib/ruby_indexer}/uri.rb +29 -3
- data/lib/ruby_indexer/ruby_indexer.rb +1 -1
- data/lib/ruby_indexer/test/class_variables_test.rb +140 -0
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +11 -6
- data/lib/ruby_indexer/test/configuration_test.rb +116 -51
- data/lib/ruby_indexer/test/enhancements_test.rb +2 -2
- data/lib/ruby_indexer/test/index_test.rb +72 -43
- data/lib/ruby_indexer/test/method_test.rb +80 -0
- data/lib/ruby_indexer/test/rbs_indexer_test.rb +1 -1
- data/lib/ruby_indexer/test/reference_finder_test.rb +1 -1
- data/lib/ruby_indexer/test/test_case.rb +2 -2
- data/lib/ruby_indexer/test/uri_test.rb +72 -0
- data/lib/ruby_lsp/addon.rb +9 -0
- data/lib/ruby_lsp/base_server.rb +15 -6
- data/lib/ruby_lsp/document.rb +10 -1
- data/lib/ruby_lsp/global_state.rb +1 -1
- data/lib/ruby_lsp/internal.rb +1 -1
- data/lib/ruby_lsp/listeners/code_lens.rb +8 -4
- data/lib/ruby_lsp/listeners/completion.rb +73 -4
- data/lib/ruby_lsp/listeners/definition.rb +73 -17
- data/lib/ruby_lsp/listeners/document_symbol.rb +49 -5
- data/lib/ruby_lsp/listeners/folding_ranges.rb +1 -1
- data/lib/ruby_lsp/listeners/hover.rb +57 -0
- data/lib/ruby_lsp/requests/completion.rb +6 -0
- data/lib/ruby_lsp/requests/completion_resolve.rb +2 -1
- data/lib/ruby_lsp/requests/definition.rb +6 -0
- data/lib/ruby_lsp/requests/prepare_rename.rb +51 -0
- data/lib/ruby_lsp/requests/prepare_type_hierarchy.rb +1 -1
- data/lib/ruby_lsp/requests/rename.rb +14 -4
- data/lib/ruby_lsp/requests/support/common.rb +1 -5
- data/lib/ruby_lsp/requests/type_hierarchy_supertypes.rb +1 -1
- data/lib/ruby_lsp/requests/workspace_symbol.rb +3 -2
- data/lib/ruby_lsp/scripts/compose_bundle.rb +1 -1
- data/lib/ruby_lsp/server.rb +42 -7
- data/lib/ruby_lsp/setup_bundler.rb +54 -46
- data/lib/ruby_lsp/test_helper.rb +45 -11
- data/lib/ruby_lsp/type_inferrer.rb +22 -0
- data/lib/ruby_lsp/utils.rb +3 -0
- metadata +7 -8
- 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(
|
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
|
-
|
622
|
-
|
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.
|
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
|
-
|
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(
|
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
|
-
|
16
|
+
uris = @config.indexable_uris
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
assert(
|
21
|
-
assert(
|
22
|
-
assert(
|
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
|
27
|
+
def test_indexable_uris_have_expanded_full_paths
|
26
28
|
@config.apply_config({ "included_patterns" => ["**/*.rb"] })
|
27
|
-
|
29
|
+
uris = @config.indexable_uris
|
28
30
|
|
29
31
|
# All paths should be expanded
|
30
|
-
assert(
|
32
|
+
assert(uris.all? { |uri| File.absolute_path?(uri.full_path) })
|
31
33
|
end
|
32
34
|
|
33
|
-
def
|
34
|
-
|
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(
|
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
|
47
|
-
|
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
|
55
|
-
|
53
|
+
def test_indexable_uris_includes_default_gems
|
54
|
+
paths = @config.indexable_uris.map(&:full_path)
|
56
55
|
|
57
|
-
assert_includes(
|
58
|
-
assert_includes(
|
59
|
-
assert_includes(
|
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
|
63
|
-
|
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(
|
67
|
+
assert_includes(paths, path)
|
69
68
|
end
|
70
69
|
end
|
71
70
|
|
72
|
-
def
|
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
|
81
|
-
|
82
|
-
|
83
|
-
|
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
|
-
|
88
|
-
"
|
89
|
-
"Included: #{
|
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
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
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
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
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
|
-
|
170
|
-
assert(
|
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
|
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
|
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
|