ruby-lsp 0.16.7 → 0.17.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|