ruby-lsp-rails 0.3.25 → 0.3.27

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: 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