ruby-lsp-rails 0.3.17 → 0.3.18
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/lib/ruby_lsp/ruby_lsp_rails/addon.rb +11 -8
- data/lib/ruby_lsp/ruby_lsp_rails/hover.rb +3 -26
- data/lib/ruby_lsp/ruby_lsp_rails/runner_client.rb +122 -35
- data/lib/ruby_lsp/ruby_lsp_rails/server.rb +23 -18
- data/lib/ruby_lsp_rails/version.rb +1 -1
- metadata +2 -3
- data/lib/ruby_lsp/ruby_lsp_rails/support/rails_document_client.rb +0 -131
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 159bd26f9edd1f611d7f13cb5864e63d8ecc1c6180bbf5a426b9e40a98ce25ba
|
4
|
+
data.tar.gz: 5266ed45904e0d62f074684a3a943ac1ef66b7827c4177ee7631d5ae46118553
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ab185650a7d5d24f1d7e7faf29cea5eabc99d905525ea840b2000003059393c60be8b79ef9f97ef3a78bf58ae695a5d5f19aa5bbecc9ea367d48090c9614538c
|
7
|
+
data.tar.gz: ef04db908af2f8ce9ee58a0059e51736f969f487b769a421d7df141cfc0518f31c2aa503eace5cc5ad24db36f56a1855ba55ffc4d5990f1eccc5d8cea1a4931c
|
@@ -28,6 +28,7 @@ module RubyLsp
|
|
28
28
|
# the real client is initialized, features that depend on it will not be blocked by using the NullClient
|
29
29
|
@rails_runner_client = T.let(NullClient.new, RunnerClient)
|
30
30
|
@global_state = T.let(nil, T.nilable(GlobalState))
|
31
|
+
@outgoing_queue = T.let(nil, T.nilable(Thread::Queue))
|
31
32
|
@addon_mutex = T.let(Mutex.new, Mutex)
|
32
33
|
@client_mutex = T.let(Mutex.new, Mutex)
|
33
34
|
@client_mutex.lock
|
@@ -35,7 +36,7 @@ module RubyLsp
|
|
35
36
|
Thread.new do
|
36
37
|
@addon_mutex.synchronize do
|
37
38
|
# We need to ensure the Rails client is fully loaded before we activate the server addons
|
38
|
-
@client_mutex.synchronize { @rails_runner_client = RunnerClient.create_client }
|
39
|
+
@client_mutex.synchronize { @rails_runner_client = RunnerClient.create_client(T.must(@outgoing_queue)) }
|
39
40
|
end
|
40
41
|
end
|
41
42
|
end
|
@@ -45,11 +46,13 @@ module RubyLsp
|
|
45
46
|
@addon_mutex.synchronize { @rails_runner_client }
|
46
47
|
end
|
47
48
|
|
48
|
-
sig { override.params(global_state: GlobalState,
|
49
|
-
def activate(global_state,
|
49
|
+
sig { override.params(global_state: GlobalState, outgoing_queue: Thread::Queue).void }
|
50
|
+
def activate(global_state, outgoing_queue)
|
50
51
|
@global_state = global_state
|
51
|
-
|
52
|
-
|
52
|
+
@outgoing_queue = outgoing_queue
|
53
|
+
@outgoing_queue << Notification.window_log_message("Activating Ruby LSP Rails add-on v#{VERSION}")
|
54
|
+
|
55
|
+
register_additional_file_watchers(global_state: global_state, outgoing_queue: outgoing_queue)
|
53
56
|
@global_state.index.register_enhancement(IndexingEnhancement.new)
|
54
57
|
|
55
58
|
# Start booting the real client in a background thread. Until this completes, the client will be a NullClient
|
@@ -123,11 +126,11 @@ module RubyLsp
|
|
123
126
|
end
|
124
127
|
end
|
125
128
|
|
126
|
-
sig { params(global_state: GlobalState,
|
127
|
-
def register_additional_file_watchers(global_state:,
|
129
|
+
sig { params(global_state: GlobalState, outgoing_queue: Thread::Queue).void }
|
130
|
+
def register_additional_file_watchers(global_state:, outgoing_queue:)
|
128
131
|
return unless global_state.supports_watching_files
|
129
132
|
|
130
|
-
|
133
|
+
outgoing_queue << Request.new(
|
131
134
|
id: "ruby-lsp-rails-file-watcher",
|
132
135
|
method: "client/registerCapability",
|
133
136
|
params: Interface::RegistrationParams.new(
|
@@ -1,8 +1,6 @@
|
|
1
1
|
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require_relative "support/rails_document_client"
|
5
|
-
|
6
4
|
module RubyLsp
|
7
5
|
module Rails
|
8
6
|
# 
|
@@ -34,7 +32,7 @@ module RubyLsp
|
|
34
32
|
@response_builder = response_builder
|
35
33
|
@nesting = T.let(node_context.nesting, T::Array[String])
|
36
34
|
@index = T.let(global_state.index, RubyIndexer::Index)
|
37
|
-
dispatcher.register(self, :on_constant_path_node_enter, :on_constant_read_node_enter
|
35
|
+
dispatcher.register(self, :on_constant_path_node_enter, :on_constant_read_node_enter)
|
38
36
|
end
|
39
37
|
|
40
38
|
sig { params(node: Prism::ConstantPathNode).void }
|
@@ -43,10 +41,7 @@ module RubyLsp
|
|
43
41
|
return unless entries
|
44
42
|
|
45
43
|
name = T.must(entries.first).name
|
46
|
-
|
47
44
|
generate_column_content(name)
|
48
|
-
|
49
|
-
generate_rails_document_link_hover(name, node.location)
|
50
45
|
end
|
51
46
|
|
52
47
|
sig { params(node: Prism::ConstantReadNode).void }
|
@@ -57,16 +52,6 @@ module RubyLsp
|
|
57
52
|
generate_column_content(T.must(entries.first).name)
|
58
53
|
end
|
59
54
|
|
60
|
-
sig { params(node: Prism::CallNode).void }
|
61
|
-
def on_call_node_enter(node)
|
62
|
-
message_value = node.message
|
63
|
-
message_loc = node.message_loc
|
64
|
-
|
65
|
-
return unless message_value && message_loc
|
66
|
-
|
67
|
-
generate_rails_document_link_hover(message_value, message_loc)
|
68
|
-
end
|
69
|
-
|
70
55
|
private
|
71
56
|
|
72
57
|
sig { params(name: String).void }
|
@@ -77,8 +62,8 @@ module RubyLsp
|
|
77
62
|
schema_file = model[:schema_file]
|
78
63
|
|
79
64
|
@response_builder.push(
|
80
|
-
"[Schema](#{URI::Generic.from_path(path: schema_file)})",
|
81
|
-
category: :
|
65
|
+
"[Schema](#{URI::Generic.from_path(path: schema_file)})\n",
|
66
|
+
category: :documentation,
|
82
67
|
) if schema_file
|
83
68
|
|
84
69
|
@response_builder.push(
|
@@ -89,14 +74,6 @@ module RubyLsp
|
|
89
74
|
category: :documentation,
|
90
75
|
)
|
91
76
|
end
|
92
|
-
|
93
|
-
sig { params(name: String, location: Prism::Location).void }
|
94
|
-
def generate_rails_document_link_hover(name, location)
|
95
|
-
urls = Support::RailsDocumentClient.generate_rails_document_urls(name)
|
96
|
-
return if urls.empty?
|
97
|
-
|
98
|
-
@response_builder.push(urls.join("\n\n"), category: :links)
|
99
|
-
end
|
100
77
|
end
|
101
78
|
end
|
102
79
|
end
|
@@ -10,20 +10,34 @@ module RubyLsp
|
|
10
10
|
class << self
|
11
11
|
extend T::Sig
|
12
12
|
|
13
|
-
sig { returns(RunnerClient) }
|
14
|
-
def create_client
|
13
|
+
sig { params(outgoing_queue: Thread::Queue).returns(RunnerClient) }
|
14
|
+
def create_client(outgoing_queue)
|
15
15
|
if File.exist?("bin/rails")
|
16
|
-
new
|
16
|
+
new(outgoing_queue)
|
17
17
|
else
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
18
|
+
unless outgoing_queue.closed?
|
19
|
+
outgoing_queue << RubyLsp::Notification.window_log_message(
|
20
|
+
<<~MESSAGE.chomp,
|
21
|
+
Ruby LSP Rails failed to locate bin/rails in the current directory: #{Dir.pwd}
|
22
|
+
Server dependent features will not be available
|
23
|
+
MESSAGE
|
24
|
+
type: RubyLsp::Constant::MessageType::WARNING,
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
22
28
|
NullClient.new
|
23
29
|
end
|
24
30
|
rescue Errno::ENOENT, StandardError => e # rubocop:disable Lint/ShadowedException
|
25
|
-
|
26
|
-
|
31
|
+
unless outgoing_queue.closed?
|
32
|
+
outgoing_queue << RubyLsp::Notification.window_log_message(
|
33
|
+
<<~MESSAGE.chomp,
|
34
|
+
Ruby LSP Rails failed to initialize server: #{e.full_message}
|
35
|
+
Server dependent features will not be available
|
36
|
+
MESSAGE
|
37
|
+
type: Constant::MessageType::ERROR,
|
38
|
+
)
|
39
|
+
end
|
40
|
+
|
27
41
|
NullClient.new
|
28
42
|
end
|
29
43
|
end
|
@@ -39,8 +53,9 @@ module RubyLsp
|
|
39
53
|
sig { returns(String) }
|
40
54
|
attr_reader :rails_root
|
41
55
|
|
42
|
-
sig { void }
|
43
|
-
def initialize
|
56
|
+
sig { params(outgoing_queue: Thread::Queue).void }
|
57
|
+
def initialize(outgoing_queue)
|
58
|
+
@outgoing_queue = T.let(outgoing_queue, Thread::Queue)
|
44
59
|
@mutex = T.let(Mutex.new, Mutex)
|
45
60
|
# Spring needs a Process session ID. It uses this ID to "attach" itself to the parent process, so that when the
|
46
61
|
# parent ends, the spring process ends as well. If this is not set, Spring will throw an error while trying to
|
@@ -69,7 +84,7 @@ module RubyLsp
|
|
69
84
|
@stdout.binmode
|
70
85
|
@stderr.binmode
|
71
86
|
|
72
|
-
|
87
|
+
log_message("Ruby LSP Rails booting server")
|
73
88
|
count = 0
|
74
89
|
|
75
90
|
begin
|
@@ -77,21 +92,29 @@ module RubyLsp
|
|
77
92
|
initialize_response = T.must(read_response)
|
78
93
|
@rails_root = T.let(initialize_response[:root], String)
|
79
94
|
rescue EmptyMessageError
|
80
|
-
|
95
|
+
log_message("Ruby LSP Rails is retrying initialize (#{count})")
|
81
96
|
retry if count < MAX_RETRIES
|
82
97
|
end
|
83
98
|
|
84
|
-
|
99
|
+
log_message("Finished booting Ruby LSP Rails server")
|
85
100
|
|
86
101
|
unless ENV["RAILS_ENV"] == "test"
|
87
102
|
at_exit do
|
88
103
|
if @wait_thread.alive?
|
89
|
-
$stderr.puts("Ruby LSP Rails is force killing the server")
|
90
104
|
sleep(0.5) # give the server a bit of time if we already issued a shutdown notification
|
91
105
|
force_kill
|
92
106
|
end
|
93
107
|
end
|
94
108
|
end
|
109
|
+
|
110
|
+
@logger_thread = T.let(
|
111
|
+
Thread.new do
|
112
|
+
while (content = @stderr.gets("\n"))
|
113
|
+
log_message(content, type: RubyLsp::Constant::MessageType::LOG)
|
114
|
+
end
|
115
|
+
end,
|
116
|
+
Thread,
|
117
|
+
)
|
95
118
|
rescue Errno::EPIPE, IncompleteMessageError
|
96
119
|
raise InitializationError, @stderr.read
|
97
120
|
end
|
@@ -100,7 +123,10 @@ module RubyLsp
|
|
100
123
|
def register_server_addon(server_addon_path)
|
101
124
|
send_notification("server_addon/register", server_addon_path: server_addon_path)
|
102
125
|
rescue IncompleteMessageError
|
103
|
-
|
126
|
+
log_message(
|
127
|
+
"Ruby LSP Rails failed to register server addon #{server_addon_path}",
|
128
|
+
type: RubyLsp::Constant::MessageType::ERROR,
|
129
|
+
)
|
104
130
|
nil
|
105
131
|
end
|
106
132
|
|
@@ -108,7 +134,10 @@ module RubyLsp
|
|
108
134
|
def model(name)
|
109
135
|
make_request("model", name: name)
|
110
136
|
rescue IncompleteMessageError
|
111
|
-
|
137
|
+
log_message(
|
138
|
+
"Ruby LSP Rails failed to get model information",
|
139
|
+
type: RubyLsp::Constant::MessageType::ERROR,
|
140
|
+
)
|
112
141
|
nil
|
113
142
|
end
|
114
143
|
|
@@ -124,15 +153,22 @@ module RubyLsp
|
|
124
153
|
model_name: model_name,
|
125
154
|
association_name: association_name,
|
126
155
|
)
|
127
|
-
rescue
|
128
|
-
|
156
|
+
rescue IncompleteMessageError
|
157
|
+
log_message(
|
158
|
+
"Ruby LSP Rails failed to get association location",
|
159
|
+
type: RubyLsp::Constant::MessageType::ERROR,
|
160
|
+
)
|
161
|
+
nil
|
129
162
|
end
|
130
163
|
|
131
164
|
sig { params(name: String).returns(T.nilable(T::Hash[Symbol, T.untyped])) }
|
132
165
|
def route_location(name)
|
133
166
|
make_request("route_location", name: name)
|
134
167
|
rescue IncompleteMessageError
|
135
|
-
|
168
|
+
log_message(
|
169
|
+
"Ruby LSP Rails failed to get route location",
|
170
|
+
type: RubyLsp::Constant::MessageType::ERROR,
|
171
|
+
)
|
136
172
|
nil
|
137
173
|
end
|
138
174
|
|
@@ -140,22 +176,58 @@ module RubyLsp
|
|
140
176
|
def route(controller:, action:)
|
141
177
|
make_request("route_info", controller: controller, action: action)
|
142
178
|
rescue IncompleteMessageError
|
143
|
-
|
179
|
+
log_message(
|
180
|
+
"Ruby LSP Rails failed to get route information",
|
181
|
+
type: RubyLsp::Constant::MessageType::ERROR,
|
182
|
+
)
|
183
|
+
nil
|
184
|
+
end
|
185
|
+
|
186
|
+
# Delegates a notification to a server add-on
|
187
|
+
sig { params(server_addon_name: String, request_name: String, params: T.untyped).void }
|
188
|
+
def delegate_notification(server_addon_name:, request_name:, **params)
|
189
|
+
send_notification(
|
190
|
+
"server_addon/delegate",
|
191
|
+
request_name: request_name,
|
192
|
+
server_addon_name: server_addon_name,
|
193
|
+
**params,
|
194
|
+
)
|
195
|
+
end
|
196
|
+
|
197
|
+
# Delegates a request to a server add-on
|
198
|
+
sig do
|
199
|
+
params(
|
200
|
+
server_addon_name: String,
|
201
|
+
request_name: String,
|
202
|
+
params: T.untyped,
|
203
|
+
).returns(T.nilable(T::Hash[Symbol, T.untyped]))
|
204
|
+
end
|
205
|
+
def delegate_request(server_addon_name:, request_name:, **params)
|
206
|
+
make_request(
|
207
|
+
"server_addon/delegate",
|
208
|
+
server_addon_name: server_addon_name,
|
209
|
+
request_name: request_name,
|
210
|
+
**params,
|
211
|
+
)
|
212
|
+
rescue IncompleteMessageError
|
144
213
|
nil
|
145
214
|
end
|
146
215
|
|
147
216
|
sig { void }
|
148
217
|
def trigger_reload
|
149
|
-
|
218
|
+
log_message("Reloading Rails application")
|
150
219
|
send_notification("reload")
|
151
220
|
rescue IncompleteMessageError
|
152
|
-
|
221
|
+
log_message(
|
222
|
+
"Ruby LSP Rails failed to trigger reload",
|
223
|
+
type: RubyLsp::Constant::MessageType::ERROR,
|
224
|
+
)
|
153
225
|
nil
|
154
226
|
end
|
155
227
|
|
156
228
|
sig { void }
|
157
229
|
def shutdown
|
158
|
-
|
230
|
+
log_message("Ruby LSP Rails shutting down server")
|
159
231
|
send_message("shutdown")
|
160
232
|
sleep(0.5) # give the server a bit of time to shutdown
|
161
233
|
[@stdin, @stdout, @stderr].each(&:close)
|
@@ -172,24 +244,24 @@ module RubyLsp
|
|
172
244
|
sig do
|
173
245
|
params(
|
174
246
|
request: String,
|
175
|
-
params: T.
|
247
|
+
params: T.untyped,
|
176
248
|
).returns(T.nilable(T::Hash[Symbol, T.untyped]))
|
177
249
|
end
|
178
|
-
def make_request(request, params
|
179
|
-
send_message(request, params)
|
250
|
+
def make_request(request, **params)
|
251
|
+
send_message(request, **params)
|
180
252
|
read_response
|
181
253
|
end
|
182
254
|
|
183
255
|
# Notifications are like messages, but one-way, with no response sent back.
|
184
|
-
sig { params(request: String, params: T.
|
185
|
-
def send_notification(request, params
|
256
|
+
sig { params(request: String, params: T.untyped).void }
|
257
|
+
def send_notification(request, **params) = send_message(request, **params)
|
186
258
|
|
187
259
|
private
|
188
260
|
|
189
|
-
sig { overridable.params(request: String, params: T.
|
190
|
-
def send_message(request, params
|
261
|
+
sig { overridable.params(request: String, params: T.untyped).void }
|
262
|
+
def send_message(request, **params)
|
191
263
|
message = { method: request }
|
192
|
-
message[:params] = params
|
264
|
+
message[:params] = params
|
193
265
|
json = message.to_json
|
194
266
|
|
195
267
|
@mutex.synchronize do
|
@@ -214,7 +286,10 @@ module RubyLsp
|
|
214
286
|
response = JSON.parse(T.must(raw_response), symbolize_names: true)
|
215
287
|
|
216
288
|
if response[:error]
|
217
|
-
|
289
|
+
log_message(
|
290
|
+
"Ruby LSP Rails error: #{response[:error]}",
|
291
|
+
type: RubyLsp::Constant::MessageType::ERROR,
|
292
|
+
)
|
218
293
|
return
|
219
294
|
end
|
220
295
|
|
@@ -229,6 +304,13 @@ module RubyLsp
|
|
229
304
|
# Windows does not support the `TERM` signal, so we're forced to use `KILL` here
|
230
305
|
Process.kill(T.must(Signal.list["KILL"]), @wait_thread.pid)
|
231
306
|
end
|
307
|
+
|
308
|
+
sig { params(message: ::String, type: ::Integer).void }
|
309
|
+
def log_message(message, type: RubyLsp::Constant::MessageType::LOG)
|
310
|
+
return if @outgoing_queue.closed?
|
311
|
+
|
312
|
+
@outgoing_queue << RubyLsp::Notification.window_log_message(message, type: type)
|
313
|
+
end
|
232
314
|
end
|
233
315
|
|
234
316
|
class NullClient < RunnerClient
|
@@ -255,8 +337,13 @@ module RubyLsp
|
|
255
337
|
|
256
338
|
private
|
257
339
|
|
258
|
-
sig {
|
259
|
-
def
|
340
|
+
sig { params(message: ::String, type: ::Integer).void }
|
341
|
+
def log_message(message, type: RubyLsp::Constant::MessageType::LOG)
|
342
|
+
# no-op
|
343
|
+
end
|
344
|
+
|
345
|
+
sig { override.params(request: String, params: T.untyped).void }
|
346
|
+
def send_message(request, **params)
|
260
347
|
# no-op
|
261
348
|
end
|
262
349
|
|
@@ -8,7 +8,22 @@ require "json"
|
|
8
8
|
|
9
9
|
module RubyLsp
|
10
10
|
module Rails
|
11
|
+
module Common
|
12
|
+
# Write a message to the client. Can be used for sending notifications to the editor
|
13
|
+
def send_message(message)
|
14
|
+
json_message = message.to_json
|
15
|
+
@stdout.write("Content-Length: #{json_message.length}\r\n\r\n#{json_message}")
|
16
|
+
end
|
17
|
+
|
18
|
+
# Log a debug message to the editor's output
|
19
|
+
def debug_message(message)
|
20
|
+
$stderr.puts(message)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
11
24
|
class ServerAddon
|
25
|
+
include Common
|
26
|
+
|
12
27
|
@server_addon_classes = []
|
13
28
|
@server_addons = {}
|
14
29
|
|
@@ -38,12 +53,6 @@ module RubyLsp
|
|
38
53
|
@stdout = stdout
|
39
54
|
end
|
40
55
|
|
41
|
-
# Write a response back. Can be used for sending notifications to the editor
|
42
|
-
def write_response(response)
|
43
|
-
json_response = response.to_json
|
44
|
-
@stdout.write("Content-Length: #{json_response.length}\r\n\r\n#{json_response}")
|
45
|
-
end
|
46
|
-
|
47
56
|
def name
|
48
57
|
raise NotImplementedError, "Not implemented!"
|
49
58
|
end
|
@@ -54,6 +63,8 @@ module RubyLsp
|
|
54
63
|
end
|
55
64
|
|
56
65
|
class Server
|
66
|
+
include Common
|
67
|
+
|
57
68
|
def initialize(stdout: $stdout, override_default_output_device: true)
|
58
69
|
# Grab references to the original pipes so that we can change the default output device further down
|
59
70
|
@stdin = $stdin
|
@@ -79,8 +90,7 @@ module RubyLsp
|
|
79
90
|
routes_reloader = ::Rails.application.routes_reloader
|
80
91
|
routes_reloader.execute_unless_loaded if routes_reloader&.respond_to?(:execute_unless_loaded)
|
81
92
|
|
82
|
-
|
83
|
-
@stdout.write("Content-Length: #{initialize_result.length}\r\n\r\n#{initialize_result}")
|
93
|
+
send_message({ result: { message: "ok", root: ::Rails.root.to_s } })
|
84
94
|
|
85
95
|
while @running
|
86
96
|
headers = @stdin.gets("\r\n\r\n")
|
@@ -96,15 +106,15 @@ module RubyLsp
|
|
96
106
|
when "shutdown"
|
97
107
|
@running = false
|
98
108
|
when "model"
|
99
|
-
|
109
|
+
send_message(resolve_database_info_from_model(params.fetch(:name)))
|
100
110
|
when "association_target_location"
|
101
|
-
|
111
|
+
send_message(resolve_association_target(params))
|
102
112
|
when "reload"
|
103
113
|
::Rails.application.reloader.reload!
|
104
114
|
when "route_location"
|
105
|
-
|
115
|
+
send_message(route_location(params.fetch(:name)))
|
106
116
|
when "route_info"
|
107
|
-
|
117
|
+
send_message(resolve_route_info(params))
|
108
118
|
when "server_addon/register"
|
109
119
|
require params[:server_addon_path]
|
110
120
|
ServerAddon.finalize_registrations!(@stdout)
|
@@ -114,16 +124,11 @@ module RubyLsp
|
|
114
124
|
ServerAddon.delegate(server_addon_name, request_name, params)
|
115
125
|
end
|
116
126
|
rescue => e
|
117
|
-
|
127
|
+
send_message({ error: e.full_message(highlight: false) })
|
118
128
|
end
|
119
129
|
|
120
130
|
private
|
121
131
|
|
122
|
-
def write_response(response)
|
123
|
-
json_response = response.to_json
|
124
|
-
@stdout.write("Content-Length: #{json_response.length}\r\n\r\n#{json_response}")
|
125
|
-
end
|
126
|
-
|
127
132
|
def resolve_route_info(requirements)
|
128
133
|
if requirements[:controller]
|
129
134
|
requirements[:controller] = requirements.fetch(:controller).underscore.delete_suffix("_controller")
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-lsp-rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.18
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shopify
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-10-
|
11
|
+
date: 2024-10-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ruby-lsp
|
@@ -53,7 +53,6 @@ files:
|
|
53
53
|
- lib/ruby_lsp/ruby_lsp_rails/support/associations.rb
|
54
54
|
- lib/ruby_lsp/ruby_lsp_rails/support/callbacks.rb
|
55
55
|
- lib/ruby_lsp/ruby_lsp_rails/support/location_builder.rb
|
56
|
-
- lib/ruby_lsp/ruby_lsp_rails/support/rails_document_client.rb
|
57
56
|
- lib/ruby_lsp_rails/railtie.rb
|
58
57
|
- lib/ruby_lsp_rails/version.rb
|
59
58
|
- lib/tasks/ruby_lsp_rails_tasks.rake
|
@@ -1,131 +0,0 @@
|
|
1
|
-
# typed: strict
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
require "net/http"
|
5
|
-
|
6
|
-
module RubyLsp
|
7
|
-
module Rails
|
8
|
-
module Support
|
9
|
-
class RailsDocumentClient
|
10
|
-
RAILS_DOC_HOST = "https://api.rubyonrails.org"
|
11
|
-
|
12
|
-
SUPPORTED_RAILS_DOC_NAMESPACES = T.let(
|
13
|
-
Regexp.union(
|
14
|
-
/ActionDispatch/,
|
15
|
-
/ActionController/,
|
16
|
-
/AbstractController/,
|
17
|
-
/ActiveRecord/,
|
18
|
-
/ActiveModel/,
|
19
|
-
/ActiveStorage/,
|
20
|
-
/ActionText/,
|
21
|
-
/ActiveJob/,
|
22
|
-
).freeze,
|
23
|
-
Regexp,
|
24
|
-
)
|
25
|
-
|
26
|
-
RAILTIES_VERSION = T.let(
|
27
|
-
[*::Gem::Specification.default_stubs, *::Gem::Specification.stubs].find do |s|
|
28
|
-
s.name == "railties"
|
29
|
-
end&.version&.to_s,
|
30
|
-
T.nilable(String),
|
31
|
-
)
|
32
|
-
|
33
|
-
class << self
|
34
|
-
extend T::Sig
|
35
|
-
|
36
|
-
sig { params(name: String).returns(T::Array[String]) }
|
37
|
-
def generate_rails_document_urls(name)
|
38
|
-
docs = search_index&.fetch(name, nil)
|
39
|
-
|
40
|
-
return [] unless docs
|
41
|
-
|
42
|
-
docs.map do |doc|
|
43
|
-
owner = doc[:owner]
|
44
|
-
|
45
|
-
link_name =
|
46
|
-
# class/module name
|
47
|
-
if owner == name
|
48
|
-
name
|
49
|
-
else
|
50
|
-
"#{owner}##{name}"
|
51
|
-
end
|
52
|
-
|
53
|
-
"[Rails Document: `#{link_name}`](#{doc[:url]})"
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
sig { returns(T.nilable(T::Hash[String, T::Array[T::Hash[Symbol, String]]])) }
|
58
|
-
private def search_index
|
59
|
-
@rails_documents ||= T.let(
|
60
|
-
build_search_index,
|
61
|
-
T.nilable(T::Hash[String, T::Array[T::Hash[Symbol, String]]]),
|
62
|
-
)
|
63
|
-
end
|
64
|
-
|
65
|
-
sig { returns(T.nilable(T::Hash[String, T::Array[T::Hash[Symbol, String]]])) }
|
66
|
-
private def build_search_index
|
67
|
-
return unless RAILTIES_VERSION
|
68
|
-
|
69
|
-
$stderr.puts("Fetching search index for Rails documentation")
|
70
|
-
|
71
|
-
response = Net::HTTP.get_response(
|
72
|
-
URI("#{RAILS_DOC_HOST}/v#{RAILTIES_VERSION}/js/search_index.js"),
|
73
|
-
{ "User-Agent" => "ruby-lsp-rails/#{RubyLsp::Rails::VERSION}" },
|
74
|
-
)
|
75
|
-
|
76
|
-
body = case response
|
77
|
-
when Net::HTTPSuccess
|
78
|
-
$stderr.puts("Finished fetching search index for Rails documentation")
|
79
|
-
response.body
|
80
|
-
when Net::HTTPRedirection
|
81
|
-
# If the version's doc is not found, e.g. Rails main, it'll be redirected
|
82
|
-
# In this case, we just fetch the latest doc
|
83
|
-
response = Net::HTTP.get_response(URI("#{RAILS_DOC_HOST}/js/search_index.js"))
|
84
|
-
if response.is_a?(Net::HTTPSuccess)
|
85
|
-
$stderr.puts("Finished fetching search index for Rails documentation")
|
86
|
-
response.body
|
87
|
-
end
|
88
|
-
else
|
89
|
-
$stderr.puts("Response failed: #{response.inspect}")
|
90
|
-
nil
|
91
|
-
end
|
92
|
-
|
93
|
-
process_search_index(body) if body
|
94
|
-
rescue StandardError => e
|
95
|
-
$stderr.puts("Exception occurred when fetching Rails document index: #{e.inspect}")
|
96
|
-
end
|
97
|
-
|
98
|
-
sig { params(js: String).returns(T::Hash[String, T::Array[T::Hash[Symbol, String]]]) }
|
99
|
-
private def process_search_index(js)
|
100
|
-
raw_data = js.sub("var search_data = ", "")
|
101
|
-
info = JSON.parse(raw_data).dig("index", "info")
|
102
|
-
|
103
|
-
# An entry looks like this:
|
104
|
-
#
|
105
|
-
# ["belongs_to", # method or module/class
|
106
|
-
# "ActiveRecord::Associations::ClassMethods", # method owner
|
107
|
-
# "classes/ActiveRecord/Associations/ClassMethods.html#method-i-belongs_to", # path to the document
|
108
|
-
# "(name, scope = nil, **options)", # method's parameters
|
109
|
-
# "<p>Specifies a one-to-one association with another class..."] # document preview
|
110
|
-
#
|
111
|
-
info.each_with_object({}) do |(method_or_class, method_owner, doc_path, _, doc_preview), table|
|
112
|
-
# If a method doesn't have documentation, there's no need to generate the link to it.
|
113
|
-
next if doc_preview.nil? || doc_preview.empty?
|
114
|
-
|
115
|
-
# If the method or class/module is not from the supported namespace, reject it
|
116
|
-
next unless [method_or_class, method_owner].any? do |elem|
|
117
|
-
elem.match?(SUPPORTED_RAILS_DOC_NAMESPACES)
|
118
|
-
end
|
119
|
-
|
120
|
-
owner = method_owner.empty? ? method_or_class : method_owner
|
121
|
-
table[method_or_class] ||= []
|
122
|
-
# It's possible to have multiple modules defining the same method name. For example,
|
123
|
-
# both `ActiveRecord::FinderMethods` and `ActiveRecord::Associations::CollectionProxy` defines `#find`
|
124
|
-
table[method_or_class] << { owner: owner, url: "#{RAILS_DOC_HOST}/v#{RAILTIES_VERSION}/#{doc_path}" }
|
125
|
-
end
|
126
|
-
end
|
127
|
-
end
|
128
|
-
end
|
129
|
-
end
|
130
|
-
end
|
131
|
-
end
|