ruby-lsp-rails 0.4.0 → 0.4.1
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/Rakefile +11 -2
- data/lib/ruby_lsp/ruby_lsp_rails/addon.rb +46 -56
- data/lib/ruby_lsp/ruby_lsp_rails/code_lens.rb +15 -24
- data/lib/ruby_lsp/ruby_lsp_rails/completion.rb +6 -14
- data/lib/ruby_lsp/ruby_lsp_rails/definition.rb +9 -20
- data/lib/ruby_lsp/ruby_lsp_rails/document_symbol.rb +12 -24
- data/lib/ruby_lsp/ruby_lsp_rails/hover.rb +39 -27
- data/lib/ruby_lsp/ruby_lsp_rails/indexing_enhancement.rb +7 -20
- data/lib/ruby_lsp/ruby_lsp_rails/rails_test_style.rb +150 -0
- data/lib/ruby_lsp/ruby_lsp_rails/runner_client.rb +40 -55
- data/lib/ruby_lsp/ruby_lsp_rails/server.rb +32 -0
- data/lib/ruby_lsp/ruby_lsp_rails/support/active_support_test_case_helper.rb +1 -3
- data/lib/ruby_lsp/ruby_lsp_rails/support/location_builder.rb +1 -3
- data/lib/ruby_lsp_rails/version.rb +1 -1
- metadata +6 -5
@@ -15,18 +15,9 @@ module RubyLsp
|
|
15
15
|
# # ^ hovering here will show information about the User model
|
16
16
|
# ```
|
17
17
|
class Hover
|
18
|
-
extend T::Sig
|
19
18
|
include Requests::Support::Common
|
20
19
|
|
21
|
-
|
22
|
-
params(
|
23
|
-
client: RunnerClient,
|
24
|
-
response_builder: ResponseBuilders::Hover,
|
25
|
-
node_context: NodeContext,
|
26
|
-
global_state: GlobalState,
|
27
|
-
dispatcher: Prism::Dispatcher,
|
28
|
-
).void
|
29
|
-
end
|
20
|
+
#: (RunnerClient client, ResponseBuilders::Hover response_builder, NodeContext node_context, GlobalState global_state, Prism::Dispatcher dispatcher) -> void
|
30
21
|
def initialize(client, response_builder, node_context, global_state, dispatcher)
|
31
22
|
@client = client
|
32
23
|
@response_builder = response_builder
|
@@ -35,26 +26,26 @@ module RubyLsp
|
|
35
26
|
dispatcher.register(self, :on_constant_path_node_enter, :on_constant_read_node_enter)
|
36
27
|
end
|
37
28
|
|
38
|
-
|
29
|
+
#: (Prism::ConstantPathNode node) -> void
|
39
30
|
def on_constant_path_node_enter(node)
|
40
31
|
entries = @index.resolve(node.slice, @nesting)
|
41
32
|
return unless entries
|
42
33
|
|
43
|
-
name =
|
34
|
+
name = entries.first.name
|
44
35
|
generate_column_content(name)
|
45
36
|
end
|
46
37
|
|
47
|
-
|
38
|
+
#: (Prism::ConstantReadNode node) -> void
|
48
39
|
def on_constant_read_node_enter(node)
|
49
40
|
entries = @index.resolve(node.name.to_s, @nesting)
|
50
41
|
return unless entries
|
51
42
|
|
52
|
-
generate_column_content(
|
43
|
+
generate_column_content(entries.first.name)
|
53
44
|
end
|
54
45
|
|
55
46
|
private
|
56
47
|
|
57
|
-
|
48
|
+
#: (String name) -> void
|
58
49
|
def generate_column_content(name)
|
59
50
|
model = @client.model(name)
|
60
51
|
return if model.nil?
|
@@ -66,20 +57,41 @@ module RubyLsp
|
|
66
57
|
category: :documentation,
|
67
58
|
) if schema_file
|
68
59
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
60
|
+
if model[:columns].any?
|
61
|
+
@response_builder.push(
|
62
|
+
"### Columns",
|
63
|
+
category: :documentation,
|
64
|
+
)
|
65
|
+
@response_builder.push(
|
66
|
+
model[:columns].map do |name, type, default_value, nullable|
|
67
|
+
primary_key_suffix = " (PK)" if model[:primary_keys].include?(name)
|
68
|
+
foreign_key_suffix = " (FK)" if model[:foreign_keys].include?(name)
|
69
|
+
suffixes = []
|
70
|
+
suffixes << "default: #{format_default(default_value, type)}" if default_value
|
71
|
+
suffixes << "not null" unless nullable || primary_key_suffix
|
72
|
+
suffix_string = " - #{suffixes.join(" - ")}" if suffixes.any?
|
73
|
+
"- **#{name}**: #{type}#{primary_key_suffix}#{foreign_key_suffix}#{suffix_string}\n"
|
74
|
+
end.join("\n"),
|
75
|
+
category: :documentation,
|
76
|
+
)
|
77
|
+
end
|
78
|
+
|
79
|
+
if model[:indexes].any?
|
80
|
+
@response_builder.push(
|
81
|
+
"### Indexes",
|
82
|
+
category: :documentation,
|
83
|
+
)
|
84
|
+
@response_builder.push(
|
85
|
+
model[:indexes].map do |index|
|
86
|
+
uniqueness = index[:unique] ? " (unique)" : ""
|
87
|
+
"- **#{index[:name]}** (#{index[:columns].join(",")})#{uniqueness}"
|
88
|
+
end.join("\n"),
|
89
|
+
category: :documentation,
|
90
|
+
)
|
91
|
+
end
|
80
92
|
end
|
81
93
|
|
82
|
-
|
94
|
+
#: (String default_value, String type) -> String
|
83
95
|
def format_default(default_value, type)
|
84
96
|
case type
|
85
97
|
when "boolean"
|
@@ -4,13 +4,8 @@
|
|
4
4
|
module RubyLsp
|
5
5
|
module Rails
|
6
6
|
class IndexingEnhancement < RubyIndexer::Enhancement
|
7
|
-
|
8
|
-
|
9
|
-
sig do
|
10
|
-
override.params(
|
11
|
-
call_node: Prism::CallNode,
|
12
|
-
).void
|
13
|
-
end
|
7
|
+
# @override
|
8
|
+
#: (Prism::CallNode call_node) -> void
|
14
9
|
def on_call_node_enter(call_node)
|
15
10
|
owner = @listener.current_owner
|
16
11
|
return unless owner
|
@@ -26,11 +21,8 @@ module RubyLsp
|
|
26
21
|
end
|
27
22
|
end
|
28
23
|
|
29
|
-
|
30
|
-
|
31
|
-
call_node: Prism::CallNode,
|
32
|
-
).void
|
33
|
-
end
|
24
|
+
# @override
|
25
|
+
#: (Prism::CallNode call_node) -> void
|
34
26
|
def on_call_node_leave(call_node)
|
35
27
|
if call_node.name == :class_methods && call_node.block
|
36
28
|
@listener.pop_namespace_stack
|
@@ -39,12 +31,7 @@ module RubyLsp
|
|
39
31
|
|
40
32
|
private
|
41
33
|
|
42
|
-
|
43
|
-
params(
|
44
|
-
owner: RubyIndexer::Entry::Namespace,
|
45
|
-
call_node: Prism::CallNode,
|
46
|
-
).void
|
47
|
-
end
|
34
|
+
#: (RubyIndexer::Entry::Namespace owner, Prism::CallNode call_node) -> void
|
48
35
|
def handle_association(owner, call_node)
|
49
36
|
arguments = call_node.arguments&.arguments
|
50
37
|
return unless arguments
|
@@ -73,7 +60,7 @@ module RubyLsp
|
|
73
60
|
@listener.add_method("#{name}=", loc, writer_signatures)
|
74
61
|
end
|
75
62
|
|
76
|
-
|
63
|
+
#: (RubyIndexer::Entry::Namespace owner, Prism::CallNode call_node) -> void
|
77
64
|
def handle_concern_extend(owner, call_node)
|
78
65
|
arguments = call_node.arguments&.arguments
|
79
66
|
return unless arguments
|
@@ -98,7 +85,7 @@ module RubyLsp
|
|
98
85
|
end
|
99
86
|
end
|
100
87
|
|
101
|
-
|
88
|
+
#: (RubyIndexer::Entry::Namespace owner, Prism::CallNode call_node) -> void
|
102
89
|
def handle_class_methods(owner, call_node)
|
103
90
|
return unless call_node.block
|
104
91
|
|
@@ -0,0 +1,150 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module RubyLsp
|
5
|
+
module Rails
|
6
|
+
class RailsTestStyle < Listeners::TestDiscovery
|
7
|
+
BASE_COMMAND = "#{RbConfig.ruby} bin/rails test" #: String
|
8
|
+
|
9
|
+
class << self
|
10
|
+
#: (Array[Hash[Symbol, untyped]]) -> Array[String]
|
11
|
+
def resolve_test_commands(items)
|
12
|
+
commands = []
|
13
|
+
queue = items.dup
|
14
|
+
|
15
|
+
full_files = []
|
16
|
+
|
17
|
+
until queue.empty?
|
18
|
+
item = T.must(queue.shift)
|
19
|
+
tags = Set.new(item[:tags])
|
20
|
+
next unless tags.include?("framework:rails")
|
21
|
+
|
22
|
+
children = item[:children]
|
23
|
+
uri = URI(item[:uri])
|
24
|
+
path = uri.full_path
|
25
|
+
next unless path
|
26
|
+
|
27
|
+
if tags.include?("test_dir")
|
28
|
+
if children.empty?
|
29
|
+
full_files.concat(Dir.glob(
|
30
|
+
"#{path}/**/{*_test,test_*}.rb",
|
31
|
+
File::Constants::FNM_EXTGLOB | File::Constants::FNM_PATHNAME,
|
32
|
+
))
|
33
|
+
end
|
34
|
+
elsif tags.include?("test_file")
|
35
|
+
full_files << path if children.empty?
|
36
|
+
elsif tags.include?("test_group")
|
37
|
+
commands << "#{BASE_COMMAND} #{path} --name \"/#{Shellwords.escape(item[:id])}(#|::)/\""
|
38
|
+
else
|
39
|
+
full_files << "#{path}:#{item.dig(:range, :start, :line) + 1}"
|
40
|
+
end
|
41
|
+
|
42
|
+
queue.concat(children)
|
43
|
+
end
|
44
|
+
|
45
|
+
unless full_files.empty?
|
46
|
+
commands << "#{BASE_COMMAND} #{full_files.join(" ")}"
|
47
|
+
end
|
48
|
+
|
49
|
+
commands
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
#: (RunnerClient client, ResponseBuilders::TestCollection response_builder, GlobalState global_state, Prism::Dispatcher dispatcher, URI::Generic uri) -> void
|
54
|
+
def initialize(client, response_builder, global_state, dispatcher, uri)
|
55
|
+
super(response_builder, global_state, dispatcher, uri)
|
56
|
+
|
57
|
+
dispatcher.register(
|
58
|
+
self,
|
59
|
+
:on_class_node_enter,
|
60
|
+
:on_call_node_enter,
|
61
|
+
:on_def_node_enter,
|
62
|
+
)
|
63
|
+
end
|
64
|
+
|
65
|
+
#: (Prism::ClassNode node) -> void
|
66
|
+
def on_class_node_enter(node)
|
67
|
+
with_test_ancestor_tracking(node) do |name, ancestors|
|
68
|
+
if declarative_minitest?(ancestors, name)
|
69
|
+
test_item = Requests::Support::TestItem.new(
|
70
|
+
name,
|
71
|
+
name,
|
72
|
+
@uri,
|
73
|
+
range_from_node(node),
|
74
|
+
framework: :rails,
|
75
|
+
)
|
76
|
+
|
77
|
+
@response_builder.add(test_item)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
#: (Prism::CallNode node) -> void
|
83
|
+
def on_call_node_enter(node)
|
84
|
+
return unless node.name == :test
|
85
|
+
return unless node.block
|
86
|
+
|
87
|
+
arguments = node.arguments&.arguments
|
88
|
+
first_arg = arguments&.first
|
89
|
+
return unless first_arg.is_a?(Prism::StringNode)
|
90
|
+
|
91
|
+
test_name = first_arg.content
|
92
|
+
test_name = "<empty test name>" if test_name.empty?
|
93
|
+
|
94
|
+
# Rails' `test "foo bar"` helper defines a method `def test_foo_bar`. We normalize test names
|
95
|
+
# the same way (spaces to underscores, prefix with `test_`) to match the actual method names
|
96
|
+
# Rails uses at runtime, ensuring proper test discovery and execution.
|
97
|
+
rails_normalized_name = "test_#{test_name.gsub(/\s+/, "_")}"
|
98
|
+
|
99
|
+
add_test_item(node, rails_normalized_name)
|
100
|
+
end
|
101
|
+
|
102
|
+
#: (Prism::DefNode node) -> void
|
103
|
+
def on_def_node_enter(node)
|
104
|
+
return if @visibility_stack.last != :public
|
105
|
+
|
106
|
+
name = node.name.to_s
|
107
|
+
return unless name.start_with?("test_")
|
108
|
+
|
109
|
+
add_test_item(node, name)
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
#: (Array[String] attached_ancestors, String fully_qualified_name) -> bool
|
115
|
+
def declarative_minitest?(attached_ancestors, fully_qualified_name)
|
116
|
+
# The declarative test style is present as long as the class extends
|
117
|
+
# ActiveSupport::Testing::Declarative
|
118
|
+
name_parts = fully_qualified_name.split("::")
|
119
|
+
singleton_name = "#{name_parts.join("::")}::<Class:#{name_parts.last}>"
|
120
|
+
@index.linearized_ancestors_of(singleton_name).include?("ActiveSupport::Testing::Declarative")
|
121
|
+
rescue RubyIndexer::Index::NonExistingNamespaceError
|
122
|
+
false
|
123
|
+
end
|
124
|
+
|
125
|
+
#: (Prism::Node node, String test_name) -> void
|
126
|
+
def add_test_item(node, test_name)
|
127
|
+
test_item = group_test_item
|
128
|
+
return unless test_item
|
129
|
+
|
130
|
+
test_item.add(Requests::Support::TestItem.new(
|
131
|
+
"#{test_item.id}##{test_name}",
|
132
|
+
test_name,
|
133
|
+
@uri,
|
134
|
+
range_from_node(node),
|
135
|
+
framework: :rails,
|
136
|
+
))
|
137
|
+
end
|
138
|
+
|
139
|
+
#: -> Requests::Support::TestItem?
|
140
|
+
def group_test_item
|
141
|
+
current_group_name = RubyIndexer::Index.actual_nesting(@nesting, nil).join("::")
|
142
|
+
|
143
|
+
# If we're finding a test method, but for the wrong framework, then the group test item will not have been
|
144
|
+
# previously pushed and thus we return early and avoid adding items for a framework this listener is not
|
145
|
+
# interested in
|
146
|
+
@response_builder[current_group_name]
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
@@ -8,9 +8,7 @@ module RubyLsp
|
|
8
8
|
module Rails
|
9
9
|
class RunnerClient
|
10
10
|
class << self
|
11
|
-
|
12
|
-
|
13
|
-
sig { params(outgoing_queue: Thread::Queue, global_state: RubyLsp::GlobalState).returns(RunnerClient) }
|
11
|
+
#: (Thread::Queue outgoing_queue, RubyLsp::GlobalState global_state) -> RunnerClient
|
14
12
|
def create_client(outgoing_queue, global_state)
|
15
13
|
if File.exist?("bin/rails")
|
16
14
|
new(outgoing_queue, global_state)
|
@@ -46,12 +44,10 @@ module RubyLsp
|
|
46
44
|
class MessageError < StandardError; end
|
47
45
|
class EmptyMessageError < MessageError; end
|
48
46
|
|
49
|
-
|
50
|
-
|
51
|
-
sig { returns(String) }
|
47
|
+
#: String
|
52
48
|
attr_reader :rails_root
|
53
49
|
|
54
|
-
|
50
|
+
#: (Thread::Queue outgoing_queue, RubyLsp::GlobalState global_state) -> void
|
55
51
|
def initialize(outgoing_queue, global_state)
|
56
52
|
@outgoing_queue = T.let(outgoing_queue, Thread::Queue)
|
57
53
|
@mutex = T.let(Mutex.new, Mutex)
|
@@ -128,7 +124,7 @@ module RubyLsp
|
|
128
124
|
raise InitializationError, @stderr.read
|
129
125
|
end
|
130
126
|
|
131
|
-
|
127
|
+
#: (String server_addon_path) -> void
|
132
128
|
def register_server_addon(server_addon_path)
|
133
129
|
send_notification("server_addon/register", server_addon_path: server_addon_path)
|
134
130
|
rescue MessageError
|
@@ -139,7 +135,7 @@ module RubyLsp
|
|
139
135
|
nil
|
140
136
|
end
|
141
137
|
|
142
|
-
|
138
|
+
#: (String name) -> Hash[Symbol, untyped]?
|
143
139
|
def model(name)
|
144
140
|
make_request("model", name: name)
|
145
141
|
rescue MessageError
|
@@ -150,12 +146,7 @@ module RubyLsp
|
|
150
146
|
nil
|
151
147
|
end
|
152
148
|
|
153
|
-
|
154
|
-
params(
|
155
|
-
model_name: String,
|
156
|
-
association_name: String,
|
157
|
-
).returns(T.nilable(T::Hash[Symbol, T.untyped]))
|
158
|
-
end
|
149
|
+
#: (model_name: String, association_name: String) -> Hash[Symbol, untyped]?
|
159
150
|
def association_target_location(model_name:, association_name:)
|
160
151
|
make_request(
|
161
152
|
"association_target_location",
|
@@ -170,7 +161,7 @@ module RubyLsp
|
|
170
161
|
nil
|
171
162
|
end
|
172
163
|
|
173
|
-
|
164
|
+
#: (String name) -> Hash[Symbol, untyped]?
|
174
165
|
def route_location(name)
|
175
166
|
make_request("route_location", name: name)
|
176
167
|
rescue MessageError
|
@@ -181,7 +172,7 @@ module RubyLsp
|
|
181
172
|
nil
|
182
173
|
end
|
183
174
|
|
184
|
-
|
175
|
+
#: (controller: String, action: String) -> Hash[Symbol, untyped]?
|
185
176
|
def route(controller:, action:)
|
186
177
|
make_request("route_info", controller: controller, action: action)
|
187
178
|
rescue MessageError
|
@@ -193,7 +184,7 @@ module RubyLsp
|
|
193
184
|
end
|
194
185
|
|
195
186
|
# Delegates a notification to a server add-on
|
196
|
-
|
187
|
+
#: (server_addon_name: String, request_name: String, **untyped params) -> void
|
197
188
|
def delegate_notification(server_addon_name:, request_name:, **params)
|
198
189
|
send_notification(
|
199
190
|
"server_addon/delegate",
|
@@ -203,7 +194,7 @@ module RubyLsp
|
|
203
194
|
)
|
204
195
|
end
|
205
196
|
|
206
|
-
|
197
|
+
#: -> String?
|
207
198
|
def pending_migrations_message
|
208
199
|
response = make_request("pending_migrations_message")
|
209
200
|
response[:pending_migrations_message] if response
|
@@ -215,7 +206,7 @@ module RubyLsp
|
|
215
206
|
nil
|
216
207
|
end
|
217
208
|
|
218
|
-
|
209
|
+
#: -> Hash[Symbol, untyped]?
|
219
210
|
def run_migrations
|
220
211
|
make_request("run_migrations")
|
221
212
|
rescue MessageError
|
@@ -227,13 +218,7 @@ module RubyLsp
|
|
227
218
|
end
|
228
219
|
|
229
220
|
# Delegates a request to a server add-on
|
230
|
-
|
231
|
-
params(
|
232
|
-
server_addon_name: String,
|
233
|
-
request_name: String,
|
234
|
-
params: T.untyped,
|
235
|
-
).returns(T.nilable(T::Hash[Symbol, T.untyped]))
|
236
|
-
end
|
221
|
+
#: (server_addon_name: String, request_name: String, **untyped params) -> Hash[Symbol, untyped]?
|
237
222
|
def delegate_request(server_addon_name:, request_name:, **params)
|
238
223
|
make_request(
|
239
224
|
"server_addon/delegate",
|
@@ -245,7 +230,7 @@ module RubyLsp
|
|
245
230
|
nil
|
246
231
|
end
|
247
232
|
|
248
|
-
|
233
|
+
#: -> void
|
249
234
|
def trigger_reload
|
250
235
|
log_message("Reloading Rails application")
|
251
236
|
send_notification("reload")
|
@@ -257,7 +242,7 @@ module RubyLsp
|
|
257
242
|
nil
|
258
243
|
end
|
259
244
|
|
260
|
-
|
245
|
+
#: -> void
|
261
246
|
def shutdown
|
262
247
|
log_message("Ruby LSP Rails shutting down server")
|
263
248
|
send_message("shutdown")
|
@@ -268,34 +253,30 @@ module RubyLsp
|
|
268
253
|
force_kill
|
269
254
|
end
|
270
255
|
|
271
|
-
|
256
|
+
#: -> bool
|
272
257
|
def stopped?
|
273
258
|
[@stdin, @stdout, @stderr].all?(&:closed?) && !@wait_thread.alive?
|
274
259
|
end
|
275
260
|
|
276
|
-
|
261
|
+
#: -> bool
|
277
262
|
def connected?
|
278
263
|
true
|
279
264
|
end
|
280
265
|
|
281
266
|
private
|
282
267
|
|
283
|
-
|
284
|
-
params(
|
285
|
-
request: String,
|
286
|
-
params: T.untyped,
|
287
|
-
).returns(T.nilable(T::Hash[Symbol, T.untyped]))
|
288
|
-
end
|
268
|
+
#: (String request, **untyped params) -> Hash[Symbol, untyped]?
|
289
269
|
def make_request(request, **params)
|
290
270
|
send_message(request, **params)
|
291
271
|
read_response
|
292
272
|
end
|
293
273
|
|
294
274
|
# Notifications are like messages, but one-way, with no response sent back.
|
295
|
-
|
275
|
+
#: (String request, **untyped params) -> void
|
296
276
|
def send_notification(request, **params) = send_message(request, **params)
|
297
277
|
|
298
|
-
|
278
|
+
# @overridable
|
279
|
+
#: (String request, **untyped params) -> void
|
299
280
|
def send_message(request, **params)
|
300
281
|
message = { method: request }
|
301
282
|
message[:params] = params
|
@@ -308,7 +289,8 @@ module RubyLsp
|
|
308
289
|
# The server connection died
|
309
290
|
end
|
310
291
|
|
311
|
-
|
292
|
+
# @overridable
|
293
|
+
#: -> Hash[Symbol, untyped]?
|
312
294
|
def read_response
|
313
295
|
raw_response = @mutex.synchronize do
|
314
296
|
content_length = read_content_length
|
@@ -334,20 +316,20 @@ module RubyLsp
|
|
334
316
|
nil
|
335
317
|
end
|
336
318
|
|
337
|
-
|
319
|
+
#: -> void
|
338
320
|
def force_kill
|
339
321
|
# Windows does not support the `TERM` signal, so we're forced to use `KILL` here
|
340
322
|
Process.kill(T.must(Signal.list["KILL"]), @wait_thread.pid)
|
341
323
|
end
|
342
324
|
|
343
|
-
|
325
|
+
#: (::String message, ?type: ::Integer) -> void
|
344
326
|
def log_message(message, type: RubyLsp::Constant::MessageType::LOG)
|
345
327
|
return if @outgoing_queue.closed?
|
346
328
|
|
347
329
|
@outgoing_queue << RubyLsp::Notification.window_log_message(message, type: type)
|
348
330
|
end
|
349
331
|
|
350
|
-
|
332
|
+
#: -> Integer?
|
351
333
|
def read_content_length
|
352
334
|
headers = @stdout.gets("\r\n\r\n")
|
353
335
|
return unless headers
|
@@ -359,7 +341,7 @@ module RubyLsp
|
|
359
341
|
end
|
360
342
|
|
361
343
|
# Read a server notification from stderr. Only intended to be used by notifier thread
|
362
|
-
|
344
|
+
#: -> Hash[Symbol, untyped]?
|
363
345
|
def read_notification
|
364
346
|
headers = @stderr.gets("\r\n\r\n")
|
365
347
|
return unless headers
|
@@ -373,7 +355,7 @@ module RubyLsp
|
|
373
355
|
JSON.parse(raw_content, symbolize_names: true)
|
374
356
|
end
|
375
357
|
|
376
|
-
|
358
|
+
#: (GlobalState global_state) -> String
|
377
359
|
def server_relevant_capabilities(global_state)
|
378
360
|
{
|
379
361
|
supports_progress: global_state.client_capabilities.supports_progress,
|
@@ -382,45 +364,48 @@ module RubyLsp
|
|
382
364
|
end
|
383
365
|
|
384
366
|
class NullClient < RunnerClient
|
385
|
-
|
386
|
-
|
387
|
-
sig { void }
|
367
|
+
#: -> void
|
388
368
|
def initialize # rubocop:disable Lint/MissingSuper
|
389
369
|
end
|
390
370
|
|
391
|
-
|
371
|
+
# @override
|
372
|
+
#: -> void
|
392
373
|
def shutdown
|
393
374
|
# no-op
|
394
375
|
end
|
395
376
|
|
396
|
-
|
377
|
+
# @override
|
378
|
+
#: -> bool
|
397
379
|
def stopped?
|
398
380
|
true
|
399
381
|
end
|
400
382
|
|
401
|
-
|
383
|
+
# @override
|
384
|
+
#: -> String
|
402
385
|
def rails_root
|
403
386
|
Dir.pwd
|
404
387
|
end
|
405
388
|
|
406
|
-
|
389
|
+
#: -> bool
|
407
390
|
def connected?
|
408
391
|
false
|
409
392
|
end
|
410
393
|
|
411
394
|
private
|
412
395
|
|
413
|
-
|
396
|
+
#: (::String message, ?type: ::Integer) -> void
|
414
397
|
def log_message(message, type: RubyLsp::Constant::MessageType::LOG)
|
415
398
|
# no-op
|
416
399
|
end
|
417
400
|
|
418
|
-
|
401
|
+
# @override
|
402
|
+
#: (String request, **untyped params) -> void
|
419
403
|
def send_message(request, **params)
|
420
404
|
# no-op
|
421
405
|
end
|
422
406
|
|
423
|
-
|
407
|
+
# @override
|
408
|
+
#: -> Hash[Symbol, untyped]?
|
424
409
|
def read_response
|
425
410
|
# no-op
|
426
411
|
end
|
@@ -364,6 +364,8 @@ module RubyLsp
|
|
364
364
|
info = {
|
365
365
|
columns: const.columns.map { |column| [column.name, column.type, column.default, column.null] },
|
366
366
|
primary_keys: Array(const.primary_key),
|
367
|
+
foreign_keys: collect_model_foreign_keys(const),
|
368
|
+
indexes: collect_model_indexes(const),
|
367
369
|
}
|
368
370
|
|
369
371
|
if ActiveRecord::Tasks::DatabaseTasks.respond_to?(:schema_dump_path)
|
@@ -435,6 +437,36 @@ module RubyLsp
|
|
435
437
|
::ActionView::PathRegistry.file_system_resolver_hooks.clear
|
436
438
|
end
|
437
439
|
end
|
440
|
+
|
441
|
+
def collect_model_foreign_keys(model)
|
442
|
+
return [] unless model.connection.respond_to?(:supports_foreign_keys?) &&
|
443
|
+
model.connection.supports_foreign_keys?
|
444
|
+
|
445
|
+
model.connection.foreign_keys(model.table_name).map do |key_definition|
|
446
|
+
key_definition.options[:column]
|
447
|
+
end
|
448
|
+
end
|
449
|
+
|
450
|
+
def collect_model_indexes(model)
|
451
|
+
return [] unless database_supports_indexing?(model)
|
452
|
+
|
453
|
+
model.connection.indexes(model.table_name).map do |index_definition|
|
454
|
+
{
|
455
|
+
name: index_definition.name,
|
456
|
+
columns: index_definition.columns,
|
457
|
+
unique: index_definition.unique,
|
458
|
+
}
|
459
|
+
end
|
460
|
+
end
|
461
|
+
|
462
|
+
def database_supports_indexing?(model)
|
463
|
+
return @database_supports_indexing if instance_variable_defined?(:@database_supports_indexing)
|
464
|
+
|
465
|
+
model.connection.indexes(model.table_name)
|
466
|
+
@database_supports_indexing = true
|
467
|
+
rescue NotImplementedError
|
468
|
+
@database_supports_indexing = false
|
469
|
+
end
|
438
470
|
end
|
439
471
|
end
|
440
472
|
end
|
@@ -4,9 +4,7 @@
|
|
4
4
|
module RubyLsp
|
5
5
|
module Rails
|
6
6
|
module ActiveSupportTestCaseHelper
|
7
|
-
|
8
|
-
|
9
|
-
sig { params(node: Prism::CallNode).returns(T.nilable(String)) }
|
7
|
+
#: (Prism::CallNode node) -> String?
|
10
8
|
def extract_test_case_name(node)
|
11
9
|
message_value = node.message
|
12
10
|
return unless message_value == "test" || message_value == "it"
|
@@ -6,9 +6,7 @@ module RubyLsp
|
|
6
6
|
module Support
|
7
7
|
class LocationBuilder
|
8
8
|
class << self
|
9
|
-
|
10
|
-
|
11
|
-
sig { params(location_string: String).returns(Interface::Location) }
|
9
|
+
#: (String location_string) -> Interface::Location
|
12
10
|
def line_location_from_s(location_string)
|
13
11
|
*file_parts, line = location_string.split(":")
|
14
12
|
raise ArgumentError, "Invalid location string given" if file_parts.empty?
|