ruby-lsp-rails 0.3.25 → 0.3.27

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 03b28222188faf1ac819337e73c244ba0a3cb588acfb286554246151b4beaa1a
4
- data.tar.gz: 5558fe8142c90eaa53991f99a54da04b1cf1fe2ded213ae737b55cc9ad8acd78
3
+ metadata.gz: 2df24f43b3e6666c310cb91e43d3b4311eea8326e27aa7bb4278c9fd03de42d1
4
+ data.tar.gz: 116f736d81055adf4a287d3d76a4f9e641cf2ac81f2a39b63ccb9603bfbd351e
5
5
  SHA512:
6
- metadata.gz: fb20529701e3008b9f38ba4840446aed72aad85b9e623bc93f691a7bee1ce0418d0b2bfabef085d5446fced0f645c0f6325dd4e78925f2e3ab20cc43e1d34543
7
- data.tar.gz: e8507ef9f18e3987c667f1c45a3419171677f318bfb4ba5572153efea73bf2ec2922a0ec62865d3aabc9e49117af8e86951975a9eadc68d7848ce6d57f75fdb2
6
+ metadata.gz: c462629b903ebbee663323f52385423fb697bd338bcf7c57574874de21f323230d381c5d6e5eb30602248f33021bc2d0082602fa3b5c0b65d674f9c9c1668ff5
7
+ data.tar.gz: e909ea43dc3a8012f1de165e1aa4cbf4ad0873134199273103e329fec5ac141792113adb5eb1eb788f065f63e3c2fba299c0501a837719a816b2698e119e50bf
@@ -56,7 +56,6 @@ module RubyLsp
56
56
  @outgoing_queue << Notification.window_log_message("Activating Ruby LSP Rails add-on v#{VERSION}")
57
57
 
58
58
  register_additional_file_watchers(global_state: global_state, outgoing_queue: outgoing_queue)
59
- @global_state.index.register_enhancement(IndexingEnhancement.new(@global_state.index))
60
59
 
61
60
  # Start booting the real client in a background thread. Until this completes, the client will be a NullClient
62
61
  @client_mutex.unlock
@@ -8,25 +8,32 @@ module RubyLsp
8
8
 
9
9
  sig do
10
10
  override.params(
11
- owner: T.nilable(RubyIndexer::Entry::Namespace),
12
- node: Prism::CallNode,
13
- file_path: String,
14
- code_units_cache: T.any(
15
- T.proc.params(arg0: Integer).returns(Integer),
16
- Prism::CodeUnitsCache,
17
- ),
11
+ call_node: Prism::CallNode,
18
12
  ).void
19
13
  end
20
- def on_call_node_enter(owner, node, file_path, code_units_cache)
14
+ def on_call_node_enter(call_node)
15
+ owner = @listener.current_owner
21
16
  return unless owner
22
17
 
23
- name = node.name
24
-
25
- case name
18
+ case call_node.name
26
19
  when :extend
27
- handle_concern_extend(owner, node)
20
+ handle_concern_extend(owner, call_node)
28
21
  when :has_one, :has_many, :belongs_to, :has_and_belongs_to_many
29
- handle_association(owner, node, file_path, code_units_cache)
22
+ handle_association(owner, call_node)
23
+ # for `class_methods do` blocks within concerns
24
+ when :class_methods
25
+ handle_class_methods(owner, call_node)
26
+ end
27
+ end
28
+
29
+ sig do
30
+ override.params(
31
+ call_node: Prism::CallNode,
32
+ ).void
33
+ end
34
+ def on_call_node_leave(call_node)
35
+ if call_node.name == :class_methods && call_node.block
36
+ @listener.pop_namespace_stack
30
37
  end
31
38
  end
32
39
 
@@ -35,16 +42,11 @@ module RubyLsp
35
42
  sig do
