ruby-lsp 0.19.0 → 0.20.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-check +1 -1
- data/lib/core_ext/uri.rb +2 -2
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +85 -36
- data/lib/ruby_indexer/lib/ruby_indexer/enhancement.rb +5 -1
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +46 -98
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +7 -6
- data/lib/ruby_indexer/lib/ruby_indexer/location.rb +22 -0
- data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +20 -5
- data/lib/ruby_indexer/lib/ruby_indexer/reference_finder.rb +76 -14
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +12 -0
- data/lib/ruby_indexer/test/enhancements_test.rb +5 -7
- data/lib/ruby_indexer/test/global_variable_test.rb +49 -0
- data/lib/ruby_indexer/test/index_test.rb +3 -0
- data/lib/ruby_indexer/test/rbs_indexer_test.rb +14 -0
- data/lib/ruby_indexer/test/reference_finder_test.rb +162 -6
- data/lib/ruby_lsp/erb_document.rb +20 -2
- data/lib/ruby_lsp/internal.rb +3 -1
- data/lib/ruby_lsp/listeners/definition.rb +20 -0
- data/lib/ruby_lsp/listeners/folding_ranges.rb +3 -3
- data/lib/ruby_lsp/requests/code_action_resolve.rb +16 -4
- data/lib/ruby_lsp/requests/completion.rb +1 -0
- data/lib/ruby_lsp/requests/definition.rb +2 -0
- data/lib/ruby_lsp/requests/document_highlight.rb +5 -1
- data/lib/ruby_lsp/requests/hover.rb +1 -0
- data/lib/ruby_lsp/requests/range_formatting.rb +57 -0
- data/lib/ruby_lsp/requests/references.rb +146 -0
- data/lib/ruby_lsp/requests/rename.rb +16 -9
- data/lib/ruby_lsp/requests/semantic_highlighting.rb +1 -1
- data/lib/ruby_lsp/requests/signature_help.rb +6 -1
- data/lib/ruby_lsp/requests/support/common.rb +1 -1
- data/lib/ruby_lsp/requests/support/formatter.rb +3 -0
- data/lib/ruby_lsp/requests/support/rubocop_formatter.rb +6 -0
- data/lib/ruby_lsp/requests/support/rubocop_runner.rb +6 -6
- data/lib/ruby_lsp/requests/support/syntax_tree_formatter.rb +8 -0
- data/lib/ruby_lsp/response_builders/document_symbol.rb +2 -2
- data/lib/ruby_lsp/response_builders/hover.rb +2 -2
- data/lib/ruby_lsp/response_builders/semantic_highlighting.rb +14 -8
- data/lib/ruby_lsp/response_builders/signature_help.rb +2 -2
- data/lib/ruby_lsp/ruby_document.rb +44 -8
- data/lib/ruby_lsp/server.rb +63 -2
- data/lib/ruby_lsp/type_inferrer.rb +1 -1
- metadata +8 -5
@@ -44,6 +44,8 @@ module RubyIndexer
|
|
44
44
|
when RBS::AST::Declarations::Constant
|
45
45
|
namespace_nesting = declaration.name.namespace.path.map(&:to_s)
|
46
46
|
handle_constant(declaration, namespace_nesting, pathname.to_s)
|
47
|
+
when RBS::AST::Declarations::Global
|
48
|
+
handle_global_variable(declaration, pathname)
|
47
49
|
else # rubocop:disable Style/EmptyElse
|
48
50
|
# Other kinds not yet handled
|
49
51
|
end
|
@@ -59,9 +61,9 @@ module RubyIndexer
|
|
59
61
|
comments = comments_to_string(declaration)
|
60
62
|
entry = if declaration.is_a?(RBS::AST::Declarations::Class)
|
61
63
|
parent_class = declaration.super_class&.name&.name&.to_s
|
62
|
-
Entry::Class.new(nesting, file_path, location, location, comments,
|
64
|
+
Entry::Class.new(nesting, file_path, location, location, comments, parent_class)
|
63
65
|
else
|
64
|
-
Entry::Module.new(nesting, file_path, location, location, comments
|
66
|
+
Entry::Module.new(nesting, file_path, location, location, comments)
|
65
67
|
end
|
66
68
|
add_declaration_mixins_to_entry(declaration, entry)
|
67
69
|
@index.add(entry)
|
@@ -134,7 +136,6 @@ module RubyIndexer
|
|
134
136
|
location,
|
135
137
|
location,
|
136
138
|
comments,
|
137
|
-
@index.configuration.encoding,
|
138
139
|
signatures,
|
139
140
|
visibility,
|
140
141
|
real_owner,
|
@@ -267,7 +268,21 @@ module RubyIndexer
|
|
267
268
|
file_path,
|
268
269
|
to_ruby_indexer_location(declaration.location),
|
269
270
|
comments_to_string(declaration),
|
270
|
-
|
271
|
+
))
|
272
|
+
end
|
273
|
+
|
274
|
+
sig { params(declaration: RBS::AST::Declarations::Global, pathname: Pathname).void }
|
275
|
+
def handle_global_variable(declaration, pathname)
|
276
|
+
name = declaration.name.to_s
|
277
|
+
file_path = pathname.to_s
|
278
|
+
location = to_ruby_indexer_location(declaration.location)
|
279
|
+
comments = comments_to_string(declaration)
|
280
|
+
|
281
|
+
@index.add(Entry::GlobalVariable.new(
|
282
|
+
name,
|
283
|
+
file_path,
|
284
|
+
location,
|
285
|
+
comments,
|
271
286
|
))
|
272
287
|
end
|
273
288
|
|
@@ -283,7 +298,6 @@ module RubyIndexer
|
|
283
298
|
file_path,
|
284
299
|
to_ruby_indexer_location(member.location),
|
285
300
|
comments,
|
286
|
-
@index.configuration.encoding,
|
287
301
|
)
|
288
302
|
|
289
303
|
@index.add(entry)
|
@@ -294,6 +308,7 @@ module RubyIndexer
|
|
294
308
|
RBS::AST::Declarations::Class,
|
295
309
|
RBS::AST::Declarations::Module,
|
296
310
|
RBS::AST::Declarations::Constant,
|
311
|
+
RBS::AST::Declarations::Global,
|
297
312
|
RBS::AST::Members::MethodDefinition,
|
298
313
|
RBS::AST::Members::Alias,
|
299
314
|
)).returns(T.nilable(String))
|
@@ -5,6 +5,38 @@ module RubyIndexer
|
|
5
5
|
class ReferenceFinder
|
6
6
|
extend T::Sig
|
7
7
|
|
8
|
+
class Target
|
9
|
+
extend T::Helpers
|
10
|
+
|
11
|
+
abstract!
|
12
|
+
end
|
13
|
+
|
14
|
+
class ConstTarget < Target
|
15
|
+
extend T::Sig
|
16
|
+
|
17
|
+
sig { returns(String) }
|
18
|
+
attr_reader :fully_qualified_name
|
19
|
+
|
20
|
+
sig { params(fully_qualified_name: String).void }
|
21
|
+
def initialize(fully_qualified_name)
|
22
|
+
super()
|
23
|
+
@fully_qualified_name = fully_qualified_name
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class MethodTarget < Target
|
28
|
+
extend T::Sig
|
29
|
+
|
30
|
+
sig { returns(String) }
|
31
|
+
attr_reader :method_name
|
32
|
+
|
33
|
+
sig { params(method_name: String).void }
|
34
|
+
def initialize(method_name)
|
35
|
+
super()
|
36
|
+
@method_name = method_name
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
8
40
|
class Reference
|
9
41
|
extend T::Sig
|
10
42
|
|
@@ -14,26 +46,29 @@ module RubyIndexer
|
|
14
46
|
sig { returns(Prism::Location) }
|
15
47
|
attr_reader :location
|
16
48
|
|
17
|
-
sig {
|
18
|
-
|
49
|
+
sig { returns(T::Boolean) }
|
50
|
+
attr_reader :declaration
|
51
|
+
|
52
|
+
sig { params(name: String, location: Prism::Location, declaration: T::Boolean).void }
|
53
|
+
def initialize(name, location, declaration:)
|
19
54
|
@name = name
|
20
55
|
@location = location
|
56
|
+
@declaration = declaration
|
21
57
|
end
|
22
58
|
end
|
23
59
|
|
24
|
-
sig { returns(T::Array[Reference]) }
|
25
|
-
attr_reader :references
|
26
|
-
|
27
60
|
sig do
|
28
61
|
params(
|
29
|
-
|
62
|
+
target: Target,
|
30
63
|
index: RubyIndexer::Index,
|
31
64
|
dispatcher: Prism::Dispatcher,
|
65
|
+
include_declarations: T::Boolean,
|
32
66
|
).void
|
33
67
|
end
|
34
|
-
def initialize(
|
35
|
-
@
|
68
|
+
def initialize(target, index, dispatcher, include_declarations: true)
|
69
|
+
@target = target
|
36
70
|
@index = index
|
71
|
+
@include_declarations = include_declarations
|
37
72
|
@stack = T.let([], T::Array[String])
|
38
73
|
@references = T.let([], T::Array[Reference])
|
39
74
|
|
@@ -59,17 +94,25 @@ module RubyIndexer
|
|
59
94
|
:on_constant_or_write_node_enter,
|
60
95
|
:on_constant_and_write_node_enter,
|
61
96
|
:on_constant_operator_write_node_enter,
|
97
|
+
:on_call_node_enter,
|
62
98
|
)
|
63
99
|
end
|
64
100
|
|
101
|
+
sig { returns(T::Array[Reference]) }
|
102
|
+
def references
|
103
|
+
return @references if @include_declarations
|
104
|
+
|
105
|
+
@references.reject(&:declaration)
|
106
|
+
end
|
107
|
+
|
65
108
|
sig { params(node: Prism::ClassNode).void }
|
66
109
|
def on_class_node_enter(node)
|
67
110
|
constant_path = node.constant_path
|
68
111
|
name = constant_path.slice
|
69
112
|
nesting = actual_nesting(name)
|
70
113
|
|
71
|
-
if nesting.join("::") == @fully_qualified_name
|
72
|
-
@references << Reference.new(name, constant_path.location)
|
114
|
+
if @target.is_a?(ConstTarget) && nesting.join("::") == @target.fully_qualified_name
|
115
|
+
@references << Reference.new(name, constant_path.location, declaration: true)
|
73
116
|
end
|
74
117
|
|
75
118
|
@stack << name
|
@@ -86,8 +129,8 @@ module RubyIndexer
|
|
86
129
|
name = constant_path.slice
|
87
130
|
nesting = actual_nesting(name)
|
88
131
|
|
89
|
-
if nesting.join("::") == @fully_qualified_name
|
90
|
-
@references << Reference.new(name, constant_path.location)
|
132
|
+
if @target.is_a?(ConstTarget) && nesting.join("::") == @target.fully_qualified_name
|
133
|
+
@references << Reference.new(name, constant_path.location, declaration: true)
|
91
134
|
end
|
92
135
|
|
93
136
|
@stack << name
|
@@ -203,6 +246,10 @@ module RubyIndexer
|
|
203
246
|
|
204
247
|
sig { params(node: Prism::DefNode).void }
|
205
248
|
def on_def_node_enter(node)
|
249
|
+
if @target.is_a?(MethodTarget) && (name = node.name.to_s) == @target.method_name
|
250
|
+
@references << Reference.new(name, node.name_loc, declaration: true)
|
251
|
+
end
|
252
|
+
|
206
253
|
if node.receiver.is_a?(Prism::SelfNode)
|
207
254
|
@stack << "<Class:#{@stack.last}>"
|
208
255
|
end
|
@@ -215,6 +262,13 @@ module RubyIndexer
|
|
215
262
|
end
|
216
263
|
end
|
217
264
|
|
265
|
+
sig { params(node: Prism::CallNode).void }
|
266
|
+
def on_call_node_enter(node)
|
267
|
+
if @target.is_a?(MethodTarget) && (name = node.name.to_s) == @target.method_name
|
268
|
+
@references << Reference.new(name, T.must(node.message_loc), declaration: false)
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
218
272
|
private
|
219
273
|
|
220
274
|
sig { params(name: String).returns(T::Array[String]) }
|
@@ -233,13 +287,21 @@ module RubyIndexer
|
|
233
287
|
|
234
288
|
sig { params(name: String, location: Prism::Location).void }
|
235
289
|
def collect_constant_references(name, location)
|
290
|
+
return unless @target.is_a?(ConstTarget)
|
291
|
+
|
236
292
|
entries = @index.resolve(name, @stack)
|
237
293
|
return unless entries
|
238
294
|
|
295
|
+
previous_reference = @references.last
|
296
|
+
|
239
297
|
entries.each do |entry|
|
240
|
-
next unless entry.name == @fully_qualified_name
|
298
|
+
next unless entry.name == @target.fully_qualified_name
|
299
|
+
|
300
|
+
# When processing a class/module declaration, we eagerly handle the constant reference. To avoid duplicates,
|
301
|
+
# when we find the constant node defining the namespace, then we have to check if it wasn't already added
|
302
|
+
next if previous_reference&.location == location
|
241
303
|
|
242
|
-
@references << Reference.new(name, location)
|
304
|
+
@references << Reference.new(name, location, declaration: false)
|
243
305
|
end
|
244
306
|
end
|
245
307
|
|
@@ -630,5 +630,17 @@ module RubyIndexer
|
|
630
630
|
its namespace nesting, and the surrounding CallNode (e.g. a method call).
|
631
631
|
COMMENTS
|
632
632
|
end
|
633
|
+
|
634
|
+
def test_lazy_comment_fetching_does_not_fail_if_file_gets_deleted
|
635
|
+
indexable = IndexablePath.new("#{Dir.pwd}/lib", "lib/ruby_lsp/does_not_exist.rb")
|
636
|
+
|
637
|
+
@index.index_single(indexable, <<~RUBY, collect_comments: false)
|
638
|
+
class Foo
|
639
|
+
end
|
640
|
+
RUBY
|
641
|
+
|
642
|
+
entry = @index["Foo"].first
|
643
|
+
assert_empty(entry.comments)
|
644
|
+
end
|
633
645
|
end
|
634
646
|
end
|
@@ -9,14 +9,14 @@ module RubyIndexer
|
|
9
9
|
enhancement_class = Class.new do
|
10
10
|
include Enhancement
|
11
11
|
|
12
|
-
def on_call_node(index, owner, node, file_path)
|
12
|
+
def on_call_node(index, owner, node, file_path, code_units_cache)
|
13
13
|
return unless owner
|
14
14
|
return unless node.name == :extend
|
15
15
|
|
16
16
|
arguments = node.arguments&.arguments
|
17
17
|
return unless arguments
|
18
18
|
|
19
|
-
location = node.location
|
19
|
+
location = Location.from_prism_location(node.location, code_units_cache)
|
20
20
|
|
21
21
|
arguments.each do |node|
|
22
22
|
next unless node.is_a?(Prism::ConstantReadNode) || node.is_a?(Prism::ConstantPathNode)
|
@@ -39,7 +39,6 @@ module RubyIndexer
|
|
39
39
|
location,
|
40
40
|
location,
|
41
41
|
nil,
|
42
|
-
index.configuration.encoding,
|
43
42
|
[Entry::Signature.new([Entry::RequiredParameter.new(name: :a)])],
|
44
43
|
Entry::Visibility::PUBLIC,
|
45
44
|
owner,
|
@@ -102,7 +101,7 @@ module RubyIndexer
|
|
102
101
|
enhancement_class = Class.new do
|
103
102
|
include Enhancement
|
104
103
|
|
105
|
-
def on_call_node(index, owner, node, file_path)
|
104
|
+
def on_call_node(index, owner, node, file_path, code_units_cache)
|
106
105
|
return unless owner
|
107
106
|
|
108
107
|
name = node.name
|
@@ -114,7 +113,7 @@ module RubyIndexer
|
|
114
113
|
association_name = arguments.first
|
115
114
|
return unless association_name.is_a?(Prism::SymbolNode)
|
116
115
|
|
117
|
-
location = association_name.location
|
116
|
+
location = Location.from_prism_location(association_name.location, code_units_cache)
|
118
117
|
|
119
118
|
index.add(Entry::Method.new(
|
120
119
|
T.must(association_name.value),
|
@@ -122,7 +121,6 @@ module RubyIndexer
|
|
122
121
|
location,
|
123
122
|
location,
|
124
123
|
nil,
|
125
|
-
index.configuration.encoding,
|
126
124
|
[],
|
127
125
|
Entry::Visibility::PUBLIC,
|
128
126
|
owner,
|
@@ -166,7 +164,7 @@ module RubyIndexer
|
|
166
164
|
enhancement_class = Class.new do
|
167
165
|
include Enhancement
|
168
166
|
|
169
|
-
def on_call_node(index, owner, node, file_path)
|
167
|
+
def on_call_node(index, owner, node, file_path, code_units_cache)
|
170
168
|
raise "Error"
|
171
169
|
end
|
172
170
|
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative "test_case"
|
5
|
+
|
6
|
+
module RubyIndexer
|
7
|
+
class GlobalVariableTest < TestCase
|
8
|
+
def test_global_variable_and_write
|
9
|
+
index(<<~RUBY)
|
10
|
+
$foo &&= 1
|
11
|
+
RUBY
|
12
|
+
|
13
|
+
assert_entry("$foo", Entry::GlobalVariable, "/fake/path/foo.rb:0-0:0-4")
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_global_variable_operator_write
|
17
|
+
index(<<~RUBY)
|
18
|
+
$foo += 1
|
19
|
+
RUBY
|
20
|
+
|
21
|
+
assert_entry("$foo", Entry::GlobalVariable, "/fake/path/foo.rb:0-0:0-4")
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_global_variable_or_write
|
25
|
+
index(<<~RUBY)
|
26
|
+
$foo ||= 1
|
27
|
+
RUBY
|
28
|
+
|
29
|
+
assert_entry("$foo", Entry::GlobalVariable, "/fake/path/foo.rb:0-0:0-4")
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_global_variable_target_node
|
33
|
+
index(<<~RUBY)
|
34
|
+
$foo, $bar = 1
|
35
|
+
RUBY
|
36
|
+
|
37
|
+
assert_entry("$foo", Entry::GlobalVariable, "/fake/path/foo.rb:0-0:0-4")
|
38
|
+
assert_entry("$bar", Entry::GlobalVariable, "/fake/path/foo.rb:0-6:0-10")
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_global_variable_write
|
42
|
+
index(<<~RUBY)
|
43
|
+
$foo = 1
|
44
|
+
RUBY
|
45
|
+
|
46
|
+
assert_entry("$foo", Entry::GlobalVariable, "/fake/path/foo.rb:0-0:0-4")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -1858,6 +1858,9 @@ module RubyIndexer
|
|
1858
1858
|
|
1859
1859
|
entries = @index.entries_for("/fake/path/foo.rb", RubyIndexer::Entry::Namespace)
|
1860
1860
|
assert_equal(["Foo", "Bar", "Bar::<Class:Bar>"], entries.map(&:name))
|
1861
|
+
|
1862
|
+
entries = @index.entries_for("/fake/path/foo.rb")
|
1863
|
+
assert_equal(["Foo", "Bar", "my_def", "Bar::<Class:Bar>", "my_singleton_def"], entries.map(&:name))
|
1861
1864
|
end
|
1862
1865
|
|
1863
1866
|
def test_entries_for_returns_nil_if_no_matches
|
@@ -76,6 +76,20 @@ module RubyIndexer
|
|
76
76
|
assert_operator(entry.location.end_column, :>, 0)
|
77
77
|
end
|
78
78
|
|
79
|
+
def test_index_global_declaration
|
80
|
+
entries = @index["$DEBUG"]
|
81
|
+
refute_nil(entries)
|
82
|
+
assert_equal(1, entries.length)
|
83
|
+
|
84
|
+
entry = entries.first
|
85
|
+
|
86
|
+
assert_instance_of(Entry::GlobalVariable, entry)
|
87
|
+
assert_equal("$DEBUG", entry.name)
|
88
|
+
assert_match(%r{/gems/rbs-.*/core/global_variables.rbs}, entry.file_path)
|
89
|
+
assert_operator(entry.location.start_column, :<, entry.location.end_column)
|
90
|
+
assert_equal(entry.location.start_line, entry.location.end_line)
|
91
|
+
end
|
92
|
+
|
79
93
|
def test_attaches_correct_owner_to_singleton_methods
|
80
94
|
entries = @index["basename"]
|
81
95
|
refute_nil(entries)
|
@@ -6,7 +6,7 @@ require "test_helper"
|
|
6
6
|
module RubyIndexer
|
7
7
|
class ReferenceFinderTest < Minitest::Test
|
8
8
|
def test_finds_constant_references
|
9
|
-
refs =
|
9
|
+
refs = find_const_references("Foo::Bar", <<~RUBY)
|
10
10
|
module Foo
|
11
11
|
class Bar
|
12
12
|
end
|
@@ -28,7 +28,7 @@ module RubyIndexer
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def test_finds_constant_references_inside_singleton_contexts
|
31
|
-
refs =
|
31
|
+
refs = find_const_references("Foo::<Class:Foo>::Bar", <<~RUBY)
|
32
32
|
class Foo
|
33
33
|
class << self
|
34
34
|
class Bar
|
@@ -47,7 +47,7 @@ module RubyIndexer
|
|
47
47
|
end
|
48
48
|
|
49
49
|
def test_finds_top_level_constant_references
|
50
|
-
refs =
|
50
|
+
refs = find_const_references("Bar", <<~RUBY)
|
51
51
|
class Bar
|
52
52
|
end
|
53
53
|
|
@@ -70,17 +70,173 @@ module RubyIndexer
|
|
70
70
|
assert_equal(8, refs[2].location.start_line)
|
71
71
|
end
|
72
72
|
|
73
|
+
def test_finds_method_references
|
74
|
+
refs = find_method_references("foo", <<~RUBY)
|
75
|
+
class Bar
|
76
|
+
def foo
|
77
|
+
end
|
78
|
+
|
79
|
+
def baz
|
80
|
+
foo
|
81
|
+
end
|
82
|
+
end
|
83
|
+
RUBY
|
84
|
+
|
85
|
+
assert_equal(2, refs.size)
|
86
|
+
|
87
|
+
assert_equal("foo", refs[0].name)
|
88
|
+
assert_equal(2, refs[0].location.start_line)
|
89
|
+
|
90
|
+
assert_equal("foo", refs[1].name)
|
91
|
+
assert_equal(6, refs[1].location.start_line)
|
92
|
+
end
|
93
|
+
|
94
|
+
def test_does_not_mismatch_on_readers_and_writers
|
95
|
+
refs = find_method_references("foo", <<~RUBY)
|
96
|
+
class Bar
|
97
|
+
def foo
|
98
|
+
end
|
99
|
+
|
100
|
+
def foo=(value)
|
101
|
+
end
|
102
|
+
|
103
|
+
def baz
|
104
|
+
self.foo = 1
|
105
|
+
self.foo
|
106
|
+
end
|
107
|
+
end
|
108
|
+
RUBY
|
109
|
+
|
110
|
+
# We want to match `foo` but not `foo=`
|
111
|
+
assert_equal(2, refs.size)
|
112
|
+
|
113
|
+
assert_equal("foo", refs[0].name)
|
114
|
+
assert_equal(2, refs[0].location.start_line)
|
115
|
+
|
116
|
+
assert_equal("foo", refs[1].name)
|
117
|
+
assert_equal(10, refs[1].location.start_line)
|
118
|
+
end
|
119
|
+
|
120
|
+
def test_matches_writers
|
121
|
+
refs = find_method_references("foo=", <<~RUBY)
|
122
|
+
class Bar
|
123
|
+
def foo
|
124
|
+
end
|
125
|
+
|
126
|
+
def foo=(value)
|
127
|
+
end
|
128
|
+
|
129
|
+
def baz
|
130
|
+
self.foo = 1
|
131
|
+
self.foo
|
132
|
+
end
|
133
|
+
end
|
134
|
+
RUBY
|
135
|
+
|
136
|
+
# We want to match `foo=` but not `foo`
|
137
|
+
assert_equal(2, refs.size)
|
138
|
+
|
139
|
+
assert_equal("foo=", refs[0].name)
|
140
|
+
assert_equal(5, refs[0].location.start_line)
|
141
|
+
|
142
|
+
assert_equal("foo=", refs[1].name)
|
143
|
+
assert_equal(9, refs[1].location.start_line)
|
144
|
+
end
|
145
|
+
|
146
|
+
def test_find_inherited_methods
|
147
|
+
refs = find_method_references("foo", <<~RUBY)
|
148
|
+
class Bar
|
149
|
+
def foo
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
class Baz < Bar
|
154
|
+
super.foo
|
155
|
+
end
|
156
|
+
RUBY
|
157
|
+
|
158
|
+
assert_equal(2, refs.size)
|
159
|
+
|
160
|
+
assert_equal("foo", refs[0].name)
|
161
|
+
assert_equal(2, refs[0].location.start_line)
|
162
|
+
|
163
|
+
assert_equal("foo", refs[1].name)
|
164
|
+
assert_equal(7, refs[1].location.start_line)
|
165
|
+
end
|
166
|
+
|
167
|
+
def test_finds_methods_created_in_mixins
|
168
|
+
refs = find_method_references("foo", <<~RUBY)
|
169
|
+
module Mixin
|
170
|
+
def foo
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
class Bar
|
175
|
+
include Mixin
|
176
|
+
end
|
177
|
+
|
178
|
+
Bar.foo
|
179
|
+
RUBY
|
180
|
+
|
181
|
+
assert_equal(2, refs.size)
|
182
|
+
|
183
|
+
assert_equal("foo", refs[0].name)
|
184
|
+
assert_equal(2, refs[0].location.start_line)
|
185
|
+
|
186
|
+
assert_equal("foo", refs[1].name)
|
187
|
+
assert_equal(10, refs[1].location.start_line)
|
188
|
+
end
|
189
|
+
|
190
|
+
def test_finds_singleton_methods
|
191
|
+
# The current implementation matches on both `Bar.foo` and `Bar#foo` even though they are different
|
192
|
+
|
193
|
+
refs = find_method_references("foo", <<~RUBY)
|
194
|
+
class Bar
|
195
|
+
class << self
|
196
|
+
def foo
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def foo
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
Bar.foo
|
205
|
+
RUBY
|
206
|
+
|
207
|
+
assert_equal(3, refs.size)
|
208
|
+
|
209
|
+
assert_equal("foo", refs[0].name)
|
210
|
+
assert_equal(3, refs[0].location.start_line)
|
211
|
+
|
212
|
+
assert_equal("foo", refs[1].name)
|
213
|
+
assert_equal(7, refs[1].location.start_line)
|
214
|
+
|
215
|
+
assert_equal("foo", refs[2].name)
|
216
|
+
assert_equal(11, refs[2].location.start_line)
|
217
|
+
end
|
218
|
+
|
73
219
|
private
|
74
220
|
|
75
|
-
def
|
221
|
+
def find_const_references(const_name, source)
|
222
|
+
target = ReferenceFinder::ConstTarget.new(const_name)
|
223
|
+
find_references(target, source)
|
224
|
+
end
|
225
|
+
|
226
|
+
def find_method_references(method_name, source)
|
227
|
+
target = ReferenceFinder::MethodTarget.new(method_name)
|
228
|
+
find_references(target, source)
|
229
|
+
end
|
230
|
+
|
231
|
+
def find_references(target, source)
|
76
232
|
file_path = "/fake.rb"
|
77
233
|
index = Index.new
|
78
234
|
index.index_single(IndexablePath.new(nil, file_path), source)
|
79
235
|
parse_result = Prism.parse(source)
|
80
236
|
dispatcher = Prism::Dispatcher.new
|
81
|
-
finder = ReferenceFinder.new(
|
237
|
+
finder = ReferenceFinder.new(target, index, dispatcher)
|
82
238
|
dispatcher.visit(parse_result.value)
|
83
|
-
finder.references
|
239
|
+
finder.references
|
84
240
|
end
|
85
241
|
end
|
86
242
|
end
|
@@ -6,10 +6,18 @@ module RubyLsp
|
|
6
6
|
extend T::Sig
|
7
7
|
extend T::Generic
|
8
8
|
|
9
|
+
ParseResultType = type_member { { fixed: Prism::ParseResult } }
|
10
|
+
|
9
11
|
sig { returns(String) }
|
10
12
|
attr_reader :host_language_source
|
11
13
|
|
12
|
-
|
14
|
+
sig do
|
15
|
+
returns(T.any(
|
16
|
+
T.proc.params(arg0: Integer).returns(Integer),
|
17
|
+
Prism::CodeUnitsCache,
|
18
|
+
))
|
19
|
+
end
|
20
|
+
attr_reader :code_units_cache
|
13
21
|
|
14
22
|
sig { params(source: String, version: Integer, uri: URI::Generic, encoding: Encoding).void }
|
15
23
|
def initialize(source:, version:, uri:, encoding: Encoding::UTF_8)
|
@@ -17,6 +25,10 @@ module RubyLsp
|
|
17
25
|
# overrides this with the proper virtual host language source
|
18
26
|
@host_language_source = T.let("", String)
|
19
27
|
super
|
28
|
+
@code_units_cache = T.let(@parse_result.code_units_cache(@encoding), T.any(
|
29
|
+
T.proc.params(arg0: Integer).returns(Integer),
|
30
|
+
Prism::CodeUnitsCache,
|
31
|
+
))
|
20
32
|
end
|
21
33
|
|
22
34
|
sig { override.returns(T::Boolean) }
|
@@ -30,6 +42,7 @@ module RubyLsp
|
|
30
42
|
# Use partial script to avoid syntax errors in ERB files where keywords may be used without the full context in
|
31
43
|
# which they will be evaluated
|
32
44
|
@parse_result = Prism.parse(scanner.ruby, partial_script: true)
|
45
|
+
@code_units_cache = @parse_result.code_units_cache(@encoding)
|
33
46
|
true
|
34
47
|
end
|
35
48
|
|
@@ -50,7 +63,12 @@ module RubyLsp
|
|
50
63
|
).returns(NodeContext)
|
51
64
|
end
|
52
65
|
def locate_node(position, node_types: [])
|
53
|
-
RubyDocument.locate(
|
66
|
+
RubyDocument.locate(
|
67
|
+
@parse_result.value,
|
68
|
+
create_scanner.find_char_position(position),
|
69
|
+
code_units_cache: @code_units_cache,
|
70
|
+
node_types: node_types,
|
71
|
+
)
|
54
72
|
end
|
55
73
|
|
56
74
|
sig { params(char_position: Integer).returns(T.nilable(T::Boolean)) }
|
data/lib/ruby_lsp/internal.rb
CHANGED
@@ -75,10 +75,12 @@ require "ruby_lsp/requests/hover"
|
|
75
75
|
require "ruby_lsp/requests/inlay_hints"
|
76
76
|
require "ruby_lsp/requests/on_type_formatting"
|
77
77
|
require "ruby_lsp/requests/prepare_type_hierarchy"
|
78
|
+
require "ruby_lsp/requests/range_formatting"
|
79
|
+
require "ruby_lsp/requests/references"
|
80
|
+
require "ruby_lsp/requests/rename"
|
78
81
|
require "ruby_lsp/requests/selection_ranges"
|
79
82
|
require "ruby_lsp/requests/semantic_highlighting"
|
80
83
|
require "ruby_lsp/requests/show_syntax_tree"
|
81
84
|
require "ruby_lsp/requests/signature_help"
|
82
85
|
require "ruby_lsp/requests/type_hierarchy_supertypes"
|
83
86
|
require "ruby_lsp/requests/workspace_symbol"
|
84
|
-
require "ruby_lsp/requests/rename"
|
@@ -39,6 +39,7 @@ module RubyLsp
|
|
39
39
|
:on_block_argument_node_enter,
|
40
40
|
:on_constant_read_node_enter,
|
41
41
|
:on_constant_path_node_enter,
|
42
|
+
:on_global_variable_read_node_enter,
|
42
43
|
:on_instance_variable_read_node_enter,
|
43
44
|
:on_instance_variable_write_node_enter,
|
44
45
|
:on_instance_variable_and_write_node_enter,
|
@@ -120,6 +121,25 @@ module RubyLsp
|
|
120
121
|
find_in_index(name)
|
121
122
|
end
|
122
123
|
|
124
|
+
sig { params(node: Prism::GlobalVariableReadNode).void }
|
125
|
+
def on_global_variable_read_node_enter(node)
|
126
|
+
entries = @index[node.name.to_s]
|
127
|
+
|
128
|
+
return unless entries
|
129
|
+
|
130
|
+
entries.each do |entry|
|
131
|
+
location = entry.location
|
132
|
+
|
133
|
+
@response_builder << Interface::Location.new(
|
134
|
+
uri: URI::Generic.from_path(path: entry.file_path).to_s,
|
135
|
+
range: Interface::Range.new(
|
136
|
+
start: Interface::Position.new(line: location.start_line - 1, character: location.start_column),
|
137
|
+
end: Interface::Position.new(line: location.end_line - 1, character: location.end_column),
|
138
|
+
),
|
139
|
+
)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
123
143
|
sig { params(node: Prism::InstanceVariableReadNode).void }
|
124
144
|
def on_instance_variable_read_node_enter(node)
|
125
145
|
handle_instance_variable_definition(node.name.to_s)
|