ruby-lsp 0.22.1 → 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 +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
|