36
43
  params(
37
44
  owner: RubyIndexer::Entry::Namespace,
38
- node: Prism::CallNode,
39
- file_path: String,
40
- code_units_cache: T.any(
41
- T.proc.params(arg0: Integer).returns(Integer),
42
- Prism::CodeUnitsCache,
43
- ),
45
+ call_node: Prism::CallNode,
44
46
  ).void
45
47
  end
46
- def handle_association(owner, node, file_path, code_units_cache)
47
- arguments = node.arguments&.arguments
48
+ def handle_association(owner, call_node)
49
+ arguments = call_node.arguments&.arguments
48
50
  return unless arguments
49
51
 
50
52
  name_arg = arguments.first
@@ -58,41 +60,22 @@ module RubyLsp
58
60
 
59
61
  return unless name
60
62
 
61
- loc = RubyIndexer::Location.from_prism_location(name_arg.location, code_units_cache)
63
+ loc = name_arg.location
62
64
 
63
65
  # Reader
64
- @index.add(RubyIndexer::Entry::Method.new(
65
- name,
66
- file_path,
67
- loc,
68
- loc,
69
- nil,
70
- [RubyIndexer::Entry::Signature.new([])],
71
- RubyIndexer::Entry::Visibility::PUBLIC,
72
- owner,
73
- ))
66
+ reader_signatures = [RubyIndexer::Entry::Signature.new([])]
67
+ @listener.add_method(name, loc, reader_signatures)
74
68
 
75
69
  # Writer
76
- @index.add(RubyIndexer::Entry::Method.new(
77
- "#{name}=",
78
- file_path,
79
- loc,
80
- loc,
81
- nil,
82
- [RubyIndexer::Entry::Signature.new([RubyIndexer::Entry::RequiredParameter.new(name: name.to_sym)])],
83
- RubyIndexer::Entry::Visibility::PUBLIC,
84
- owner,
85
- ))
70
+ writer_signatures = [
71
+ RubyIndexer::Entry::Signature.new([RubyIndexer::Entry::RequiredParameter.new(name: name.to_sym)]),
72
+ ]
73
+ @listener.add_method("#{name}=", loc, writer_signatures)
86
74
  end
87
75
 
88
- sig do
89
- params(
90
- owner: RubyIndexer::Entry::Namespace,
91
- node: Prism::CallNode,
92
- ).void
93
- end
94
- def handle_concern_extend(owner, node)
95
- arguments = node.arguments&.arguments
76
+ sig { params(owner: RubyIndexer::Entry::Namespace, call_node: Prism::CallNode).void }
77
+ def handle_concern_extend(owner, call_node)
78
+ arguments = call_node.arguments&.arguments
96
79
  return unless arguments
97
80
 
98
81
  arguments.each do |node|
@@ -101,7 +84,7 @@ module RubyLsp
101
84
  module_name = node.full_name
102
85
  next unless module_name == "ActiveSupport::Concern"
103
86
 
104
- @index.register_included_hook(owner.name) do |index, base|
87
+ @listener.register_included_hook do |index, base|
105
88
  class_methods_name = "#{owner.name}::ClassMethods"
106
89
 
107
90
  if index.indexed?(class_methods_name)
@@ -114,6 +97,13 @@ module RubyLsp
114
97
  # Do nothing
115
98
  end
116
99
  end
100
+
101
+ sig { params(owner: RubyIndexer::Entry::Namespace, call_node: Prism::CallNode).void }
102
+ def handle_class_methods(owner, call_node)
103
+ return unless call_node.block
104
+
105
+ @listener.add_module("ClassMethods", call_node.location, call_node.location)
106
+ end
117
107
  end
118
108
  end
119
109
  end
@@ -27,7 +27,7 @@ module RubyLsp
27
27
 
28
28
  NullClient.new
29
29
  end
30
- rescue Errno::ENOENT, StandardError => e # rubocop:disable Lint/ShadowedException
30
+ rescue StandardError => e
31
31
  unless outgoing_queue.closed?
