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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ea71850cee42d73b31e37c68aa3438a7d59c4c610fef1e2e20a157ff7c7bd92d
4
- data.tar.gz: 5d4506c255005cc442fbe95fc73bfe58220a70155313b4451b8a3e549feef9de
3
+ metadata.gz: 76155fcaf046652d4e13655bf8f1d38764a0a755cd4e090a70a7e551e77dc598
4
+ data.tar.gz: d417e7a57d97dbd0a7b1b2aa2ec999793ef6a8fb3ada39a0c1ad06688af8fd3b
5
5
  SHA512:
6
- metadata.gz: e8538d6e4330168cc5d480ca157cb0c57e178ff3b24429f1a456986e3f91f471a830b3d6ab04b943dd5d18f4d9116ff25ccf94da91db28e8c9c4ad850a488532
7
- data.tar.gz: d527c41fcffc45935bab4a5b6332d940c07fdcbaaec189a223bbba5db46459d2f5fb033a3a4809f7ffc74aa1bf1cdcb0ed228cc6f244db4b25ff2ea57aa57421
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"] - ["test/ruby_lsp_rails/server_test.rb"]
16
+ t.test_files = FileList["test/**/*_test.rb"]
20
17
  end
21
18
 
22
- Rake::TestTask.new(:server_test) do |t|
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 = T.let(NullClient.new, RunnerClient)
32
- @global_state = T.let(nil, T.nilable(GlobalState))
33
- @outgoing_queue = T.let(nil, T.nilable(Thread::Queue))
34
- @settings = T.let(
35
- {
36
- enablePendingMigrationsPrompt: true,
37
- },
38
- T::Hash[Symbol, T.untyped],
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(T.must(@outgoing_queue), T.must(@global_state))
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 = T.let(uri.to_standardized_path, T.nilable(String))
84
- @group_id = T.let(1, Integer)
85
- @group_id_stack = T.let([], T::Array[Integer])
86
- @constant_name_stack = T.let([], T::Array[[String, T.nilable(String)]])
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, _ = T.must(@constant_name_stack.last)
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(T.must(@path)).split("_").first
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 = T.let({}, T::Hash[String, Prism::Node])
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 = T.let(node_context.nesting, T::Array[String])
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(T.must(node.message))
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 = T.let([], T::Array[String])
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, T.must(message))
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, T.must(message))
55
+ handle_symbol_and_string_arg_types(node, message)
54
56
  when "validates_with"
55
- handle_class_arg_types(node, T.must(message))
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(T.must(argument.value_loc)),
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(T.must(argument.value_loc)),
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 = T.let(node_context.nesting, T::Array[String])
25
- @index = T.let(global_state.index, RubyIndexer::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 = T.must(queue.shift)
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.content
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 = T.let(outgoing_queue, Thread::Queue)
53
- @mutex = T.let(Mutex.new, 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 = T.let(stdin, IO)
82
- @stdout = T.let(stdout, IO)
83
- @stderr = T.let(stderr, IO)
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 = T.let(wait_thread, Process::Waiter)
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 = T.must(read_response)
95
- @rails_root = T.let(initialize_response[:root], String)
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 = T.let(
110
- Thread.new do
111
- until @stderr.closed?
112
- notification = read_notification
113
-
114
- unless @outgoing_queue.closed? || !notification
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
- rescue IOError
119
- # The server was shutdown and stderr is already closed
120
- end,
121
- Thread,
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(T.must(raw_response), symbolize_names: true)
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(T.must(Signal.list["KILL"]), @wait_thread.pid)
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: false
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 @capabilities[:supports_progress]
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 @capabilities[:supports_progress]
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 @capabilities[:supports_progress]
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(@stderr, id, @capabilities[:supports_progress])
136
- return block.call(progress_block) unless @capabilities[:supports_progress]
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
- @stdout.write("Content-Length: #{json_message.bytesize}\r\n\r\n#{json_message}")
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
- @stderr.write("Content-Length: #{json_message.bytesize}\r\n\r\n#{json_message}")
168
+ stderr.write("Content-Length: #{json_message.bytesize}\r\n\r\n#{json_message}")
155
169
  end
156
170
  end
157
171
 
158
- class ServerAddon
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.new(stdout, stderr, capabilities)
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
- def initialize(stdout, stderr, capabilities)
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
- write("Content-Length: #{json_message.bytesize}\r\n\r\n#{json_message}")
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
- @stdin = $stdin
224
- @stdout = stdout
225
- @stderr = stderr
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.first + ":" + source_location.second.to_s }
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.abstract_class?
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 if instance_variable_defined?(:@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
- T.cast(parts, T::Array[Prism::StringNode]).map(&:content).join
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 = T.let(
9
- [
10
- "belongs_to",
11
- "has_many",
12
- "has_one",
13
- "has_and_belongs_to_many",
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 = T.let(
9
- [
10
- "before_validation",
11
- "after_validation",
12
- "before_save",
13
- "around_save",
14
- "after_save",
15
- "before_create",
16
- "around_create",
17
- "after_create",
18
- "after_commit",
19
- "after_create_commit",
20
- "after_update_commit",
21
- "after_destroy_commit",
22
- "after_save_commit",
23
- "after_rollback",
24
- "before_update",
25
- "around_update",
26
- "after_update",
27
- "before_destroy",
28
- "around_destroy",
29
- "after_destroy",
30
- "after_initialize",
31
- "after_find",
32
- "after_touch",
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 = T.let(
38
- [
39
- "after_action",
40
- "append_after_action",
41
- "append_around_action",
42
- "append_before_action",
43
- "around_action",
44
- "before_action",
45
- "prepend_after_action",
46
- "prepend_around_action",
47
- "prepend_before_action",
48
- "skip_after_action",
49
- "skip_around_action",
50
- "skip_before_action",
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 = T.let(
56
- [
57
- "after_enqueue",
58
- "after_perform",
59
- "around_enqueue",
60
- "around_perform",
61
- "before_enqueue",
62
- "before_perform",
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 = T.let((MODELS + CONTROLLERS + JOBS).freeze, T::Array[String])
58
+ ALL = (MODELS + CONTROLLERS + JOBS).freeze #: Array[String]
68
59
  end
69
60
  end
70
61
  end
@@ -3,6 +3,6 @@
3
3
 
4
4
  module RubyLsp
5
5
  module Rails
6
- VERSION = "0.4.1"
6
+ VERSION = "0.4.2"
7
7
  end
8
8
  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.1
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.14
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.14
28
+ version: 0.23.16
29
29
  - - "<"
30
30
  - !ruby/object:Gem::Version
31
31
  version: 0.24.0