ruby-lsp 0.22.1 → 0.23.0
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.
- 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 +88 -22
- 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/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/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 +12 -1
- 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 +31 -41
- 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
@@ -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
|
-
# "
|
33
|
-
# "
|
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
|
-
@
|
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[
|
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(
|
59
|
-
def delete(
|
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
|
-
@
|
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
|
-
@
|
82
|
+
@uris_to_entries.delete(key)
|
81
83
|
|
82
|
-
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
|
-
(@
|
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[
|
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
|
346
|
-
#
|
347
|
-
#
|
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
|
-
|
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(
|
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 = (
|
368
|
+
progress_step = (uris.length / 100.0).ceil
|
367
369
|
|
368
|
-
|
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
|
-
|
376
|
+
index_file(uri, collect_comments: false)
|
375
377
|
end
|
376
378
|
end
|
377
379
|
|
378
|
-
sig { params(
|
379
|
-
def index_single(
|
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(
|
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
|
-
|
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
|
-
|
399
|
-
|
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 #{
|
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
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
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(
|
610
|
-
index_single(
|
627
|
+
delete(uri)
|
628
|
+
index_single(uri, source)
|
611
629
|
|
612
|
-
updated_entries = @
|
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.
|
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
|
-
|
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(
|
683
|
-
entries = @
|
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
|
-
|
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,
|
64
|
+
Entry::Class.new(nesting, uri, location, location, comments, parent_class)
|
65
65
|
else
|
66
|
-
Entry::Module.new(nesting,
|
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,
|
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
|
-
|
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
|
-
|
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],
|
264
|
-
def handle_constant(declaration, nesting,
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
17
|
-
|
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/
|
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"
|
@@ -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
|