32
32
  outgoing_queue << RubyLsp::Notification.window_log_message(
33
33
  <<~MESSAGE.chomp,
@@ -44,7 +44,6 @@ module RubyLsp
44
44
 
45
45
  class InitializationError < StandardError; end
46
46
  class MessageError < StandardError; end
47
- class IncompleteMessageError < MessageError; end
48
47
  class EmptyMessageError < MessageError; end
49
48
 
50
49
  extend T::Sig
@@ -109,7 +108,7 @@ module RubyLsp
109
108
  end,
110
109
  Thread,
111
110
  )
112
- rescue Errno::EPIPE, IncompleteMessageError
111
+ rescue StandardError
113
112
  raise InitializationError, @stderr.read
114
113
  end
115
114
 
@@ -20,6 +20,43 @@ module RubyLsp
20
20
  def log_message(message)
21
21
  $stderr.puts(message)
22
22
  end
23
+
24
+ # Sends an error result to a request, if the request failed. DO NOT INVOKE THIS METHOD FOR NOTIFICATIONS! Use
25
+ # `log_message` instead, otherwise the client/server communication will go out of sync
26
+ def send_error_response(message)
27
+ send_message({ error: message })
28
+ end
29
+
30
+ # Sends a result back to the client
31
+ def send_result(result)
32
+ send_message({ result: result })
33
+ end
34
+
35
+ # Handle possible errors for a request. This should only be used for requests, which means messages that return a
36
+ # response back to the client. Errors are returned as an error object back to the client
37
+ def with_request_error_handling(request_name, &block)
38
+ block.call
39
+ rescue ActiveRecord::ConnectionNotEstablished
40
+ # Since this is a common problem, we show a specific error message to the user, instead of the full stack trace.
41
+ send_error_response("Request #{request_name} failed because database connection was not established.")
42
+ rescue ActiveRecord::NoDatabaseError
43
+ send_error_response("Request #{request_name} failed because the database does not exist.")
44
+ rescue => e
45
+ send_error_response("Request #{request_name} failed:\n#{e.full_message(highlight: false)}")
46
+ end
47
+
48
+ # Handle possible errors for a notification. This should only be used for notifications, which means messages that
49
+ # do not return a response back to the client. Errors are logged to the editor's output panel
50
+ def with_notification_error_handling(notification_name, &block)
51
+ block.call
52
+ rescue ActiveRecord::ConnectionNotEstablished
53
+ # Since this is a common problem, we show a specific error message to the user, instead of the full stack trace.
54
+ log_message("Request #{notification_name} failed because database connection was not established.")
55
+ rescue ActiveRecord::NoDatabaseError
56
+ log_message("Request #{notification_name} failed because the database does not exist.")
57
+ rescue => e
58
+ log_message("Request #{notification_name} failed:\n#{e.full_message(highlight: false)}")
59
+ end
23
60
  end
24
61
 
25
62
  class ServerAddon
@@ -87,11 +124,8 @@ module RubyLsp
87
124
  end
88
125
 
89
126
  def start
90
- # Load routes if they haven't been loaded yet (see https://github.com/rails/rails/pull/51614).
91
- routes_reloader = ::Rails.application.routes_reloader
92
- routes_reloader.execute_unless_loaded if routes_reloader&.respond_to?(:execute_unless_loaded)
93
-
94
- send_message({ result: { message: "ok", root: ::Rails.root.to_s } })
127
+ load_routes
128
+ send_result({ message: "ok", root: ::Rails.root.to_s })
95
129
 
96
130
  while @running
97
131
  headers = @stdin.gets("\r\n\r\n")
@@ -107,34 +141,46 @@ module RubyLsp
107
141
  when "shutdown"
108
142
  @running = false
109
143
  when "model"
110
- send_message(resolve_database_info_from_model(params.fetch(:name)))
144
+ with_request_error_handling(request) do
145
+ send_result(resolve_database_info_from_model(params.fetch(:name)))
146
+ end
111
147
  when "association_target_location"
