ruby-lsp 0.16.7 → 0.17.3
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 +3 -1
- data/VERSION +1 -1
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +195 -18
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +129 -12
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +333 -44
- data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +99 -0
- data/lib/ruby_indexer/ruby_indexer.rb +1 -0
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +58 -11
- data/lib/ruby_indexer/test/configuration_test.rb +5 -6
- data/lib/ruby_indexer/test/constant_test.rb +9 -9
- data/lib/ruby_indexer/test/index_test.rb +867 -7
- data/lib/ruby_indexer/test/instance_variables_test.rb +131 -0
- data/lib/ruby_indexer/test/method_test.rb +57 -0
- data/lib/ruby_indexer/test/rbs_indexer_test.rb +42 -0
- data/lib/ruby_indexer/test/test_case.rb +10 -1
- data/lib/ruby_lsp/addon.rb +8 -8
- data/lib/ruby_lsp/document.rb +26 -3
- data/lib/ruby_lsp/global_state.rb +37 -16
- data/lib/ruby_lsp/internal.rb +2 -0
- data/lib/ruby_lsp/listeners/code_lens.rb +2 -2
- data/lib/ruby_lsp/listeners/completion.rb +74 -17
- data/lib/ruby_lsp/listeners/definition.rb +82 -24
- data/lib/ruby_lsp/listeners/hover.rb +62 -6
- data/lib/ruby_lsp/listeners/signature_help.rb +4 -4
- data/lib/ruby_lsp/node_context.rb +39 -0
- data/lib/ruby_lsp/requests/code_action_resolve.rb +73 -2
- data/lib/ruby_lsp/requests/code_actions.rb +16 -15
- data/lib/ruby_lsp/requests/completion.rb +21 -13
- data/lib/ruby_lsp/requests/completion_resolve.rb +9 -0
- data/lib/ruby_lsp/requests/definition.rb +25 -5
- data/lib/ruby_lsp/requests/document_highlight.rb +2 -2
- data/lib/ruby_lsp/requests/hover.rb +5 -6
- data/lib/ruby_lsp/requests/on_type_formatting.rb +8 -4
- data/lib/ruby_lsp/requests/signature_help.rb +3 -3
- data/lib/ruby_lsp/requests/support/common.rb +4 -3
- data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +23 -6
- data/lib/ruby_lsp/requests/support/rubocop_formatter.rb +5 -1
- data/lib/ruby_lsp/requests/support/rubocop_runner.rb +4 -0
- data/lib/ruby_lsp/requests/workspace_symbol.rb +7 -4
- data/lib/ruby_lsp/server.rb +22 -5
- data/lib/ruby_lsp/test_helper.rb +1 -0
- metadata +29 -5
@@ -0,0 +1,131 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative "test_case"
|
5
|
+
|
6
|
+
module RubyIndexer
|
7
|
+
class InstanceVariableTest < TestCase
|
8
|
+
def test_instance_variable_write
|
9
|
+
index(<<~RUBY)
|
10
|
+
module Foo
|
11
|
+
class Bar
|
12
|
+
def initialize
|
13
|
+
# Hello
|
14
|
+
@a = 1
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
RUBY
|
19
|
+
|
20
|
+
assert_entry("@a", Entry::InstanceVariable, "/fake/path/foo.rb:4-6:4-8")
|
21
|
+
|
22
|
+
entry = T.must(@index["@a"]&.first)
|
23
|
+
assert_equal("Foo::Bar", T.must(entry.owner).name)
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_instance_variable_and_write
|
27
|
+
index(<<~RUBY)
|
28
|
+
module Foo
|
29
|
+
class Bar
|
30
|
+
def initialize
|
31
|
+
# Hello
|
32
|
+
@a &&= value
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
RUBY
|
37
|
+
|
38
|
+
assert_entry("@a", Entry::InstanceVariable, "/fake/path/foo.rb:4-6:4-8")
|
39
|
+
|
40
|
+
entry = T.must(@index["@a"]&.first)
|
41
|
+
assert_equal("Foo::Bar", T.must(entry.owner).name)
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_instance_variable_operator_write
|
45
|
+
index(<<~RUBY)
|
46
|
+
module Foo
|
47
|
+
class Bar
|
48
|
+
def initialize
|
49
|
+
# Hello
|
50
|
+
@a += value
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
RUBY
|
55
|
+
|
56
|
+
assert_entry("@a", Entry::InstanceVariable, "/fake/path/foo.rb:4-6:4-8")
|
57
|
+
|
58
|
+
entry = T.must(@index["@a"]&.first)
|
59
|
+
assert_equal("Foo::Bar", T.must(entry.owner).name)
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_instance_variable_or_write
|
63
|
+
index(<<~RUBY)
|
64
|
+
module Foo
|
65
|
+
class Bar
|
66
|
+
def initialize
|
67
|
+
# Hello
|
68
|
+
@a ||= value
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
RUBY
|
73
|
+
|
74
|
+
assert_entry("@a", Entry::InstanceVariable, "/fake/path/foo.rb:4-6:4-8")
|
75
|
+
|
76
|
+
entry = T.must(@index["@a"]&.first)
|
77
|
+
assert_equal("Foo::Bar", T.must(entry.owner).name)
|
78
|
+
end
|
79
|
+
|
80
|
+
def test_instance_variable_target
|
81
|
+
index(<<~RUBY)
|
82
|
+
module Foo
|
83
|
+
class Bar
|
84
|
+
def initialize
|
85
|
+
# Hello
|
86
|
+
@a, @b = [1, 2]
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
RUBY
|
91
|
+
|
92
|
+
assert_entry("@a", Entry::InstanceVariable, "/fake/path/foo.rb:4-6:4-8")
|
93
|
+
assert_entry("@b", Entry::InstanceVariable, "/fake/path/foo.rb:4-10:4-12")
|
94
|
+
|
95
|
+
entry = T.must(@index["@a"]&.first)
|
96
|
+
assert_equal("Foo::Bar", T.must(entry.owner).name)
|
97
|
+
|
98
|
+
entry = T.must(@index["@b"]&.first)
|
99
|
+
assert_equal("Foo::Bar", T.must(entry.owner).name)
|
100
|
+
end
|
101
|
+
|
102
|
+
def test_empty_name_instance_variables
|
103
|
+
index(<<~RUBY)
|
104
|
+
module Foo
|
105
|
+
class Bar
|
106
|
+
def initialize
|
107
|
+
@ = 123
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
RUBY
|
112
|
+
|
113
|
+
refute_entry("@")
|
114
|
+
end
|
115
|
+
|
116
|
+
def test_class_instance_variables
|
117
|
+
index(<<~RUBY)
|
118
|
+
module Foo
|
119
|
+
class Bar
|
120
|
+
@a = 123
|
121
|
+
end
|
122
|
+
end
|
123
|
+
RUBY
|
124
|
+
|
125
|
+
assert_entry("@a", Entry::InstanceVariable, "/fake/path/foo.rb:2-4:2-6")
|
126
|
+
|
127
|
+
entry = T.must(@index["@a"]&.first)
|
128
|
+
assert_equal("Foo::Bar", T.must(entry.owner).name)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -71,6 +71,43 @@ module RubyIndexer
|
|
71
71
|
assert_equal("Bar", second_entry.owner.name)
|
72
72
|
end
|
73
73
|
|
74
|
+
def test_visibility_tracking
|
75
|
+
index(<<~RUBY)
|
76
|
+
private def foo
|
77
|
+
end
|
78
|
+
|
79
|
+
def bar; end
|
80
|
+
|
81
|
+
protected
|
82
|
+
|
83
|
+
def baz; end
|
84
|
+
RUBY
|
85
|
+
|
86
|
+
assert_entry("foo", Entry::InstanceMethod, "/fake/path/foo.rb:0-8:1-3", visibility: Entry::Visibility::PRIVATE)
|
87
|
+
assert_entry("bar", Entry::InstanceMethod, "/fake/path/foo.rb:3-0:3-12", visibility: Entry::Visibility::PUBLIC)
|
88
|
+
assert_entry("baz", Entry::InstanceMethod, "/fake/path/foo.rb:7-0:7-12", visibility: Entry::Visibility::PROTECTED)
|
89
|
+
end
|
90
|
+
|
91
|
+
def test_visibility_tracking_with_nested_class_or_modules
|
92
|
+
index(<<~RUBY)
|
93
|
+
class Foo
|
94
|
+
private
|
95
|
+
|
96
|
+
def foo; end
|
97
|
+
|
98
|
+
class Bar
|
99
|
+
def bar; end
|
100
|
+
end
|
101
|
+
|
102
|
+
def baz; end
|
103
|
+
end
|
104
|
+
RUBY
|
105
|
+
|
106
|
+
assert_entry("foo", Entry::InstanceMethod, "/fake/path/foo.rb:3-2:3-14", visibility: Entry::Visibility::PRIVATE)
|
107
|
+
assert_entry("bar", Entry::InstanceMethod, "/fake/path/foo.rb:6-4:6-16", visibility: Entry::Visibility::PUBLIC)
|
108
|
+
assert_entry("baz", Entry::InstanceMethod, "/fake/path/foo.rb:9-2:9-14", visibility: Entry::Visibility::PRIVATE)
|
109
|
+
end
|
110
|
+
|
74
111
|
def test_method_with_parameters
|
75
112
|
index(<<~RUBY)
|
76
113
|
class Foo
|
@@ -341,5 +378,25 @@ module RubyIndexer
|
|
341
378
|
entry = T.must(@index["third"]&.first)
|
342
379
|
assert_equal("Foo", T.must(entry.owner).name)
|
343
380
|
end
|
381
|
+
|
382
|
+
def test_keeps_track_of_aliases
|
383
|
+
index(<<~RUBY)
|
384
|
+
class Foo
|
385
|
+
alias whatever to_s
|
386
|
+
alias_method :foo, :to_a
|
387
|
+
alias_method "bar", "to_a"
|
388
|
+
|
389
|
+
# These two are not indexed because they are dynamic or incomplete
|
390
|
+
alias_method baz, :to_a
|
391
|
+
alias_method :baz
|
392
|
+
end
|
393
|
+
RUBY
|
394
|
+
|
395
|
+
assert_entry("whatever", Entry::UnresolvedMethodAlias, "/fake/path/foo.rb:1-8:1-16")
|
396
|
+
assert_entry("foo", Entry::UnresolvedMethodAlias, "/fake/path/foo.rb:2-15:2-19")
|
397
|
+
assert_entry("bar", Entry::UnresolvedMethodAlias, "/fake/path/foo.rb:3-15:3-20")
|
398
|
+
# Foo plus 3 valid aliases
|
399
|
+
assert_equal(4, @index.instance_variable_get(:@entries).length - @default_indexed_entries.length)
|
400
|
+
end
|
344
401
|
end
|
345
402
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative "test_case"
|
5
|
+
|
6
|
+
module RubyIndexer
|
7
|
+
class RBSIndexerTest < TestCase
|
8
|
+
def test_index_core_classes
|
9
|
+
entries = @index["Array"]
|
10
|
+
refute_nil(entries)
|
11
|
+
assert_equal(1, entries.length)
|
12
|
+
entry = entries.first
|
13
|
+
assert_match(%r{/gems/rbs-.*/core/array.rbs}, entry.file_path)
|
14
|
+
assert_equal("array.rbs", entry.file_name)
|
15
|
+
assert_equal("Object", entry.parent_class)
|
16
|
+
assert_equal(1, entry.mixin_operations.length)
|
17
|
+
enumerable_include = entry.mixin_operations.first
|
18
|
+
assert_equal("Enumerable", enumerable_include.module_name)
|
19
|
+
|
20
|
+
# Using fixed positions would be fragile, so let's just check some basics.
|
21
|
+
assert_operator(entry.location.start_line, :>, 0)
|
22
|
+
assert_operator(entry.location.end_line, :>, entry.location.start_line)
|
23
|
+
assert_equal(0, entry.location.start_column)
|
24
|
+
assert_operator(entry.location.end_column, :>, 0)
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_index_core_modules
|
28
|
+
entries = @index["Kernel"]
|
29
|
+
refute_nil(entries)
|
30
|
+
assert_equal(1, entries.length)
|
31
|
+
entry = entries.first
|
32
|
+
assert_match(%r{/gems/rbs-.*/core/kernel.rbs}, entry.file_path)
|
33
|
+
assert_equal("kernel.rbs", entry.file_name)
|
34
|
+
|
35
|
+
# Using fixed positions would be fragile, so let's just check some basics.
|
36
|
+
assert_operator(entry.location.start_line, :>, 0)
|
37
|
+
assert_operator(entry.location.end_line, :>, entry.location.start_line)
|
38
|
+
assert_equal(0, entry.location.start_column)
|
39
|
+
assert_operator(entry.location.end_column, :>, 0)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -7,6 +7,8 @@ module RubyIndexer
|
|
7
7
|
class TestCase < Minitest::Test
|
8
8
|
def setup
|
9
9
|
@index = Index.new
|
10
|
+
RBSIndexer.new(@index).index_ruby_core
|
11
|
+
@default_indexed_entries = @index.instance_variable_get(:@entries).dup
|
10
12
|
end
|
11
13
|
|
12
14
|
private
|
@@ -15,8 +17,9 @@ module RubyIndexer
|
|
15
17
|
@index.index_single(IndexablePath.new(nil, "/fake/path/foo.rb"), source)
|
16
18
|
end
|
17
19
|
|
18
|
-
def assert_entry(expected_name, type, expected_location)
|
20
|
+
def assert_entry(expected_name, type, expected_location, visibility: nil)
|
19
21
|
entries = @index[expected_name]
|
22
|
+
refute_nil(entries, "Expected #{expected_name} to be indexed")
|
20
23
|
refute_empty(entries, "Expected #{expected_name} to be indexed")
|
21
24
|
|
22
25
|
entry = entries.first
|
@@ -28,6 +31,8 @@ module RubyIndexer
|
|
28
31
|
":#{location.end_line - 1}-#{location.end_column}"
|
29
32
|
|
30
33
|
assert_equal(expected_location, location_string)
|
34
|
+
|
35
|
+
assert_equal(visibility, entry.visibility) if visibility
|
31
36
|
end
|
32
37
|
|
33
38
|
def refute_entry(expected_name)
|
@@ -39,6 +44,10 @@ module RubyIndexer
|
|
39
44
|
assert_empty(@index.instance_variable_get(:@entries), "Expected nothing to be indexed")
|
40
45
|
end
|
41
46
|
|
47
|
+
def assert_no_indexed_entries
|
48
|
+
assert_equal(@default_indexed_entries, @index.instance_variable_get(:@entries))
|
49
|
+
end
|
50
|
+
|
42
51
|
def assert_no_entry(entry)
|
43
52
|
refute(@index.instance_variable_get(:@entries).key?(entry), "Expected '#{entry}' to not be indexed")
|
44
53
|
end
|
data/lib/ruby_lsp/addon.rb
CHANGED
@@ -99,8 +99,8 @@ module RubyLsp
|
|
99
99
|
end
|
100
100
|
|
101
101
|
sig { returns(String) }
|
102
|
-
def
|
103
|
-
@errors.
|
102
|
+
def errors_details
|
103
|
+
@errors.map(&:full_message).join("\n\n")
|
104
104
|
end
|
105
105
|
|
106
106
|
# Each addon should implement `MyAddon#activate` and use to perform any sort of initialization, such as
|
@@ -131,11 +131,11 @@ module RubyLsp
|
|
131
131
|
sig do
|
132
132
|
overridable.params(
|
133
133
|
response_builder: ResponseBuilders::Hover,
|
134
|
-
|
134
|
+
node_context: NodeContext,
|
135
135
|
dispatcher: Prism::Dispatcher,
|
136
136
|
).void
|
137
137
|
end
|
138
|
-
def create_hover_listener(response_builder,
|
138
|
+
def create_hover_listener(response_builder, node_context, dispatcher); end
|
139
139
|
|
140
140
|
# Creates a new DocumentSymbol listener. This method is invoked on every DocumentSymbol request
|
141
141
|
sig do
|
@@ -159,21 +159,21 @@ module RubyLsp
|
|
159
159
|
overridable.params(
|
160
160
|
response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::Location],
|
161
161
|
uri: URI::Generic,
|
162
|
-
|
162
|
+
node_context: NodeContext,
|
163
163
|
dispatcher: Prism::Dispatcher,
|
164
164
|
).void
|
165
165
|
end
|
166
|
-
def create_definition_listener(response_builder, uri,
|
166
|
+
def create_definition_listener(response_builder, uri, node_context, dispatcher); end
|
167
167
|
|
168
168
|
# Creates a new Completion listener. This method is invoked on every Completion request
|
169
169
|
sig do
|
170
170
|
overridable.params(
|
171
171
|
response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::CompletionItem],
|
172
|
-
|
172
|
+
node_context: NodeContext,
|
173
173
|
dispatcher: Prism::Dispatcher,
|
174
174
|
uri: URI::Generic,
|
175
175
|
).void
|
176
176
|
end
|
177
|
-
def create_completion_listener(response_builder,
|
177
|
+
def create_completion_listener(response_builder, node_context, dispatcher, uri); end
|
178
178
|
end
|
179
179
|
end
|
data/lib/ruby_lsp/document.rb
CHANGED
@@ -110,7 +110,7 @@ module RubyLsp
|
|
110
110
|
params(
|
111
111
|
position: T::Hash[Symbol, T.untyped],
|
112
112
|
node_types: T::Array[T.class_of(Prism::Node)],
|
113
|
-
).returns(
|
113
|
+
).returns(NodeContext)
|
114
114
|
end
|
115
115
|
def locate_node(position, node_types: [])
|
116
116
|
locate(@parse_result.value, create_scanner.find_char_position(position), node_types: node_types)
|
@@ -121,13 +121,14 @@ module RubyLsp
|
|
121
121
|
node: Prism::Node,
|
122
122
|
char_position: Integer,
|
123
123
|
node_types: T::Array[T.class_of(Prism::Node)],
|
124
|
-
).returns(
|
124
|
+
).returns(NodeContext)
|
125
125
|
end
|
126
126
|
def locate(node, char_position, node_types: [])
|
127
127
|
queue = T.let(node.child_nodes.compact, T::Array[T.nilable(Prism::Node)])
|
128
128
|
closest = node
|
129
129
|
parent = T.let(nil, T.nilable(Prism::Node))
|
130
130
|
nesting = T.let([], T::Array[T.any(Prism::ClassNode, Prism::ModuleNode)])
|
131
|
+
call_node = T.let(nil, T.nilable(Prism::CallNode))
|
131
132
|
|
132
133
|
until queue.empty?
|
133
134
|
candidate = queue.shift
|
@@ -159,6 +160,15 @@ module RubyLsp
|
|
159
160
|
nesting << candidate
|
160
161
|
end
|
161
162
|
|
163
|
+
if candidate.is_a?(Prism::CallNode)
|
164
|
+
arg_loc = candidate.arguments&.location
|
165
|
+
blk_loc = candidate.block&.location
|
166
|
+
if (arg_loc && (arg_loc.start_offset...arg_loc.end_offset).cover?(char_position)) ||
|
167
|
+
(blk_loc && (blk_loc.start_offset...blk_loc.end_offset).cover?(char_position))
|
168
|
+
call_node = candidate
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
162
172
|
# If there are node types to filter by, and the current node is not one of those types, then skip it
|
163
173
|
next if node_types.any? && node_types.none? { |type| candidate.class == type }
|
164
174
|
|
@@ -170,7 +180,20 @@ module RubyLsp
|
|
170
180
|
end
|
171
181
|
end
|
172
182
|
|
173
|
-
|
183
|
+
# When targeting the constant part of a class/module definition, we do not want the nesting to be duplicated. That
|
184
|
+
# is, when targeting Bar in the following example:
|
185
|
+
#
|
186
|
+
# ```ruby
|
187
|
+
# class Foo::Bar; end
|
188
|
+
# ```
|
189
|
+
# The correct target is `Foo::Bar` with an empty nesting. `Foo::Bar` should not appear in the nesting stack, even
|
190
|
+
# though the class/module node does indeed enclose the target, because it would lead to incorrect behavior
|
191
|
+
if closest.is_a?(Prism::ConstantReadNode) || closest.is_a?(Prism::ConstantPathNode)
|
192
|
+
last_level = nesting.last
|
193
|
+
nesting.pop if last_level && last_level.constant_path == closest
|
194
|
+
end
|
195
|
+
|
196
|
+
NodeContext.new(closest, parent, nesting.map { |n| n.constant_path.location.slice }, call_node)
|
174
197
|
end
|
175
198
|
|
176
199
|
sig { returns(T::Boolean) }
|
@@ -54,18 +54,19 @@ module RubyLsp
|
|
54
54
|
|
55
55
|
sig { params(options: T::Hash[Symbol, T.untyped]).void }
|
56
56
|
def apply_options(options)
|
57
|
-
|
57
|
+
direct_dependencies = gather_direct_dependencies
|
58
|
+
all_dependencies = gather_direct_and_indirect_dependencies
|
58
59
|
workspace_uri = options.dig(:workspaceFolders, 0, :uri)
|
59
60
|
@workspace_uri = URI(workspace_uri) if workspace_uri
|
60
61
|
|
61
62
|
specified_formatter = options.dig(:initializationOptions, :formatter)
|
62
63
|
@formatter = specified_formatter if specified_formatter
|
63
|
-
@formatter = detect_formatter(
|
64
|
+
@formatter = detect_formatter(direct_dependencies, all_dependencies) if @formatter == "auto"
|
64
65
|
|
65
66
|
specified_linters = options.dig(:initializationOptions, :linters)
|
66
|
-
@linters = specified_linters || detect_linters(
|
67
|
-
@test_library = detect_test_library(
|
68
|
-
@typechecker = detect_typechecker(
|
67
|
+
@linters = specified_linters || detect_linters(direct_dependencies)
|
68
|
+
@test_library = detect_test_library(direct_dependencies)
|
69
|
+
@typechecker = detect_typechecker(direct_dependencies)
|
69
70
|
|
70
71
|
encodings = options.dig(:capabilities, :general, :positionEncodings)
|
71
72
|
@encoding = if !encodings || encodings.empty?
|
@@ -103,16 +104,18 @@ module RubyLsp
|
|
103
104
|
|
104
105
|
private
|
105
106
|
|
106
|
-
sig { params(
|
107
|
-
def detect_formatter(
|
107
|
+
sig { params(direct_dependencies: T::Array[String], all_dependencies: T::Array[String]).returns(String) }
|
108
|
+
def detect_formatter(direct_dependencies, all_dependencies)
|
108
109
|
# NOTE: Intentionally no $ at end, since we want to match rubocop-shopify, etc.
|
109
|
-
if
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
110
|
+
return "rubocop" if direct_dependencies.any?(/^rubocop/)
|
111
|
+
|
112
|
+
syntax_tree_is_direct_dependency = direct_dependencies.include?("syntax_tree")
|
113
|
+
return "syntax_tree" if syntax_tree_is_direct_dependency
|
114
|
+
|
115
|
+
rubocop_is_transitive_dependency = all_dependencies.include?("rubocop")
|
116
|
+
return "rubocop" if dot_rubocop_yml_present && rubocop_is_transitive_dependency
|
117
|
+
|
118
|
+
"none"
|
116
119
|
end
|
117
120
|
|
118
121
|
# Try to detect if there are linters in the project's dependencies. For auto-detection, we always only consider a
|
@@ -132,7 +135,7 @@ module RubyLsp
|
|
132
135
|
# by ruby-lsp-rails. A Rails app doesn't need to depend on the rails gem itself, individual components like
|
133
136
|
# activestorage may be added to the gemfile so that other components aren't downloaded. Check for the presence
|
134
137
|
# of bin/rails to support these cases.
|
135
|
-
elsif
|
138
|
+
elsif bin_rails_present
|
136
139
|
"rails"
|
137
140
|
# NOTE: Intentionally ends with $ to avoid mis-matching minitest-reporters, etc. in a Rails app.
|
138
141
|
elsif dependencies.any?(/^minitest$/)
|
@@ -162,8 +165,18 @@ module RubyLsp
|
|
162
165
|
false
|
163
166
|
end
|
164
167
|
|
168
|
+
sig { returns(T::Boolean) }
|
169
|
+
def bin_rails_present
|
170
|
+
File.exist?(File.join(workspace_path, "bin/rails"))
|
171
|
+
end
|
172
|
+
|
173
|
+
sig { returns(T::Boolean) }
|
174
|
+
def dot_rubocop_yml_present
|
175
|
+
File.exist?(File.join(workspace_path, ".rubocop.yml"))
|
176
|
+
end
|
177
|
+
|
165
178
|
sig { returns(T::Array[String]) }
|
166
|
-
def
|
179
|
+
def gather_direct_dependencies
|
167
180
|
Bundler.with_original_env { Bundler.default_gemfile }
|
168
181
|
Bundler.locked_gems.dependencies.keys + gemspec_dependencies
|
169
182
|
rescue Bundler::GemfileNotFound
|
@@ -176,5 +189,13 @@ module RubyLsp
|
|
176
189
|
.grep(Bundler::Source::Gemspec)
|
177
190
|
.flat_map { _1.gemspec&.dependencies&.map(&:name) }
|
178
191
|
end
|
192
|
+
|
193
|
+
sig { returns(T::Array[String]) }
|
194
|
+
def gather_direct_and_indirect_dependencies
|
195
|
+
Bundler.with_original_env { Bundler.default_gemfile }
|
196
|
+
Bundler.locked_gems.specs.map(&:name)
|
197
|
+
rescue Bundler::GemfileNotFound
|
198
|
+
[]
|
199
|
+
end
|
179
200
|
end
|
180
201
|
end
|
data/lib/ruby_lsp/internal.rb
CHANGED
@@ -18,6 +18,7 @@ require "set"
|
|
18
18
|
require "prism"
|
19
19
|
require "prism/visitor"
|
20
20
|
require "language_server-protocol"
|
21
|
+
require "rbs"
|
21
22
|
|
22
23
|
require "ruby-lsp"
|
23
24
|
require "ruby_lsp/base_server"
|
@@ -29,6 +30,7 @@ require "ruby_lsp/global_state"
|
|
29
30
|
require "ruby_lsp/server"
|
30
31
|
require "ruby_lsp/requests"
|
31
32
|
require "ruby_lsp/response_builders"
|
33
|
+
require "ruby_lsp/node_context"
|
32
34
|
require "ruby_lsp/document"
|
33
35
|
require "ruby_lsp/ruby_document"
|
34
36
|
require "ruby_lsp/store"
|
@@ -236,7 +236,7 @@ module RubyLsp
|
|
236
236
|
# so there must be something to the left of the available path.
|
237
237
|
group_stack = T.must(group_stack[last_dynamic_reference_index + 1..])
|
238
238
|
if method_name
|
239
|
-
" --name " + "/::#{Shellwords.escape(group_stack.join("::") + "#" + method_name)}$/"
|
239
|
+
" --name " + "/::#{Shellwords.escape(group_stack.join("::")) + "#" + Shellwords.escape(method_name)}$/"
|
240
240
|
else
|
241
241
|
# When clicking on a CodeLens for `Test`, `(#|::)` will match all tests
|
242
242
|
# that are registered on the class itself (matches after `#`) and all tests
|
@@ -245,7 +245,7 @@ module RubyLsp
|
|
245
245
|
end
|
246
246
|
elsif method_name
|
247
247
|
# We know the entire path, do an exact match
|
248
|
-
" --name " + Shellwords.escape(group_stack.join("::") + "#" + method_name)
|
248
|
+
" --name " + Shellwords.escape(group_stack.join("::")) + "#" + Shellwords.escape(method_name)
|
249
249
|
elsif spec_name
|
250
250
|
" --name " + "/#{Shellwords.escape(spec_name)}/"
|
251
251
|
else
|