ruby-lsp-rails 0.4.1 → 0.4.2
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 +2 -11
- data/lib/ruby_lsp/ruby_lsp_rails/addon.rb +13 -12
- data/lib/ruby_lsp/ruby_lsp_rails/code_lens.rb +8 -6
- data/lib/ruby_lsp/ruby_lsp_rails/completion.rb +1 -1
- data/lib/ruby_lsp/ruby_lsp_rails/definition.rb +4 -2
- data/lib/ruby_lsp/ruby_lsp_rails/document_symbol.rb +12 -6
- data/lib/ruby_lsp/ruby_lsp_rails/hover.rb +2 -2
- data/lib/ruby_lsp/ruby_lsp_rails/rails_test_style.rb +2 -2
- data/lib/ruby_lsp/ruby_lsp_rails/runner_client.rb +24 -24
- data/lib/ruby_lsp/ruby_lsp_rails/server.rb +86 -32
- data/lib/ruby_lsp/ruby_lsp_rails/support/active_support_test_case_helper.rb +2 -1
- data/lib/ruby_lsp/ruby_lsp_rails/support/associations.rb +6 -9
- data/lib/ruby_lsp/ruby_lsp_rails/support/callbacks.rb +48 -57
- data/lib/ruby_lsp_rails/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 76155fcaf046652d4e13655bf8f1d38764a0a755cd4e090a70a7e551e77dc598
|
4
|
+
data.tar.gz: d417e7a57d97dbd0a7b1b2aa2ec999793ef6a8fb3ada39a0c1ad06688af8fd3b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5f53d2aebe48cb0ce755541d4af87e564d2ca5dcdd70f411390c4d685890f6d398da400f223a77678c56e915b6a34ea6330b4495a02f407967040c07d9a5a164
|
7
|
+
data.tar.gz: 063117bc235a4bf99f148d65c8170601a35b58b7d95ad8098114ccd213c930f0d9a8ea12f51b12022c7ad6284672209ae0eafbd475f079c325ff8bc236c08688
|
data/Rakefile
CHANGED
@@ -10,19 +10,10 @@ load "rails/tasks/statistics.rake"
|
|
10
10
|
require "bundler/gem_tasks"
|
11
11
|
require "rake/testtask"
|
12
12
|
|
13
|
-
# Since `server.rb` runs within the host Rails application, we want to ensure
|
14
|
-
# we don't accidentally depend on sorbet-runtime. `server_test` intentionally doesn't use `test_helper`.
|
15
|
-
# We run `server_test` in a seperate Rake task.
|
16
13
|
Rake::TestTask.new(:test) do |t|
|
17
14
|
t.libs << "test"
|
18
15
|
t.libs << "lib"
|
19
|
-
t.test_files = FileList["test/**/*_test.rb"]
|
16
|
+
t.test_files = FileList["test/**/*_test.rb"]
|
20
17
|
end
|
21
18
|
|
22
|
-
|
23
|
-
t.libs << "test"
|
24
|
-
t.libs << "lib"
|
25
|
-
t.test_files = ["test/ruby_lsp_rails/server_test.rb"]
|
26
|
-
end
|
27
|
-
|
28
|
-
task default: [:"db:setup", :test, :server_test]
|
19
|
+
task default: :test
|
@@ -28,24 +28,25 @@ module RubyLsp
|
|
28
28
|
|
29
29
|
# We first initialize the client as a NullClient, so that we can start the server in a background thread. Until
|
30
30
|
# the real client is initialized, features that depend on it will not be blocked by using the NullClient
|
31
|
-
@rails_runner_client =
|
32
|
-
@global_state =
|
33
|
-
@outgoing_queue =
|
34
|
-
@settings =
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
@addon_mutex = T.let(Mutex.new, Mutex)
|
41
|
-
@client_mutex = T.let(Mutex.new, Mutex)
|
31
|
+
@rails_runner_client = NullClient.new #: RunnerClient
|
32
|
+
@global_state = nil #: GlobalState?
|
33
|
+
@outgoing_queue = nil #: Thread::Queue?
|
34
|
+
@settings = {
|
35
|
+
enablePendingMigrationsPrompt: true,
|
36
|
+
} #: Hash[Symbol, untyped],
|
37
|
+
|
38
|
+
@addon_mutex = Mutex.new #: Mutex
|
39
|
+
@client_mutex = Mutex.new #: Mutex
|
42
40
|
@client_mutex.lock
|
43
41
|
|
44
42
|
Thread.new do
|
45
43
|
@addon_mutex.synchronize do
|
46
44
|
# We need to ensure the Rails client is fully loaded before we activate the server addons
|
47
45
|
@client_mutex.synchronize do
|
48
|
-
@rails_runner_client = RunnerClient.create_client(
|
46
|
+
@rails_runner_client = RunnerClient.create_client(
|
47
|
+
@outgoing_queue, #: as !nil
|
48
|
+
@global_state, #: as !nil
|
49
|
+
)
|
49
50
|
end
|
50
51
|
offer_to_run_pending_migrations
|
51
52
|
end
|
@@ -80,10 +80,10 @@ module RubyLsp
|
|
80
80
|
@client = client
|
81
81
|
@global_state = global_state
|
82
82
|
@response_builder = response_builder
|
83
|
-
@path =
|
84
|
-
@group_id =
|
85
|
-
@group_id_stack =
|
86
|
-
@constant_name_stack =
|
83
|
+
@path = uri.to_standardized_path #: String?
|
84
|
+
@group_id = 1 #: Integer
|
85
|
+
@group_id_stack = [] #: Array[Integer]
|
86
|
+
@constant_name_stack = [] #: Array[[String, String?]]
|
87
87
|
|
88
88
|
dispatcher.register(
|
89
89
|
self,
|
@@ -209,7 +209,7 @@ module RubyLsp
|
|
209
209
|
|
210
210
|
#: (Prism::DefNode node) -> void
|
211
211
|
def add_route_code_lens_to_action(node)
|
212
|
-
class_name, _ =
|
212
|
+
class_name, _ = @constant_name_stack.last #: as !nil
|
213
213
|
route = @client.route(controller: class_name, action: node.name.to_s)
|
214
214
|
return unless route
|
215
215
|
|
@@ -236,7 +236,9 @@ module RubyLsp
|
|
236
236
|
|
237
237
|
#: -> String?
|
238
238
|
def migration_version
|
239
|
-
File.basename(
|
239
|
+
File.basename(
|
240
|
+
@path, #: as !nil
|
241
|
+
).split("_").first
|
240
242
|
end
|
241
243
|
|
242
244
|
#: (Prism::Node node, name: String, command: String) -> void
|
@@ -37,7 +37,7 @@ module RubyLsp
|
|
37
37
|
return if resolved_class.nil?
|
38
38
|
|
39
39
|
arguments = @node_context.call_node.arguments&.arguments
|
40
|
-
indexed_call_node_args =
|
40
|
+
indexed_call_node_args = {} #: Hash[String, Prism::Node]
|
41
41
|
|
42
42
|
if arguments
|
43
43
|
indexed_call_node_args = index_call_node_args(arguments: arguments)
|
@@ -35,7 +35,7 @@ module RubyLsp
|
|
35
35
|
@client = client
|
36
36
|
@response_builder = response_builder
|
37
37
|
@node_context = node_context
|
38
|
-
@nesting =
|
38
|
+
@nesting = node_context.nesting #: Array[String]
|
39
39
|
@index = index
|
40
40
|
|
41
41
|
dispatcher.register(self, :on_call_node_enter, :on_symbol_node_enter, :on_string_node_enter)
|
@@ -121,7 +121,9 @@ module RubyLsp
|
|
121
121
|
|
122
122
|
#: (Prism::CallNode node) -> void
|
123
123
|
def handle_route(node)
|
124
|
-
result = @client.route_location(
|
124
|
+
result = @client.route_location(
|
125
|
+
node.message, #: as !nil
|
126
|
+
)
|
125
127
|
return unless result
|
126
128
|
|
127
129
|
@response_builder << Support::LocationBuilder.line_location_from_s(result.fetch(:location))
|
@@ -15,7 +15,7 @@ module RubyLsp
|
|
15
15
|
#: (ResponseBuilders::DocumentSymbol response_builder, Prism::Dispatcher dispatcher) -> void
|
16
16
|
def initialize(response_builder, dispatcher)
|
17
17
|
@response_builder = response_builder
|
18
|
-
@namespace_stack =
|
18
|
+
@namespace_stack = [] #: Array[String]
|
19
19
|
|
20
20
|
dispatcher.register(
|
21
21
|
self,
|
@@ -45,14 +45,16 @@ module RubyLsp
|
|
45
45
|
return if receiver && !receiver.is_a?(Prism::SelfNode)
|
46
46
|
|
47
47
|
message = node.message
|
48
|
+
return unless message
|
49
|
+
|
48
50
|
case message
|
49
51
|
when *Support::Callbacks::ALL, "validate"
|
50
|
-
handle_all_arg_types(node,
|
52
|
+
handle_all_arg_types(node, message)
|
51
53
|
when "validates", "validates!", "validates_each", "belongs_to", "has_one", "has_many",
|
52
54
|
"has_and_belongs_to_many", "attr_readonly", "scope"
|
53
|
-
handle_symbol_and_string_arg_types(node,
|
55
|
+
handle_symbol_and_string_arg_types(node, message)
|
54
56
|
when "validates_with"
|
55
|
-
handle_class_arg_types(node,
|
57
|
+
handle_class_arg_types(node, message)
|
56
58
|
end
|
57
59
|
end
|
58
60
|
|
@@ -113,7 +115,9 @@ module RubyLsp
|
|
113
115
|
append_document_symbol(
|
114
116
|
name: "#{message} :#{name}",
|
115
117
|
range: range_from_location(argument.location),
|
116
|
-
selection_range: range_from_location(
|
118
|
+
selection_range: range_from_location(
|
119
|
+
argument.value_loc, #: as !nil
|
120
|
+
),
|
117
121
|
)
|
118
122
|
when Prism::StringNode
|
119
123
|
name = argument.content
|
@@ -172,7 +176,9 @@ module RubyLsp
|
|
172
176
|
append_document_symbol(
|
173
177
|
name: "#{message} :#{name}",
|
174
178
|
range: range_from_location(argument.location),
|
175
|
-
selection_range: range_from_location(
|
179
|
+
selection_range: range_from_location(
|
180
|
+
argument.value_loc, #: as !nil
|
181
|
+
),
|
176
182
|
)
|
177
183
|
when Prism::StringNode
|
178
184
|
name = argument.content
|
@@ -21,8 +21,8 @@ module RubyLsp
|
|
21
21
|
def initialize(client, response_builder, node_context, global_state, dispatcher)
|
22
22
|
@client = client
|
23
23
|
@response_builder = response_builder
|
24
|
-
@nesting =
|
25
|
-
@index =
|
24
|
+
@nesting = node_context.nesting #: Array[String]
|
25
|
+
@index = global_state.index #: RubyIndexer::Index
|
26
26
|
dispatcher.register(self, :on_constant_path_node_enter, :on_constant_read_node_enter)
|
27
27
|
end
|
28
28
|
|
@@ -15,7 +15,7 @@ module RubyLsp
|
|
15
15
|
full_files = []
|
16
16
|
|
17
17
|
until queue.empty?
|
18
|
-
item =
|
18
|
+
item = queue.shift #: as !nil
|
19
19
|
tags = Set.new(item[:tags])
|
20
20
|
next unless tags.include?("framework:rails")
|
21
21
|
|
@@ -88,7 +88,7 @@ module RubyLsp
|
|
88
88
|
first_arg = arguments&.first
|
89
89
|
return unless first_arg.is_a?(Prism::StringNode)
|
90
90
|
|
91
|
-
test_name = first_arg.
|
91
|
+
test_name = first_arg.unescaped
|
92
92
|
test_name = "<empty test name>" if test_name.empty?
|
93
93
|
|
94
94
|
# Rails' `test "foo bar"` helper defines a method `def test_foo_bar`. We normalize test names
|
@@ -49,8 +49,8 @@ module RubyLsp
|
|
49
49
|
|
50
50
|
#: (Thread::Queue outgoing_queue, RubyLsp::GlobalState global_state) -> void
|
51
51
|
def initialize(outgoing_queue, global_state)
|
52
|
-
@outgoing_queue =
|
53
|
-
@mutex =
|
52
|
+
@outgoing_queue = outgoing_queue #: Thread::Queue
|
53
|
+
@mutex = Mutex.new #: Mutex
|
54
54
|
# Spring needs a Process session ID. It uses this ID to "attach" itself to the parent process, so that when the
|
55
55
|
# parent ends, the spring process ends as well. If this is not set, Spring will throw an error while trying to
|
56
56
|
# set its own session ID
|
@@ -78,21 +78,21 @@ module RubyLsp
|
|
78
78
|
)
|
79
79
|
end
|
80
80
|
|
81
|
-
@stdin =
|
82
|
-
@stdout =
|
83
|
-
@stderr =
|
81
|
+
@stdin = stdin #: IO
|
82
|
+
@stdout = stdout #: IO
|
83
|
+
@stderr = stderr #: IO
|
84
84
|
@stdin.sync = true
|
85
85
|
@stdout.sync = true
|
86
86
|
@stderr.sync = true
|
87
|
-
@wait_thread =
|
87
|
+
@wait_thread = wait_thread #: Process::Waiter
|
88
88
|
|
89
89
|
# We set binmode for Windows compatibility
|
90
90
|
@stdin.binmode
|
91
91
|
@stdout.binmode
|
92
92
|
@stderr.binmode
|
93
93
|
|
94
|
-
initialize_response =
|
95
|
-
@rails_root =
|
94
|
+
initialize_response = read_response #: as !nil
|
95
|
+
@rails_root = initialize_response[:root] #: String
|
96
96
|
log_message("Finished booting Ruby LSP Rails server")
|
97
97
|
|
98
98
|
unless ENV["RAILS_ENV"] == "test"
|
@@ -106,20 +106,17 @@ module RubyLsp
|
|
106
106
|
|
107
107
|
# Responsible for transmitting notifications coming from the server to the outgoing queue, so that we can do
|
108
108
|
# things such as showing progress notifications initiated by the server
|
109
|
-
@notifier_thread =
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
@outgoing_queue << notification
|
116
|
-
end
|
109
|
+
@notifier_thread = Thread.new do
|
110
|
+
until @stderr.closed?
|
111
|
+
notification = read_notification
|
112
|
+
|
113
|
+
unless @outgoing_queue.closed? || !notification
|
114
|
+
@outgoing_queue << notification
|
117
115
|
end
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
)
|
116
|
+
end
|
117
|
+
rescue IOError
|
118
|
+
# The server was shutdown and stderr is already closed
|
119
|
+
end #: Thread
|
123
120
|
rescue StandardError
|
124
121
|
raise InitializationError, @stderr.read
|
125
122
|
end
|
@@ -298,9 +295,9 @@ module RubyLsp
|
|
298
295
|
raise EmptyMessageError unless content_length
|
299
296
|
|
300
297
|
@stdout.read(content_length)
|
301
|
-
end
|
298
|
+
end #: as !nil
|
302
299
|
|
303
|
-
response = JSON.parse(
|
300
|
+
response = JSON.parse(raw_response, symbolize_names: true)
|
304
301
|
|
305
302
|
if response[:error]
|
306
303
|
log_message(
|
@@ -319,7 +316,10 @@ module RubyLsp
|
|
319
316
|
#: -> void
|
320
317
|
def force_kill
|
321
318
|
# Windows does not support the `TERM` signal, so we're forced to use `KILL` here
|
322
|
-
Process.kill(
|
319
|
+
Process.kill(
|
320
|
+
Signal.list["KILL"], #: as !nil
|
321
|
+
@wait_thread.pid,
|
322
|
+
)
|
323
323
|
end
|
324
324
|
|
325
325
|
#: (::String message, ?type: ::Integer) -> void
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "json"
|
@@ -7,14 +7,17 @@ require "delegate"
|
|
7
7
|
|
8
8
|
module RubyLsp
|
9
9
|
module Rails
|
10
|
+
# @requires_ancestor: ServerComponent
|
10
11
|
module Common
|
11
12
|
class Progress
|
13
|
+
#: (IO | StringIO, String, bool) -> void
|
12
14
|
def initialize(stderr, id, supports_progress)
|
13
15
|
@stderr = stderr
|
14
16
|
@id = id
|
15
17
|
@supports_progress = supports_progress
|
16
18
|
end
|
17
19
|
|
20
|
+
#: (percentage: Integer?, message: String?) -> void
|
18
21
|
def report(percentage: nil, message: nil)
|
19
22
|
return unless @supports_progress
|
20
23
|
return unless percentage || message
|
@@ -37,23 +40,27 @@ module RubyLsp
|
|
37
40
|
|
38
41
|
# Log a message to the editor's output panel. The type is the number of the message type, which can be found in
|
39
42
|
# the specification https://microsoft.github.io/language-server-protocol/specification/#messageType
|
43
|
+
#: (String, type: Integer) -> void
|
40
44
|
def log_message(message, type: 4)
|
41
45
|
send_notification({ method: "window/logMessage", params: { type: type, message: message } })
|
42
46
|
end
|
43
47
|
|
44
48
|
# Sends an error result to a request, if the request failed. DO NOT INVOKE THIS METHOD FOR NOTIFICATIONS! Use
|
45
49
|
# `log_message` instead, otherwise the client/server communication will go out of sync
|
50
|
+
#: (String) -> void
|
46
51
|
def send_error_response(message)
|
47
52
|
send_message({ error: message })
|
48
53
|
end
|
49
54
|
|
50
55
|
# Sends a result back to the client
|
56
|
+
#: (Hash[Symbol | String, untyped]?) -> void
|
51
57
|
def send_result(result)
|
52
58
|
send_message({ result: result })
|
53
59
|
end
|
54
60
|
|
55
61
|
# Handle possible errors for a request. This should only be used for requests, which means messages that return a
|
56
62
|
# response back to the client. Errors are returned as an error object back to the client
|
63
|
+
#: (String) { () -> void } -> void
|
57
64
|
def with_request_error_handling(request_name, &block)
|
58
65
|
block.call
|
59
66
|
rescue ActiveRecord::ConnectionNotEstablished
|
@@ -67,6 +74,7 @@ module RubyLsp
|
|
67
74
|
|
68
75
|
# Handle possible errors for a notification. This should only be used for notifications, which means messages that
|
69
76
|
# do not return a response back to the client. Errors are logged to the editor's output panel
|
77
|
+
#: (String) { () -> void } -> void
|
70
78
|
def with_notification_error_handling(notification_name, &block)
|
71
79
|
block.call
|
72
80
|
rescue ActiveRecord::ConnectionNotEstablished
|
@@ -78,8 +86,9 @@ module RubyLsp
|
|
78
86
|
log_message("Request #{notification_name} failed:\n#{e.full_message(highlight: false)}")
|
79
87
|
end
|
80
88
|
|
89
|
+
#: (String, String, percentage: Integer?, message: String?) -> void
|
81
90
|
def begin_progress(id, title, percentage: nil, message: nil)
|
82
|
-
return unless
|
91
|
+
return unless capabilities[:supports_progress]
|
83
92
|
|
84
93
|
# This is actually a request, but it is sent asynchronously and we do not return the response back to the
|
85
94
|
# server, so we consider it a notification from the perspective of the client/runtime server dynamic
|
@@ -103,8 +112,9 @@ module RubyLsp
|
|
103
112
|
})
|
104
113
|
end
|
105
114
|
|
115
|
+
#: (String, percentage: Integer?, message: String?) -> void
|
106
116
|
def report_progress(id, percentage: nil, message: nil)
|
107
|
-
return unless
|
117
|
+
return unless capabilities[:supports_progress]
|
108
118
|
|
109
119
|
send_notification({
|
110
120
|
method: "$/progress",
|
@@ -119,8 +129,9 @@ module RubyLsp
|
|
119
129
|
})
|
120
130
|
end
|
121
131
|
|
132
|
+
#: (String) -> void
|
122
133
|
def end_progress(id)
|
123
|
-
return unless
|
134
|
+
return unless capabilities[:supports_progress]
|
124
135
|
|
125
136
|
send_notification({
|
126
137
|
method: "$/progress",
|
@@ -131,9 +142,10 @@ module RubyLsp
|
|
131
142
|
})
|
132
143
|
end
|
133
144
|
|
145
|
+
#: (String, String, percentage: Integer?, message: String?) { (Progress) -> void } -> void
|
134
146
|
def with_progress(id, title, percentage: nil, message: nil, &block)
|
135
|
-
progress_block = Progress.new(
|
136
|
-
return block.call(progress_block) unless
|
147
|
+
progress_block = Progress.new(stderr, id, capabilities[:supports_progress])
|
148
|
+
return block.call(progress_block) unless capabilities[:supports_progress]
|
137
149
|
|
138
150
|
begin_progress(id, title, percentage: percentage, message: message)
|
139
151
|
block.call(progress_block)
|
@@ -143,86 +155,113 @@ module RubyLsp
|
|
143
155
|
private
|
144
156
|
|
145
157
|
# Write a response message back to the client
|
158
|
+
#: (Hash[String | Symbol, untyped]) -> void
|
146
159
|
def send_message(message)
|
147
160
|
json_message = message.to_json
|
148
|
-
|
161
|
+
stdout.write("Content-Length: #{json_message.bytesize}\r\n\r\n#{json_message}")
|
149
162
|
end
|
150
163
|
|
151
164
|
# Write a notification to the client to be transmitted to the editor
|
165
|
+
#: (Hash[String | Symbol, untyped]) -> void
|
152
166
|
def send_notification(message)
|
153
167
|
json_message = message.to_json
|
154
|
-
|
168
|
+
stderr.write("Content-Length: #{json_message.bytesize}\r\n\r\n#{json_message}")
|
155
169
|
end
|
156
170
|
end
|
157
171
|
|
158
|
-
class
|
172
|
+
class ServerComponent
|
173
|
+
#: IO | StringIO
|
174
|
+
attr_reader :stdout
|
175
|
+
|
176
|
+
#: IO | StringIO
|
177
|
+
attr_reader :stderr
|
178
|
+
|
179
|
+
#: Hash[Symbol | String, untyped]
|
180
|
+
attr_reader :capabilities
|
181
|
+
|
182
|
+
#: (IO | StringIO, IO | StringIO, Hash[Symbol | String, untyped]) -> void
|
183
|
+
def initialize(stdout, stderr, capabilities)
|
184
|
+
@stdout = stdout
|
185
|
+
@stderr = stderr
|
186
|
+
@capabilities = capabilities
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
class ServerAddon < ServerComponent
|
159
191
|
include Common
|
160
192
|
|
161
|
-
@server_addon_classes = []
|
162
|
-
@server_addons = {}
|
193
|
+
@server_addon_classes = [] #: Array[singleton(ServerAddon)]
|
194
|
+
@server_addons = {} #: Hash[String, ServerAddon]
|
163
195
|
|
164
196
|
class << self
|
165
197
|
# We keep track of runtime server add-ons the same way we track other add-ons, by storing classes that inherit
|
166
198
|
# from the base one
|
199
|
+
#: (singleton(ServerAddon)) -> void
|
167
200
|
def inherited(child)
|
168
201
|
@server_addon_classes << child
|
169
202
|
super
|
170
203
|
end
|
171
204
|
|
172
205
|
# Delegate `request` with `params` to the server add-on with the given `name`
|
206
|
+
#: (String, String, Hash[Symbol | String, untyped]) -> void
|
173
207
|
def delegate(name, request, params)
|
174
208
|
@server_addons[name]&.execute(request, params)
|
175
209
|
end
|
176
210
|
|
177
211
|
# Instantiate all server addons and store them in a hash for easy access after we have discovered the classes
|
212
|
+
#: (IO | StringIO, IO | StringIO, Hash[Symbol | String, untyped]) -> void
|
178
213
|
def finalize_registrations!(stdout, stderr, capabilities)
|
179
214
|
until @server_addon_classes.empty?
|
180
|
-
addon = @server_addon_classes.shift
|
215
|
+
addon = @server_addon_classes.shift #: as !nil
|
216
|
+
.new(stdout, stderr, capabilities)
|
181
217
|
@server_addons[addon.name] = addon
|
182
218
|
end
|
183
219
|
end
|
184
220
|
end
|
185
221
|
|
186
|
-
|
187
|
-
@stdout = stdout
|
188
|
-
@stderr = stderr
|
189
|
-
@capabilities = capabilities
|
190
|
-
end
|
191
|
-
|
222
|
+
#: -> String
|
192
223
|
def name
|
193
224
|
raise NotImplementedError, "Not implemented!"
|
194
225
|
end
|
195
226
|
|
227
|
+
#: (String, Hash[String | Symbol, untyped]) -> untyped
|
196
228
|
def execute(request, params)
|
197
229
|
raise NotImplementedError, "Not implemented!"
|
198
230
|
end
|
199
231
|
end
|
200
232
|
|
201
233
|
class IOWrapper < SimpleDelegator
|
234
|
+
#: (untyped) -> void
|
202
235
|
def puts(*args)
|
203
236
|
args.each { |arg| log("#{arg}\n") }
|
204
237
|
end
|
205
238
|
|
239
|
+
#: (untyped) -> void
|
206
240
|
def print(*args)
|
207
241
|
args.each { |arg| log(arg.to_s) }
|
208
242
|
end
|
209
243
|
|
210
244
|
private
|
211
245
|
|
246
|
+
#: (untyped) -> void
|
212
247
|
def log(message)
|
213
248
|
json_message = { method: "window/logMessage", params: { type: 4, message: message } }.to_json
|
214
|
-
|
249
|
+
|
250
|
+
self #: as untyped # rubocop:disable Style/RedundantSelf
|
251
|
+
.write("Content-Length: #{json_message.bytesize}\r\n\r\n#{json_message}")
|
215
252
|
end
|
216
253
|
end
|
217
254
|
|
218
|
-
class Server
|
255
|
+
class Server < ServerComponent
|
219
256
|
include Common
|
220
257
|
|
258
|
+
#: (IO | StringIO, IO | StringIO, bool, Hash[Symbol | String, untyped]) -> void
|
221
259
|
def initialize(stdout: $stdout, stderr: $stderr, override_default_output_device: true, capabilities: {})
|
222
260
|
# Grab references to the original pipes so that we can change the default output device further down
|
223
|
-
|
224
|
-
@
|
225
|
-
|
261
|
+
|
262
|
+
@stdin = $stdin #: IO
|
263
|
+
super(stdout, stderr, capabilities)
|
264
|
+
|
226
265
|
@stdin.sync = true
|
227
266
|
@stdout.sync = true
|
228
267
|
@stderr.sync = true
|
@@ -230,31 +269,31 @@ module RubyLsp
|
|
230
269
|
@stdout.binmode
|
231
270
|
@stderr.binmode
|
232
271
|
|
233
|
-
# A hash containing the capabilities of the editor that may be relevant for the runtime server
|
234
|
-
@capabilities = capabilities
|
235
|
-
|
236
272
|
# # Set the default output device to be $stderr. This means that using `puts` by itself will default to printing
|
237
273
|
# # to $stderr and only explicit `$stdout.puts` will go to $stdout. This reduces the chance that output coming
|
238
274
|
# # from the Rails app will be accidentally sent to the client
|
239
275
|
$> = IOWrapper.new(@stderr) if override_default_output_device
|
240
276
|
|
241
|
-
@running = true
|
277
|
+
@running = true #: bool
|
278
|
+
@database_supports_indexing = nil #: bool?
|
242
279
|
end
|
243
280
|
|
281
|
+
#: -> void
|
244
282
|
def start
|
245
283
|
load_routes
|
246
284
|
clear_file_system_resolver_hooks
|
247
285
|
send_result({ message: "ok", root: ::Rails.root.to_s })
|
248
286
|
|
249
287
|
while @running
|
250
|
-
headers = @stdin.gets("\r\n\r\n")
|
251
|
-
json = @stdin.read(headers[/Content-Length: (\d+)/i, 1].to_i)
|
288
|
+
headers = @stdin.gets("\r\n\r\n") #: as String
|
289
|
+
json = @stdin.read(headers[/Content-Length: (\d+)/i, 1].to_i) #: as String
|
252
290
|
|
253
291
|
request = JSON.parse(json, symbolize_names: true)
|
254
292
|
execute(request.fetch(:method), request[:params])
|
255
293
|
end
|
256
294
|
end
|
257
295
|
|
296
|
+
#: (String, Hash[Symbol | String, untyped]) -> void
|
258
297
|
def execute(request, params)
|
259
298
|
case request
|
260
299
|
when "shutdown"
|
@@ -306,6 +345,7 @@ module RubyLsp
|
|
306
345
|
|
307
346
|
private
|
308
347
|
|
348
|
+
#: (Hash[Symbol | String, untyped]) -> Hash[Symbol | String, untyped]?
|
309
349
|
def resolve_route_info(requirements)
|
310
350
|
if requirements[:controller]
|
311
351
|
requirements[:controller] = requirements.fetch(:controller).underscore.delete_suffix("_controller")
|
@@ -334,6 +374,7 @@ module RubyLsp
|
|
334
374
|
# We also check that it's enabled.
|
335
375
|
if ActionDispatch::Routing::Mapper.respond_to?(:route_source_locations) &&
|
336
376
|
ActionDispatch::Routing::Mapper.route_source_locations
|
377
|
+
#: (String) -> Hash[Symbol | String, untyped]?
|
337
378
|
def route_location(name)
|
338
379
|
# In Rails 8, Rails.application.routes.named_routes is not populated by default
|
339
380
|
if ::Rails.application.respond_to?(:reload_routes_unless_loaded)
|
@@ -352,11 +393,13 @@ module RubyLsp
|
|
352
393
|
{ location: ::Rails.root.join(route.source_location).to_s }
|
353
394
|
end
|
354
395
|
else
|
396
|
+
#: (String) -> Hash[Symbol | String, untyped]?
|
355
397
|
def route_location(name)
|
356
398
|
nil
|
357
399
|
end
|
358
400
|
end
|
359
401
|
|
402
|
+
#: (String) -> Hash[Symbol | String, untyped]?
|
360
403
|
def resolve_database_info_from_model(model_name)
|
361
404
|
const = ActiveSupport::Inflector.safe_constantize(model_name)
|
362
405
|
return unless active_record_model?(const)
|
@@ -375,28 +418,33 @@ module RubyLsp
|
|
375
418
|
info
|
376
419
|
end
|
377
420
|
|
421
|
+
#: (Hash[Symbol | String, untyped]) -> Hash[Symbol | String, untyped]?
|
378
422
|
def resolve_association_target(params)
|
379
423
|
const = ActiveSupport::Inflector.safe_constantize(params[:model_name])
|
380
424
|
return unless active_record_model?(const)
|
381
425
|
|
382
426
|
association_klass = const.reflect_on_association(params[:association_name].intern).klass
|
383
427
|
source_location = Object.const_source_location(association_klass.to_s)
|
428
|
+
return unless source_location
|
384
429
|
|
385
|
-
{ location: source_location
|
430
|
+
{ location: "#{source_location[0]}:#{source_location[1]}" }
|
386
431
|
rescue NameError
|
387
432
|
nil
|
388
433
|
end
|
389
434
|
|
435
|
+
#: (Module?) -> bool
|
390
436
|
def active_record_model?(const)
|
391
437
|
!!(
|
392
438
|
const &&
|
393
439
|
defined?(ActiveRecord) &&
|
394
440
|
const.is_a?(Class) &&
|
395
441
|
ActiveRecord::Base > const && # We do this 'backwards' in case the class overwrites `<`
|
396
|
-
!const
|
442
|
+
!const #: as singleton(ActiveRecord::Base)
|
443
|
+
.abstract_class?
|
397
444
|
)
|
398
445
|
end
|
399
446
|
|
447
|
+
#: -> String?
|
400
448
|
def pending_migrations_message
|
401
449
|
# `check_all_pending!` is only available since Rails 7.1
|
402
450
|
return unless defined?(ActiveRecord) && ActiveRecord::Migration.respond_to?(:check_all_pending!)
|
@@ -407,6 +455,7 @@ module RubyLsp
|
|
407
455
|
e.message
|
408
456
|
end
|
409
457
|
|
458
|
+
#: -> Hash[Symbol | String, untyped]
|
410
459
|
def run_migrations
|
411
460
|
# Running migrations invokes `load` which will repeatedly load the same files. It's not designed to be invoked
|
412
461
|
# multiple times within the same process. To avoid any memory bloat, we run migrations in a separate process
|
@@ -418,6 +467,7 @@ module RubyLsp
|
|
418
467
|
{ message: stdout, status: status.exitstatus }
|
419
468
|
end
|
420
469
|
|
470
|
+
#: -> void
|
421
471
|
def load_routes
|
422
472
|
with_notification_error_handling("initial_load_routes") do
|
423
473
|
# Load routes if they haven't been loaded yet (see https://github.com/rails/rails/pull/51614).
|
@@ -430,6 +480,7 @@ module RubyLsp
|
|
430
480
|
# watches files. Since the Rails application is already booted by the time we reach this script, we can't no-op
|
431
481
|
# the file watcher implementation. Instead, we clear the hooks to prevent the registered file watchers from being
|
432
482
|
# instantiated
|
483
|
+
#: -> void
|
433
484
|
def clear_file_system_resolver_hooks
|
434
485
|
return unless defined?(::ActionView::PathRegistry)
|
435
486
|
|
@@ -438,6 +489,7 @@ module RubyLsp
|
|
438
489
|
end
|
439
490
|
end
|
440
491
|
|
492
|
+
#: (singleton(ActiveRecord::Base)) -> Array[String]
|
441
493
|
def collect_model_foreign_keys(model)
|
442
494
|
return [] unless model.connection.respond_to?(:supports_foreign_keys?) &&
|
443
495
|
model.connection.supports_foreign_keys?
|
@@ -447,6 +499,7 @@ module RubyLsp
|
|
447
499
|
end
|
448
500
|
end
|
449
501
|
|
502
|
+
#: (singleton(ActiveRecord::Base)) -> Array[Hash[Symbol, untyped]]
|
450
503
|
def collect_model_indexes(model)
|
451
504
|
return [] unless database_supports_indexing?(model)
|
452
505
|
|
@@ -459,8 +512,9 @@ module RubyLsp
|
|
459
512
|
end
|
460
513
|
end
|
461
514
|
|
515
|
+
#: (singleton(ActiveRecord::Base)) -> bool
|
462
516
|
def database_supports_indexing?(model)
|
463
|
-
return @database_supports_indexing
|
517
|
+
return @database_supports_indexing unless @database_supports_indexing.nil?
|
464
518
|
|
465
519
|
model.connection.indexes(model.table_name)
|
466
520
|
@database_supports_indexing = true
|
@@ -19,7 +19,8 @@ module RubyLsp
|
|
19
19
|
parts = first_argument.parts
|
20
20
|
|
21
21
|
if parts.all? { |part| part.is_a?(Prism::StringNode) }
|
22
|
-
|
22
|
+
parts #: as Array[Prism::StringNode]
|
23
|
+
.map(&:content).join
|
23
24
|
end
|
24
25
|
when Prism::StringNode
|
25
26
|
first_argument.content
|
@@ -5,15 +5,12 @@ module RubyLsp
|
|
5
5
|
module Rails
|
6
6
|
module Support
|
7
7
|
module Associations
|
8
|
-
ALL =
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
].freeze,
|
15
|
-
T::Array[String],
|
16
|
-
)
|
8
|
+
ALL = [
|
9
|
+
"belongs_to",
|
10
|
+
"has_many",
|
11
|
+
"has_one",
|
12
|
+
"has_and_belongs_to_many",
|
13
|
+
].freeze
|
17
14
|
end
|
18
15
|
end
|
19
16
|
end
|
@@ -5,66 +5,57 @@ module RubyLsp
|
|
5
5
|
module Rails
|
6
6
|
module Support
|
7
7
|
module Callbacks
|
8
|
-
MODELS =
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
].freeze,
|
34
|
-
T::Array[String],
|
35
|
-
)
|
8
|
+
MODELS = [
|
9
|
+
"before_validation",
|
10
|
+
"after_validation",
|
11
|
+
"before_save",
|
12
|
+
"around_save",
|
13
|
+
"after_save",
|
14
|
+
"before_create",
|
15
|
+
"around_create",
|
16
|
+
"after_create",
|
17
|
+
"after_commit",
|
18
|
+
"after_create_commit",
|
19
|
+
"after_update_commit",
|
20
|
+
"after_destroy_commit",
|
21
|
+
"after_save_commit",
|
22
|
+
"after_rollback",
|
23
|
+
"before_update",
|
24
|
+
"around_update",
|
25
|
+
"after_update",
|
26
|
+
"before_destroy",
|
27
|
+
"around_destroy",
|
28
|
+
"after_destroy",
|
29
|
+
"after_initialize",
|
30
|
+
"after_find",
|
31
|
+
"after_touch",
|
32
|
+
].freeze
|
36
33
|
|
37
|
-
CONTROLLERS =
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
].freeze,
|
52
|
-
T::Array[String],
|
53
|
-
)
|
34
|
+
CONTROLLERS = [
|
35
|
+
"after_action",
|
36
|
+
"append_after_action",
|
37
|
+
"append_around_action",
|
38
|
+
"append_before_action",
|
39
|
+
"around_action",
|
40
|
+
"before_action",
|
41
|
+
"prepend_after_action",
|
42
|
+
"prepend_around_action",
|
43
|
+
"prepend_before_action",
|
44
|
+
"skip_after_action",
|
45
|
+
"skip_around_action",
|
46
|
+
"skip_before_action",
|
47
|
+
].freeze
|
54
48
|
|
55
|
-
JOBS =
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
].freeze,
|
64
|
-
T::Array[String],
|
65
|
-
)
|
49
|
+
JOBS = [
|
50
|
+
"after_enqueue",
|
51
|
+
"after_perform",
|
52
|
+
"around_enqueue",
|
53
|
+
"around_perform",
|
54
|
+
"before_enqueue",
|
55
|
+
"before_perform",
|
56
|
+
].freeze
|
66
57
|
|
67
|
-
ALL =
|
58
|
+
ALL = (MODELS + CONTROLLERS + JOBS).freeze #: Array[String]
|
68
59
|
end
|
69
60
|
end
|
70
61
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-lsp-rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shopify
|
@@ -15,7 +15,7 @@ dependencies:
|
|
15
15
|
requirements:
|
16
16
|
- - ">="
|
17
17
|
- !ruby/object:Gem::Version
|
18
|
-
version: 0.23.
|
18
|
+
version: 0.23.16
|
19
19
|
- - "<"
|
20
20
|
- !ruby/object:Gem::Version
|
21
21
|
version: 0.24.0
|
@@ -25,7 +25,7 @@ dependencies:
|
|
25
25
|
requirements:
|
26
26
|
- - ">="
|
27
27
|
- !ruby/object:Gem::Version
|
28
|
-
version: 0.23.
|
28
|
+
version: 0.23.16
|
29
29
|
- - "<"
|
30
30
|
- !ruby/object:Gem::Version
|
31
31
|
version: 0.24.0
|