112
- send_message(resolve_association_target(params))
148
+ with_request_error_handling(request) do
149
+ send_result(resolve_association_target(params))
150
+ end
113
151
  when "pending_migrations_message"
114
- send_message({ result: { pending_migrations_message: pending_migrations_message } })
152
+ with_request_error_handling(request) do
153
+ send_result({ pending_migrations_message: pending_migrations_message })
154
+ end
115
155
  when "run_migrations"
116
- send_message({ result: run_migrations })
156
+ with_request_error_handling(request) do
157
+ send_result(run_migrations)
158
+ end
117
159
  when "reload"
118
- ::Rails.application.reloader.reload!
160
+ with_notification_error_handling(request) do
161
+ ::Rails.application.reloader.reload!
162
+ end
119
163
  when "route_location"
120
- send_message(route_location(params.fetch(:name)))
164
+ with_request_error_handling(request) do
165
+ send_result(route_location(params.fetch(:name)))
166
+ end
121
167
  when "route_info"
122
- send_message(resolve_route_info(params))
168
+ with_request_error_handling(request) do
169
+ send_result(resolve_route_info(params))
170
+ end
123
171
  when "server_addon/register"
124
- require params[:server_addon_path]
125
- ServerAddon.finalize_registrations!(@stdout)
172
+ with_notification_error_handling(request) do
173
+ require params[:server_addon_path]
174
+ ServerAddon.finalize_registrations!(@stdout)
175
+ end
126
176
  when "server_addon/delegate"
127
177
  server_addon_name = params[:server_addon_name]
128
178
  request_name = params[:request_name]
179
+
180
+ # Do not wrap this in error handlers. Server add-ons need to have the flexibility to choose if they want to
181
+ # include a response or not as part of error handling, so a blanket approach is not appropriate.
129
182
  ServerAddon.delegate(server_addon_name, request_name, params.except(:request_name, :server_addon_name))
130
183
  end
131
- request_name = request
132
- request_name = "#{params[:server_addon_name]}##{params[:request_name]}" if request == "server_addon/delegate"
133
- # Since this is a common problem, we show a specific error message to the user, instead of the full stack trace.
134
- rescue ActiveRecord::ConnectionNotEstablished
135
- log_message("Request #{request_name} failed because database connection was not established.")
136
- rescue => e
137
- log_message("Request #{request_name} failed:\n" + e.full_message(highlight: false))
138
184
  end
139
185
 
140
186
  private
@@ -152,19 +198,15 @@ module RubyLsp
152
198
  end
153
199
 
154
200
  source_location = route&.respond_to?(:source_location) && route.source_location
201
+ return unless source_location
155
202
 
156
- if source_location
157
- file, _, line = source_location.rpartition(":")
158
- body = {
159
- source_location: [::Rails.root.join(file).to_s, line],
160
- verb: route.verb,
161
- path: route.path.spec.to_s,
162
- }
203
+ file, _, line = source_location.rpartition(":")
163
204
 
164
- { result: body }
165
- else
166
- { result: nil }
167
- end
205
+ {
206
+ source_location: [::Rails.root.join(file).to_s, line],
207
+ verb: route.verb,
208
+ path: route.path.spec.to_s,
209
+ }
168
210
  end
169
211
 
170
212
  # Older versions of Rails don't support `route_source_locations`.
@@ -178,74 +220,48 @@ module RubyLsp
178
220
  end
179
221
 
180
222
  match_data = name.match(/^(.+)(_path|_url)$/)
181
- return { result: nil } unless match_data
223
+ return unless match_data
182
224
 
183
225
  key = match_data[1]
184
226
 
185
227
  # A token could match the _path or _url pattern, but not be an actual route.
186
228
  route = ::Rails.application.routes.named_routes.get(key)
187
- return { result: nil } unless route&.source_location
188
-
189
- {
190
- result: {
191
- location: ::Rails.root.join(route.source_location).to_s,
192
- },
193
- }
194
- rescue => e
195
- { error: e.full_message(highlight: false) }
229
+ return unless route&.source_location
230
+
231
+ { location: ::Rails.root.join(route.source_location).to_s }
196
232
  end
