ruby-lsp 0.17.16 → 0.18.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/README.md +4 -110
- data/VERSION +1 -1
- data/exe/ruby-lsp +10 -8
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +14 -6
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +157 -27
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +31 -12
- data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +2 -2
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +10 -10
- data/lib/ruby_indexer/test/constant_test.rb +4 -4
- data/lib/ruby_indexer/test/enhancements_test.rb +2 -2
- data/lib/ruby_indexer/test/index_test.rb +41 -0
- data/lib/ruby_indexer/test/method_test.rb +257 -2
- data/lib/ruby_indexer/test/rbs_indexer_test.rb +1 -1
- data/lib/ruby_lsp/addon.rb +3 -2
- data/lib/ruby_lsp/base_server.rb +21 -1
- data/lib/ruby_lsp/document.rb +5 -3
- data/lib/ruby_lsp/erb_document.rb +29 -10
- data/lib/ruby_lsp/global_state.rb +15 -1
- data/lib/ruby_lsp/listeners/code_lens.rb +34 -5
- data/lib/ruby_lsp/listeners/folding_ranges.rb +1 -1
- data/lib/ruby_lsp/listeners/semantic_highlighting.rb +28 -0
- data/lib/ruby_lsp/listeners/signature_help.rb +55 -24
- data/lib/ruby_lsp/rbs_document.rb +5 -4
- data/lib/ruby_lsp/requests/code_action_resolve.rb +0 -15
- data/lib/ruby_lsp/requests/code_actions.rb +0 -10
- data/lib/ruby_lsp/requests/code_lens.rb +1 -11
- data/lib/ruby_lsp/requests/completion.rb +3 -20
- data/lib/ruby_lsp/requests/completion_resolve.rb +0 -8
- data/lib/ruby_lsp/requests/definition.rb +6 -20
- data/lib/ruby_lsp/requests/diagnostics.rb +0 -10
- data/lib/ruby_lsp/requests/document_highlight.rb +7 -14
- data/lib/ruby_lsp/requests/document_link.rb +0 -10
- data/lib/ruby_lsp/requests/document_symbol.rb +0 -17
- data/lib/ruby_lsp/requests/folding_ranges.rb +0 -10
- data/lib/ruby_lsp/requests/formatting.rb +3 -17
- data/lib/ruby_lsp/requests/hover.rb +9 -9
- data/lib/ruby_lsp/requests/inlay_hints.rb +0 -30
- data/lib/ruby_lsp/requests/on_type_formatting.rb +0 -10
- data/lib/ruby_lsp/requests/prepare_type_hierarchy.rb +0 -11
- data/lib/ruby_lsp/requests/request.rb +17 -1
- data/lib/ruby_lsp/requests/selection_ranges.rb +0 -10
- data/lib/ruby_lsp/requests/semantic_highlighting.rb +1 -23
- data/lib/ruby_lsp/requests/show_syntax_tree.rb +0 -11
- data/lib/ruby_lsp/requests/signature_help.rb +5 -20
- data/lib/ruby_lsp/requests/support/common.rb +1 -1
- data/lib/ruby_lsp/requests/support/rubocop_runner.rb +2 -0
- data/lib/ruby_lsp/requests/type_hierarchy_supertypes.rb +0 -11
- data/lib/ruby_lsp/requests/workspace_symbol.rb +0 -12
- data/lib/ruby_lsp/ruby_document.rb +4 -3
- data/lib/ruby_lsp/server.rb +23 -8
- data/lib/ruby_lsp/setup_bundler.rb +31 -13
- data/lib/ruby_lsp/type_inferrer.rb +6 -2
- data/lib/ruby_lsp/utils.rb +11 -1
- metadata +7 -14
- data/lib/ruby_lsp/check_docs.rb +0 -130
@@ -213,10 +213,10 @@ module RubyIndexer
|
|
213
213
|
RUBY
|
214
214
|
|
215
215
|
foo_entry = @index["Foo"].first
|
216
|
-
assert_equal("This is a Foo comment\nThis is another Foo comment", foo_entry.comments
|
216
|
+
assert_equal("This is a Foo comment\nThis is another Foo comment", foo_entry.comments)
|
217
217
|
|
218
218
|
bar_entry = @index["Bar"].first
|
219
|
-
assert_equal("This Bar comment has 1 line padding", bar_entry.comments
|
219
|
+
assert_equal("This Bar comment has 1 line padding", bar_entry.comments)
|
220
220
|
end
|
221
221
|
|
222
222
|
def test_skips_comments_containing_invalid_encodings
|
@@ -239,10 +239,10 @@ module RubyIndexer
|
|
239
239
|
RUBY
|
240
240
|
|
241
241
|
foo_entry = @index["Foo"].first
|
242
|
-
assert_equal("This is a Foo comment\nThis is another Foo comment", foo_entry.comments
|
242
|
+
assert_equal("This is a Foo comment\nThis is another Foo comment", foo_entry.comments)
|
243
243
|
|
244
244
|
bar_entry = @index["Foo::Bar"].first
|
245
|
-
assert_equal("This is a Bar comment", bar_entry.comments
|
245
|
+
assert_equal("This is a Bar comment", bar_entry.comments)
|
246
246
|
end
|
247
247
|
|
248
248
|
def test_comments_can_be_attached_to_a_reopened_class
|
@@ -255,10 +255,10 @@ module RubyIndexer
|
|
255
255
|
RUBY
|
256
256
|
|
257
257
|
first_foo_entry = @index["Foo"][0]
|
258
|
-
assert_equal("This is a Foo comment", first_foo_entry.comments
|
258
|
+
assert_equal("This is a Foo comment", first_foo_entry.comments)
|
259
259
|
|
260
260
|
second_foo_entry = @index["Foo"][1]
|
261
|
-
assert_equal("This is another Foo comment", second_foo_entry.comments
|
261
|
+
assert_equal("This is another Foo comment", second_foo_entry.comments)
|
262
262
|
end
|
263
263
|
|
264
264
|
def test_comments_removes_the_leading_pound_and_space
|
@@ -271,10 +271,10 @@ module RubyIndexer
|
|
271
271
|
RUBY
|
272
272
|
|
273
273
|
first_foo_entry = @index["Foo"][0]
|
274
|
-
assert_equal("This is a Foo comment", first_foo_entry.comments
|
274
|
+
assert_equal("This is a Foo comment", first_foo_entry.comments)
|
275
275
|
|
276
276
|
second_foo_entry = @index["Bar"][0]
|
277
|
-
assert_equal("This is a Bar comment", second_foo_entry.comments
|
277
|
+
assert_equal("This is a Bar comment", second_foo_entry.comments)
|
278
278
|
end
|
279
279
|
|
280
280
|
def test_private_class_and_module_indexing
|
@@ -483,7 +483,7 @@ module RubyIndexer
|
|
483
483
|
|
484
484
|
foo = T.must(@index["Foo::<Class:Foo>"].first)
|
485
485
|
assert_equal(4, foo.location.start_line)
|
486
|
-
assert_equal("Some extra comments", foo.comments
|
486
|
+
assert_equal("Some extra comments", foo.comments)
|
487
487
|
end
|
488
488
|
|
489
489
|
def test_dynamic_singleton_class_blocks
|
@@ -501,7 +501,7 @@ module RubyIndexer
|
|
501
501
|
# That pattern cannot be properly analyzed statically and assuming that it's always a regular singleton simplifies
|
502
502
|
# the implementation considerably.
|
503
503
|
assert_equal(3, singleton.location.start_line)
|
504
|
-
assert_equal("Some extra comments", singleton.comments
|
504
|
+
assert_equal("Some extra comments", singleton.comments)
|
505
505
|
end
|
506
506
|
|
507
507
|
def test_namespaces_inside_singleton_blocks
|
@@ -86,16 +86,16 @@ module RubyIndexer
|
|
86
86
|
A::BAZ = 1
|
87
87
|
RUBY
|
88
88
|
|
89
|
-
foo_comment = @index["FOO"].first.comments
|
89
|
+
foo_comment = @index["FOO"].first.comments
|
90
90
|
assert_equal("FOO comment", foo_comment)
|
91
91
|
|
92
|
-
a_foo_comment = @index["A::FOO"].first.comments
|
92
|
+
a_foo_comment = @index["A::FOO"].first.comments
|
93
93
|
assert_equal("A::FOO comment", a_foo_comment)
|
94
94
|
|
95
|
-
bar_comment = @index["BAR"].first.comments
|
95
|
+
bar_comment = @index["BAR"].first.comments
|
96
96
|
assert_equal("::BAR comment", bar_comment)
|
97
97
|
|
98
|
-
a_baz_comment = @index["A::BAZ"].first.comments
|
98
|
+
a_baz_comment = @index["A::BAZ"].first.comments
|
99
99
|
assert_equal("A::BAZ comment", a_baz_comment)
|
100
100
|
end
|
101
101
|
|
@@ -38,7 +38,7 @@ module RubyIndexer
|
|
38
38
|
file_path,
|
39
39
|
location,
|
40
40
|
location,
|
41
|
-
|
41
|
+
nil,
|
42
42
|
[Entry::Signature.new([Entry::RequiredParameter.new(name: :a)])],
|
43
43
|
Entry::Visibility::PUBLIC,
|
44
44
|
owner,
|
@@ -120,7 +120,7 @@ module RubyIndexer
|
|
120
120
|
file_path,
|
121
121
|
location,
|
122
122
|
location,
|
123
|
-
|
123
|
+
nil,
|
124
124
|
[],
|
125
125
|
Entry::Visibility::PUBLIC,
|
126
126
|
owner,
|
@@ -1822,5 +1822,46 @@ module RubyIndexer
|
|
1822
1822
|
@index.linearized_ancestors_of("Foo::Child::<Class:Child>"),
|
1823
1823
|
)
|
1824
1824
|
end
|
1825
|
+
|
1826
|
+
def test_resolving_circular_method_aliases_on_class_reopen
|
1827
|
+
index(<<~RUBY)
|
1828
|
+
class Foo
|
1829
|
+
alias bar ==
|
1830
|
+
def ==(other) = true
|
1831
|
+
end
|
1832
|
+
|
1833
|
+
class Foo
|
1834
|
+
alias == bar
|
1835
|
+
end
|
1836
|
+
RUBY
|
1837
|
+
|
1838
|
+
method = @index.resolve_method("==", "Foo").first
|
1839
|
+
assert_kind_of(Entry::Method, method)
|
1840
|
+
assert_equal("==", method.name)
|
1841
|
+
|
1842
|
+
candidates = @index.method_completion_candidates("=", "Foo")
|
1843
|
+
assert_equal(["==", "==="], candidates.map(&:name))
|
1844
|
+
end
|
1845
|
+
|
1846
|
+
def test_entries_for
|
1847
|
+
index(<<~RUBY)
|
1848
|
+
class Foo; end
|
1849
|
+
|
1850
|
+
module Bar
|
1851
|
+
def my_def; end
|
1852
|
+
def self.my_singleton_def; end
|
1853
|
+
end
|
1854
|
+
RUBY
|
1855
|
+
|
1856
|
+
entries = @index.entries_for("/fake/path/foo.rb", Entry)
|
1857
|
+
assert_equal(["Foo", "Bar", "my_def", "Bar::<Class:Bar>", "my_singleton_def"], entries.map(&:name))
|
1858
|
+
|
1859
|
+
entries = @index.entries_for("/fake/path/foo.rb", RubyIndexer::Entry::Namespace)
|
1860
|
+
assert_equal(["Foo", "Bar", "Bar::<Class:Bar>"], entries.map(&:name))
|
1861
|
+
end
|
1862
|
+
|
1863
|
+
def test_entries_for_returns_nil_if_no_matches
|
1864
|
+
assert_nil(@index.entries_for("non_existing_file.rb", Entry::Namespace))
|
1865
|
+
end
|
1825
1866
|
end
|
1826
1867
|
end
|
@@ -330,6 +330,33 @@ module RubyIndexer
|
|
330
330
|
assert_empty(parameters)
|
331
331
|
end
|
332
332
|
|
333
|
+
def test_methods_with_argument_forwarding
|
334
|
+
index(<<~RUBY)
|
335
|
+
class Foo
|
336
|
+
def bar(...)
|
337
|
+
end
|
338
|
+
|
339
|
+
def baz(a, ...)
|
340
|
+
end
|
341
|
+
end
|
342
|
+
RUBY
|
343
|
+
|
344
|
+
entry = T.must(@index["bar"].first)
|
345
|
+
assert_instance_of(Entry::Method, entry, "Expected `bar` to be indexed")
|
346
|
+
|
347
|
+
parameters = entry.signatures.first.parameters
|
348
|
+
assert_equal(1, parameters.length)
|
349
|
+
assert_instance_of(Entry::ForwardingParameter, parameters.first)
|
350
|
+
|
351
|
+
entry = T.must(@index["baz"].first)
|
352
|
+
assert_instance_of(Entry::Method, entry, "Expected `baz` to be indexed")
|
353
|
+
|
354
|
+
parameters = entry.signatures.first.parameters
|
355
|
+
assert_equal(2, parameters.length)
|
356
|
+
assert_instance_of(Entry::RequiredParameter, parameters[0])
|
357
|
+
assert_instance_of(Entry::ForwardingParameter, parameters[1])
|
358
|
+
end
|
359
|
+
|
333
360
|
def test_keeps_track_of_method_owner
|
334
361
|
index(<<~RUBY)
|
335
362
|
class Foo
|
@@ -355,9 +382,9 @@ module RubyIndexer
|
|
355
382
|
RUBY
|
356
383
|
|
357
384
|
assert_entry("bar", Entry::Accessor, "/fake/path/foo.rb:2-15:2-18")
|
358
|
-
assert_equal("Hello there", @index["bar"].first.comments
|
385
|
+
assert_equal("Hello there", @index["bar"].first.comments)
|
359
386
|
assert_entry("other", Entry::Accessor, "/fake/path/foo.rb:2-21:2-26")
|
360
|
-
assert_equal("Hello there", @index["other"].first.comments
|
387
|
+
assert_equal("Hello there", @index["other"].first.comments)
|
361
388
|
assert_entry("baz=", Entry::Accessor, "/fake/path/foo.rb:3-15:3-18")
|
362
389
|
assert_entry("qux", Entry::Accessor, "/fake/path/foo.rb:4-17:4-20")
|
363
390
|
assert_entry("qux=", Entry::Accessor, "/fake/path/foo.rb:4-17:4-20")
|
@@ -461,5 +488,233 @@ module RubyIndexer
|
|
461
488
|
assert_equal(6, name_location.start_column)
|
462
489
|
assert_equal(9, name_location.end_column)
|
463
490
|
end
|
491
|
+
|
492
|
+
def test_signature_matches_for_a_method_with_positional_params
|
493
|
+
index(<<~RUBY)
|
494
|
+
class Foo
|
495
|
+
def bar(a, b = 123)
|
496
|
+
end
|
497
|
+
end
|
498
|
+
RUBY
|
499
|
+
|
500
|
+
entry = T.must(@index["bar"].first)
|
501
|
+
|
502
|
+
# Matching calls
|
503
|
+
assert_signature_matches(entry, "bar()")
|
504
|
+
assert_signature_matches(entry, "bar(1)")
|
505
|
+
assert_signature_matches(entry, "bar(1, 2)")
|
506
|
+
assert_signature_matches(entry, "bar(...)")
|
507
|
+
assert_signature_matches(entry, "bar(1, ...)")
|
508
|
+
assert_signature_matches(entry, "bar(*a)")
|
509
|
+
assert_signature_matches(entry, "bar(1, *a)")
|
510
|
+
assert_signature_matches(entry, "bar(1, *a, 2)")
|
511
|
+
assert_signature_matches(entry, "bar(*a, 2)")
|
512
|
+
assert_signature_matches(entry, "bar(1, **a)")
|
513
|
+
assert_signature_matches(entry, "bar(1) {}")
|
514
|
+
# This call is impossible to analyze statically because it depends on whether there are elements inside `a` or
|
515
|
+
# not. If there's nothing, the call will fail. But if there's anything inside, the hash will become the first
|
516
|
+
# positional argument
|
517
|
+
assert_signature_matches(entry, "bar(**a)")
|
518
|
+
|
519
|
+
# Non matching calls
|
520
|
+
|
521
|
+
refute_signature_matches(entry, "bar(1, 2, 3)")
|
522
|
+
refute_signature_matches(entry, "bar(1, b: 2)")
|
523
|
+
refute_signature_matches(entry, "bar(1, 2, c: 3)")
|
524
|
+
end
|
525
|
+
|
526
|
+
def test_signature_matches_for_a_method_with_argument_forwarding
|
527
|
+
index(<<~RUBY)
|
528
|
+
class Foo
|
529
|
+
def bar(...)
|
530
|
+
end
|
531
|
+
end
|
532
|
+
RUBY
|
533
|
+
|
534
|
+
entry = T.must(@index["bar"].first)
|
535
|
+
|
536
|
+
# All calls match a forwarding parameter
|
537
|
+
assert_signature_matches(entry, "bar(1)")
|
538
|
+
assert_signature_matches(entry, "bar(1, 2)")
|
539
|
+
assert_signature_matches(entry, "bar(...)")
|
540
|
+
assert_signature_matches(entry, "bar(1, ...)")
|
541
|
+
assert_signature_matches(entry, "bar(*a)")
|
542
|
+
assert_signature_matches(entry, "bar(1, *a)")
|
543
|
+
assert_signature_matches(entry, "bar(1, *a, 2)")
|
544
|
+
assert_signature_matches(entry, "bar(*a, 2)")
|
545
|
+
assert_signature_matches(entry, "bar(1, **a)")
|
546
|
+
assert_signature_matches(entry, "bar(1) {}")
|
547
|
+
assert_signature_matches(entry, "bar()")
|
548
|
+
assert_signature_matches(entry, "bar(1, 2, 3)")
|
549
|
+
assert_signature_matches(entry, "bar(1, 2, a: 1, b: 5) {}")
|
550
|
+
end
|
551
|
+
|
552
|
+
def test_signature_matches_for_post_forwarding_parameter
|
553
|
+
index(<<~RUBY)
|
554
|
+
class Foo
|
555
|
+
def bar(a, ...)
|
556
|
+
end
|
557
|
+
end
|
558
|
+
RUBY
|
559
|
+
|
560
|
+
entry = T.must(@index["bar"].first)
|
561
|
+
|
562
|
+
# All calls with at least one positional argument match
|
563
|
+
assert_signature_matches(entry, "bar(1)")
|
564
|
+
assert_signature_matches(entry, "bar(1, 2)")
|
565
|
+
assert_signature_matches(entry, "bar(...)")
|
566
|
+
assert_signature_matches(entry, "bar(1, ...)")
|
567
|
+
assert_signature_matches(entry, "bar(*a)")
|
568
|
+
assert_signature_matches(entry, "bar(1, *a)")
|
569
|
+
assert_signature_matches(entry, "bar(1, *a, 2)")
|
570
|
+
assert_signature_matches(entry, "bar(*a, 2)")
|
571
|
+
assert_signature_matches(entry, "bar(1, **a)")
|
572
|
+
assert_signature_matches(entry, "bar(1) {}")
|
573
|
+
assert_signature_matches(entry, "bar(1, 2, 3)")
|
574
|
+
assert_signature_matches(entry, "bar(1, 2, a: 1, b: 5) {}")
|
575
|
+
assert_signature_matches(entry, "bar()")
|
576
|
+
end
|
577
|
+
|
578
|
+
def test_signature_matches_for_destructured_parameters
|
579
|
+
index(<<~RUBY)
|
580
|
+
class Foo
|
581
|
+
def bar(a, (b, c))
|
582
|
+
end
|
583
|
+
end
|
584
|
+
RUBY
|
585
|
+
|
586
|
+
entry = T.must(@index["bar"].first)
|
587
|
+
|
588
|
+
# All calls with at least one positional argument match
|
589
|
+
assert_signature_matches(entry, "bar()")
|
590
|
+
assert_signature_matches(entry, "bar(1)")
|
591
|
+
assert_signature_matches(entry, "bar(1, 2)")
|
592
|
+
assert_signature_matches(entry, "bar(...)")
|
593
|
+
assert_signature_matches(entry, "bar(1, ...)")
|
594
|
+
assert_signature_matches(entry, "bar(*a)")
|
595
|
+
assert_signature_matches(entry, "bar(1, *a)")
|
596
|
+
assert_signature_matches(entry, "bar(*a, 2)")
|
597
|
+
# This matches because `bar(1, *[], 2)` would result in `bar(1, 2)`, which is a valid call
|
598
|
+
assert_signature_matches(entry, "bar(1, *a, 2)")
|
599
|
+
assert_signature_matches(entry, "bar(1, **a)")
|
600
|
+
assert_signature_matches(entry, "bar(1) {}")
|
601
|
+
|
602
|
+
refute_signature_matches(entry, "bar(1, 2, 3)")
|
603
|
+
refute_signature_matches(entry, "bar(1, 2, a: 1, b: 5) {}")
|
604
|
+
end
|
605
|
+
|
606
|
+
def test_signature_matches_for_post_parameters
|
607
|
+
index(<<~RUBY)
|
608
|
+
class Foo
|
609
|
+
def bar(*splat, a)
|
610
|
+
end
|
611
|
+
end
|
612
|
+
RUBY
|
613
|
+
|
614
|
+
entry = T.must(@index["bar"].first)
|
615
|
+
|
616
|
+
# All calls with at least one positional argument match
|
617
|
+
assert_signature_matches(entry, "bar(1)")
|
618
|
+
assert_signature_matches(entry, "bar(1, 2)")
|
619
|
+
assert_signature_matches(entry, "bar(...)")
|
620
|
+
assert_signature_matches(entry, "bar(1, ...)")
|
621
|
+
assert_signature_matches(entry, "bar(*a)")
|
622
|
+
assert_signature_matches(entry, "bar(1, *a)")
|
623
|
+
assert_signature_matches(entry, "bar(*a, 2)")
|
624
|
+
assert_signature_matches(entry, "bar(1, *a, 2)")
|
625
|
+
assert_signature_matches(entry, "bar(1, **a)")
|
626
|
+
assert_signature_matches(entry, "bar(1, 2, 3)")
|
627
|
+
assert_signature_matches(entry, "bar(1) {}")
|
628
|
+
assert_signature_matches(entry, "bar()")
|
629
|
+
|
630
|
+
refute_signature_matches(entry, "bar(1, 2, a: 1, b: 5) {}")
|
631
|
+
end
|
632
|
+
|
633
|
+
def test_signature_matches_for_keyword_parameters
|
634
|
+
index(<<~RUBY)
|
635
|
+
class Foo
|
636
|
+
def bar(a:, b: 123)
|
637
|
+
end
|
638
|
+
end
|
639
|
+
RUBY
|
640
|
+
|
641
|
+
entry = T.must(@index["bar"].first)
|
642
|
+
|
643
|
+
assert_signature_matches(entry, "bar(...)")
|
644
|
+
assert_signature_matches(entry, "bar()")
|
645
|
+
assert_signature_matches(entry, "bar(a: 1)")
|
646
|
+
assert_signature_matches(entry, "bar(a: 1, b: 32)")
|
647
|
+
|
648
|
+
refute_signature_matches(entry, "bar(a: 1, c: 2)")
|
649
|
+
refute_signature_matches(entry, "bar(1, ...)")
|
650
|
+
refute_signature_matches(entry, "bar(1) {}")
|
651
|
+
refute_signature_matches(entry, "bar(1, *a)")
|
652
|
+
refute_signature_matches(entry, "bar(*a, 2)")
|
653
|
+
refute_signature_matches(entry, "bar(1, *a, 2)")
|
654
|
+
refute_signature_matches(entry, "bar(1, **a)")
|
655
|
+
refute_signature_matches(entry, "bar(*a)")
|
656
|
+
refute_signature_matches(entry, "bar(1)")
|
657
|
+
refute_signature_matches(entry, "bar(1, 2)")
|
658
|
+
refute_signature_matches(entry, "bar(1, 2, a: 1, b: 5) {}")
|
659
|
+
end
|
660
|
+
|
661
|
+
def test_signature_matches_for_keyword_splats
|
662
|
+
index(<<~RUBY)
|
663
|
+
class Foo
|
664
|
+
def bar(a, b:, **kwargs)
|
665
|
+
end
|
666
|
+
end
|
667
|
+
RUBY
|
668
|
+
|
669
|
+
entry = T.must(@index["bar"].first)
|
670
|
+
|
671
|
+
assert_signature_matches(entry, "bar(...)")
|
672
|
+
assert_signature_matches(entry, "bar()")
|
673
|
+
assert_signature_matches(entry, "bar(1)")
|
674
|
+
assert_signature_matches(entry, "bar(1, b: 2)")
|
675
|
+
assert_signature_matches(entry, "bar(1, b: 2, c: 3, d: 4)")
|
676
|
+
|
677
|
+
refute_signature_matches(entry, "bar(1, 2, b: 2)")
|
678
|
+
end
|
679
|
+
|
680
|
+
def test_partial_signature_matches
|
681
|
+
# It's important to match signatures partially, because we want to figure out which signature we should show while
|
682
|
+
# the user is in the middle of typing
|
683
|
+
index(<<~RUBY)
|
684
|
+
class Foo
|
685
|
+
def bar(a:, b:)
|
686
|
+
end
|
687
|
+
|
688
|
+
def baz(a, b)
|
689
|
+
end
|
690
|
+
end
|
691
|
+
RUBY
|
692
|
+
|
693
|
+
entry = T.must(@index["bar"].first)
|
694
|
+
assert_signature_matches(entry, "bar(a: 1)")
|
695
|
+
|
696
|
+
entry = T.must(@index["baz"].first)
|
697
|
+
assert_signature_matches(entry, "baz(1)")
|
698
|
+
end
|
699
|
+
|
700
|
+
private
|
701
|
+
|
702
|
+
sig { params(entry: Entry::Method, call_string: String).void }
|
703
|
+
def assert_signature_matches(entry, call_string)
|
704
|
+
sig = T.must(entry.signatures.first)
|
705
|
+
arguments = parse_prism_args(call_string)
|
706
|
+
assert(sig.matches?(arguments), "Expected #{call_string} to match #{entry.name}#{entry.decorated_parameters}")
|
707
|
+
end
|
708
|
+
|
709
|
+
sig { params(entry: Entry::Method, call_string: String).void }
|
710
|
+
def refute_signature_matches(entry, call_string)
|
711
|
+
sig = T.must(entry.signatures.first)
|
712
|
+
arguments = parse_prism_args(call_string)
|
713
|
+
refute(sig.matches?(arguments), "Expected #{call_string} to not match #{entry.name}#{entry.decorated_parameters}")
|
714
|
+
end
|
715
|
+
|
716
|
+
def parse_prism_args(s)
|
717
|
+
Array(Prism.parse(s).value.statements.body.first.arguments&.arguments)
|
718
|
+
end
|
464
719
|
end
|
465
720
|
end
|
@@ -345,7 +345,7 @@ module RubyIndexer
|
|
345
345
|
assert_equal("all?", entry.old_name)
|
346
346
|
assert_equal("Array", entry.owner.name)
|
347
347
|
assert(entry.file_path.end_with?("core/array.rbs"))
|
348
|
-
assert_includes(entry.comments
|
348
|
+
assert_includes(entry.comments, "Returns `true` if any element of `self` meets a given criterion.")
|
349
349
|
end
|
350
350
|
|
351
351
|
private
|
data/lib/ruby_lsp/addon.rb
CHANGED
@@ -30,6 +30,8 @@ module RubyLsp
|
|
30
30
|
# Addon instances that have declared a handler to accept file watcher events
|
31
31
|
@file_watcher_addons = T.let([], T::Array[Addon])
|
32
32
|
|
33
|
+
AddonNotFoundError = Class.new(StandardError)
|
34
|
+
|
33
35
|
class << self
|
34
36
|
extend T::Sig
|
35
37
|
|
@@ -78,11 +80,10 @@ module RubyLsp
|
|
78
80
|
errors
|
79
81
|
end
|
80
82
|
|
81
|
-
# Intended for use by tests for addons
|
82
83
|
sig { params(addon_name: String).returns(Addon) }
|
83
84
|
def get(addon_name)
|
84
85
|
addon = addons.find { |addon| addon.name == addon_name }
|
85
|
-
raise "Could not find addon '#{addon_name}'" unless addon
|
86
|
+
raise AddonNotFoundError, "Could not find addon '#{addon_name}'" unless addon
|
86
87
|
|
87
88
|
addon
|
88
89
|
end
|
data/lib/ruby_lsp/base_server.rb
CHANGED
@@ -31,6 +31,7 @@ module RubyLsp
|
|
31
31
|
Thread,
|
32
32
|
)
|
33
33
|
|
34
|
+
@global_state = T.let(GlobalState.new, GlobalState)
|
34
35
|
Thread.main.priority = 1
|
35
36
|
end
|
36
37
|
|
@@ -52,7 +53,26 @@ module RubyLsp
|
|
52
53
|
message[:params][:textDocument][:uri] = parsed_uri
|
53
54
|
|
54
55
|
# We don't want to try to parse documents on text synchronization notifications
|
55
|
-
|
56
|
+
unless method.start_with?("textDocument/did")
|
57
|
+
document = @store.get(parsed_uri)
|
58
|
+
|
59
|
+
# If the client supports request delegation and we're working with an ERB document and there was
|
60
|
+
# something to parse, then we have to maintain the client updated about the virtual state of the host
|
61
|
+
# language source
|
62
|
+
if document.parse! && @global_state.supports_request_delegation && document.is_a?(ERBDocument)
|
63
|
+
send_message(
|
64
|
+
Notification.new(
|
65
|
+
method: "delegate/textDocument/virtualState",
|
66
|
+
params: {
|
67
|
+
textDocument: {
|
68
|
+
uri: uri,
|
69
|
+
text: document.host_language_source,
|
70
|
+
},
|
71
|
+
},
|
72
|
+
),
|
73
|
+
)
|
74
|
+
end
|
75
|
+
end
|
56
76
|
rescue Store::NonExistingDocumentError
|
57
77
|
# If we receive a request for a file that no longer exists, we don't want to fail
|
58
78
|
end
|
data/lib/ruby_lsp/document.rb
CHANGED
@@ -51,7 +51,8 @@ module RubyLsp
|
|
51
51
|
@version = T.let(version, Integer)
|
52
52
|
@uri = T.let(uri, URI::Generic)
|
53
53
|
@needs_parsing = T.let(true, T::Boolean)
|
54
|
-
@parse_result = T.let(
|
54
|
+
@parse_result = T.let(T.unsafe(nil), ParseResultType)
|
55
|
+
parse!
|
55
56
|
end
|
56
57
|
|
57
58
|
sig { params(other: Document[T.untyped]).returns(T::Boolean) }
|
@@ -106,8 +107,9 @@ module RubyLsp
|
|
106
107
|
@cache.clear
|
107
108
|
end
|
108
109
|
|
109
|
-
|
110
|
-
|
110
|
+
# Returns `true` if the document was parsed and `false` if nothing needed parsing
|
111
|
+
sig { abstract.returns(T::Boolean) }
|
112
|
+
def parse!; end
|
111
113
|
|
112
114
|
sig { abstract.returns(T::Boolean) }
|
113
115
|
def syntax_error?; end
|
@@ -6,17 +6,30 @@ module RubyLsp
|
|
6
6
|
extend T::Sig
|
7
7
|
extend T::Generic
|
8
8
|
|
9
|
+
sig { returns(String) }
|
10
|
+
attr_reader :host_language_source
|
11
|
+
|
9
12
|
ParseResultType = type_member { { fixed: Prism::ParseResult } }
|
10
13
|
|
11
|
-
sig {
|
12
|
-
def
|
13
|
-
|
14
|
+
sig { params(source: String, version: Integer, uri: URI::Generic, encoding: Encoding).void }
|
15
|
+
def initialize(source:, version:, uri:, encoding: Encoding::UTF_8)
|
16
|
+
# This has to be initialized before calling super because we call `parse` in the parent constructor, which
|
17
|
+
# overrides this with the proper virtual host language source
|
18
|
+
@host_language_source = T.let("", String)
|
19
|
+
super
|
20
|
+
end
|
21
|
+
|
22
|
+
sig { override.returns(T::Boolean) }
|
23
|
+
def parse!
|
24
|
+
return false unless @needs_parsing
|
14
25
|
|
15
26
|
@needs_parsing = false
|
16
27
|
scanner = ERBScanner.new(@source)
|
17
28
|
scanner.scan
|
29
|
+
@host_language_source = scanner.host_language
|
18
30
|
# assigning empty scopes to turn Prism into eval mode
|
19
31
|
@parse_result = Prism.parse(scanner.ruby, scopes: [[]])
|
32
|
+
true
|
20
33
|
end
|
21
34
|
|
22
35
|
sig { override.returns(T::Boolean) }
|
@@ -39,16 +52,22 @@ module RubyLsp
|
|
39
52
|
RubyDocument.locate(@parse_result.value, create_scanner.find_char_position(position), node_types: node_types)
|
40
53
|
end
|
41
54
|
|
55
|
+
sig { params(char_position: Integer).returns(T.nilable(T::Boolean)) }
|
56
|
+
def inside_host_language?(char_position)
|
57
|
+
char = @host_language_source[char_position]
|
58
|
+
char && char != " "
|
59
|
+
end
|
60
|
+
|
42
61
|
class ERBScanner
|
43
62
|
extend T::Sig
|
44
63
|
|
45
64
|
sig { returns(String) }
|
46
|
-
attr_reader :ruby, :
|
65
|
+
attr_reader :ruby, :host_language
|
47
66
|
|
48
67
|
sig { params(source: String).void }
|
49
68
|
def initialize(source)
|
50
69
|
@source = source
|
51
|
-
@
|
70
|
+
@host_language = T.let(+"", String)
|
52
71
|
@ruby = T.let(+"", String)
|
53
72
|
@current_pos = T.let(0, Integer)
|
54
73
|
@inside_ruby = T.let(false, T::Boolean)
|
@@ -104,16 +123,16 @@ module RubyLsp
|
|
104
123
|
end
|
105
124
|
when "\r"
|
106
125
|
@ruby << char
|
107
|
-
@
|
126
|
+
@host_language << char
|
108
127
|
|
109
128
|
if next_char == "\n"
|
110
129
|
@ruby << next_char
|
111
|
-
@
|
130
|
+
@host_language << next_char
|
112
131
|
@current_pos += 1
|
113
132
|
end
|
114
133
|
when "\n"
|
115
134
|
@ruby << char
|
116
|
-
@
|
135
|
+
@host_language << char
|
117
136
|
else
|
118
137
|
push_char(T.must(char))
|
119
138
|
end
|
@@ -123,10 +142,10 @@ module RubyLsp
|
|
123
142
|
def push_char(char)
|
124
143
|
if @inside_ruby
|
125
144
|
@ruby << char
|
126
|
-
@
|
145
|
+
@host_language << " " * char.length
|
127
146
|
else
|
128
147
|
@ruby << " " * char.length
|
129
|
-
@
|
148
|
+
@host_language << char
|
130
149
|
end
|
131
150
|
end
|
132
151
|
|
@@ -21,7 +21,7 @@ module RubyLsp
|
|
21
21
|
attr_reader :encoding
|
22
22
|
|
23
23
|
sig { returns(T::Boolean) }
|
24
|
-
attr_reader :supports_watching_files, :experimental_features
|
24
|
+
attr_reader :supports_watching_files, :experimental_features, :supports_request_delegation
|
25
25
|
|
26
26
|
sig { returns(TypeInferrer) }
|
27
27
|
attr_reader :type_inferrer
|
@@ -40,6 +40,13 @@ module RubyLsp
|
|
40
40
|
@supports_watching_files = T.let(false, T::Boolean)
|
41
41
|
@experimental_features = T.let(false, T::Boolean)
|
42
42
|
@type_inferrer = T.let(TypeInferrer.new(@index, @experimental_features), TypeInferrer)
|
43
|
+
@addon_settings = T.let({}, T::Hash[String, T.untyped])
|
44
|
+
@supports_request_delegation = T.let(false, T::Boolean)
|
45
|
+
end
|
46
|
+
|
47
|
+
sig { params(addon_name: String).returns(T.nilable(T::Hash[Symbol, T.untyped])) }
|
48
|
+
def settings_for_addon(addon_name)
|
49
|
+
@addon_settings[addon_name]
|
43
50
|
end
|
44
51
|
|
45
52
|
sig { params(identifier: String, instance: Requests::Support::Formatter).void }
|
@@ -119,6 +126,13 @@ module RubyLsp
|
|
119
126
|
@experimental_features = options.dig(:initializationOptions, :experimentalFeaturesEnabled) || false
|
120
127
|
@type_inferrer.experimental_features = @experimental_features
|
121
128
|
|
129
|
+
addon_settings = options.dig(:initializationOptions, :addonSettings)
|
130
|
+
if addon_settings
|
131
|
+
addon_settings.transform_keys!(&:to_s)
|
132
|
+
@addon_settings.merge!(addon_settings)
|
133
|
+
end
|
134
|
+
|
135
|
+
@supports_request_delegation = options.dig(:capabilities, :experimental, :requestDelegation) || false
|
122
136
|
notifications
|
123
137
|
end
|
124
138
|
|