197
233
  else
198
234
  def route_location(name)
199
- { result: nil }
235
+ nil
200
236
  end
201
237
  end
202
238
 
203
239
  def resolve_database_info_from_model(model_name)
204
240
  const = ActiveSupport::Inflector.safe_constantize(model_name)
205
- unless active_record_model?(const)
206
- return {
207
- result: nil,
208
- }
209
- end
241
+ return unless active_record_model?(const)
210
242
 
211
243
  info = {
212
- result: {
213
- columns: const.columns.map { |column| [column.name, column.type, column.default, column.null] },
214
- primary_keys: Array(const.primary_key),
215
- },
244
+ columns: const.columns.map { |column| [column.name, column.type, column.default, column.null] },
245
+ primary_keys: Array(const.primary_key),
216
246
  }
217
247
 
218
248
  if ActiveRecord::Tasks::DatabaseTasks.respond_to?(:schema_dump_path)
219
- info[:result][:schema_file] =
220
- ActiveRecord::Tasks::DatabaseTasks.schema_dump_path(const.connection.pool.db_config)
221
-
249
+ info[:schema_file] = ActiveRecord::Tasks::DatabaseTasks.schema_dump_path(const.connection.pool.db_config)
222
250
  end
251
+
223
252
  info
224
- rescue => e
225
- { error: e.full_message(highlight: false) }
226
253
  end
227
254
 
228
255
  def resolve_association_target(params)
229
256
  const = ActiveSupport::Inflector.safe_constantize(params[:model_name])
230
- unless active_record_model?(const)
231
- return {
232
- result: nil,
233
- }
234
- end
257
+ return unless active_record_model?(const)
235
258
 
236
259
  association_klass = const.reflect_on_association(params[:association_name].intern).klass
237
-
238
260
  source_location = Object.const_source_location(association_klass.to_s)
239
261
 
240
- {
241
- result: {
242
- location: source_location.first + ":" + source_location.second.to_s,
243
- },
244
- }
262
+ { location: source_location.first + ":" + source_location.second.to_s }
245
263
  rescue NameError
246
- {
247
- result: nil,
248
- }
264
+ nil
249
265
  end
250
266
 
251
267
  def active_record_model?(const)
@@ -278,6 +294,14 @@ module RubyLsp
278
294
 
279
295
  { message: stdout, status: status.exitstatus }
280
296
  end
297
+
298
+ def load_routes
299
+ with_notification_error_handling("initial_load_routes") do
300
+ # Load routes if they haven't been loaded yet (see https://github.com/rails/rails/pull/51614).
301
+ routes_reloader = ::Rails.application.routes_reloader
302
+ routes_reloader.execute_unless_loaded if routes_reloader&.respond_to?(:execute_unless_loaded)
303
+ end
304
+ end
281
305
  end
282
306
  end
283
307
  end
@@ -3,6 +3,6 @@
3
3
 
4
4
  module RubyLsp
5
5
  module Rails
6
- VERSION = "0.3.25"
6
+ VERSION = "0.3.27"
7
7
  end
8
8
  end
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.25
4
+ version: 0.3.27
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-11-06 00:00:00.000000000 Z
11
+ date: 2024-11-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ruby-lsp
@@ -16,20 +16,20 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 0.21.2
19
+ version: 0.22.0
20
20
  - - "<"
21
21
  - !ruby/object:Gem::Version
22
- version: 0.22.0
22
+ version: 0.23.0
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
26
26
  requirements:
27
27
  - - ">="
28
28
  - !ruby/object:Gem::Version
29
- version: 0.21.2
29
+ version: 0.22.0
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
- version: 0.22.0
32
+ version: 0.23.0
33
33
  description: A Ruby LSP addon that adds extra editor functionality for Rails applications
34
34
  email:
35
35
  - ruby@shopify.com