ruby-lsp-rails 0.3.26 → 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: df7c6791cb1fc007fc4705e034eb60cde1d5905a1771b145c2d4bb6f36224658
4
- data.tar.gz: 1fdd80b7787bf31a237aa9f9f196c3170ee5e612ce8105c2fc20a63fd9027f73
3
+ metadata.gz: 2df24f43b3e6666c310cb91e43d3b4311eea8326e27aa7bb4278c9fd03de42d1
4
+ data.tar.gz: 116f736d81055adf4a287d3d76a4f9e641cf2ac81f2a39b63ccb9603bfbd351e
5
5
  SHA512:
6
- metadata.gz: 8de8df639f440b7bcedb854e03ead56eb56517569dc31938759593cce32ff87d968b07b88598de01939fab8d3e1b8bb73108cf21b85c7f30493093d1f8d2973a
7
- data.tar.gz: 69550bcd5575bf7d8f6f82e7e738e53392fd776534dc88495274ab500e9ea3e11472e1008d05fe27de3ae40beee9a43d039be49866902e569ec7fdd0c91af161
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
 
@@ -19,7 +19,43 @@ module RubyLsp
19
19
  # Log a message to the editor's output panel
20
20
  def log_message(message)
21
21
  $stderr.puts(message)
22
- send_message({ result: nil })
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)}")
23
59
  end
24
60
  end
25
61
 
@@ -88,11 +124,8 @@ module RubyLsp
88
124
  end
89
125
 
90
126
  def start
91
- # Load routes if they haven't been loaded yet (see https://github.com/rails/rails/pull/51614).
92
- routes_reloader = ::Rails.application.routes_reloader
93
- routes_reloader.execute_unless_loaded if routes_reloader&.respond_to?(:execute_unless_loaded)
94
-
95
- send_message({ result: { message: "ok", root: ::Rails.root.to_s } })
127
+ load_routes
128
+ send_result({ message: "ok", root: ::Rails.root.to_s })
96
129
 
97
130
  while @running
98
131
  headers = @stdin.gets("\r\n\r\n")
@@ -104,41 +137,50 @@ module RubyLsp
104
137
  end
105
138
 
106
139
  def execute(request, params)
107
- request_name = request
108
- request_name = "#{params[:server_addon_name]}##{params[:request_name]}" if request == "server_addon/delegate"
109
-
110
140
  case request
111
141
  when "shutdown"
112
142
  @running = false
113
143
  when "model"
114
- 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
115
147
  when "association_target_location"
116
- send_message(resolve_association_target(params))
148
+ with_request_error_handling(request) do
149
+ send_result(resolve_association_target(params))
150
+ end
117
151
  when "pending_migrations_message"
118
- 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
119
155
  when "run_migrations"
120
- send_message({ result: run_migrations })
156
+ with_request_error_handling(request) do
157
+ send_result(run_migrations)
158
+ end
121
159
  when "reload"
122
- ::Rails.application.reloader.reload!
160
+ with_notification_error_handling(request) do
161
+ ::Rails.application.reloader.reload!
162
+ end
123
163
  when "route_location"
124
- send_message(route_location(params.fetch(:name)))
164
+ with_request_error_handling(request) do
165
+ send_result(route_location(params.fetch(:name)))
166
+ end
125
167
  when "route_info"
126
- send_message(resolve_route_info(params))
168
+ with_request_error_handling(request) do
169
+ send_result(resolve_route_info(params))
170
+ end
127
171
  when "server_addon/register"
128
- require params[:server_addon_path]
129
- ServerAddon.finalize_registrations!(@stdout)
172
+ with_notification_error_handling(request) do
173
+ require params[:server_addon_path]
174
+ ServerAddon.finalize_registrations!(@stdout)
175
+ end
130
176
  when "server_addon/delegate"
131
177
  server_addon_name = params[:server_addon_name]
132
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.
133
182
  ServerAddon.delegate(server_addon_name, request_name, params.except(:request_name, :server_addon_name))
134
183
  end
135
- # Since this is a common problem, we show a specific error message to the user, instead of the full stack trace.
136
- rescue ActiveRecord::ConnectionNotEstablished
137
- log_message("Request #{request_name} failed because database connection was not established.")
138
- rescue ActiveRecord::NoDatabaseError
139
- log_message("Request #{request_name} failed because the database does not exist.")
140
- rescue => e
141
- log_message("Request #{request_name} failed:\n" + e.full_message(highlight: false))
142
184
  end
143
185
 
144
186
  private
@@ -156,19 +198,15 @@ module RubyLsp
156
198
  end
157
199
 
158
200
  source_location = route&.respond_to?(:source_location) && route.source_location
201
+ return unless source_location
159
202
 
160
- if source_location
161
- file, _, line = source_location.rpartition(":")
162
- body = {
163
- source_location: [::Rails.root.join(file).to_s, line],
164
- verb: route.verb,
165
- path: route.path.spec.to_s,
166
- }
203
+ file, _, line = source_location.rpartition(":")
167
204
 
168
- { result: body }
169
- else
170
- { result: nil }
171
- end
205
+ {
206
+ source_location: [::Rails.root.join(file).to_s, line],
207
+ verb: route.verb,
208
+ path: route.path.spec.to_s,
209
+ }
172
210
  end
173
211
 
174
212
  # Older versions of Rails don't support `route_source_locations`.
@@ -182,74 +220,48 @@ module RubyLsp
182
220
  end
183
221
 
184
222
  match_data = name.match(/^(.+)(_path|_url)$/)
185
- return { result: nil } unless match_data
223
+ return unless match_data
186
224
 
187
225
  key = match_data[1]
188
226
 
189
227
  # A token could match the _path or _url pattern, but not be an actual route.
190
228
  route = ::Rails.application.routes.named_routes.get(key)
191
- return { result: nil } unless route&.source_location
192
-
193
- {
194
- result: {
195
- location: ::Rails.root.join(route.source_location).to_s,
196
- },
197
- }
198
- rescue => e
199
- { error: e.full_message(highlight: false) }
229
+ return unless route&.source_location
230
+
231
+ { location: ::Rails.root.join(route.source_location).to_s }
200
232
  end
201
233
  else
202
234
  def route_location(name)
203
- { result: nil }
235
+ nil
204
236
  end
205
237
  end
206
238
 
207
239
  def resolve_database_info_from_model(model_name)
208
240
  const = ActiveSupport::Inflector.safe_constantize(model_name)
209
- unless active_record_model?(const)
210
- return {
211
- result: nil,
212
- }
213
- end
241
+ return unless active_record_model?(const)
214
242
 
215
243
  info = {
216
- result: {
217
- columns: const.columns.map { |column| [column.name, column.type, column.default, column.null] },
218
- primary_keys: Array(const.primary_key),
219
- },
244
+ columns: const.columns.map { |column| [column.name, column.type, column.default, column.null] },
245
+ primary_keys: Array(const.primary_key),
220
246
  }
221
247
 
222
248
  if ActiveRecord::Tasks::DatabaseTasks.respond_to?(:schema_dump_path)
223
- info[:result][:schema_file] =
224
- ActiveRecord::Tasks::DatabaseTasks.schema_dump_path(const.connection.pool.db_config)
225
-
249
+ info[:schema_file] = ActiveRecord::Tasks::DatabaseTasks.schema_dump_path(const.connection.pool.db_config)
226
250
  end
251
+
227
252
  info
228
- rescue => e
229
- { error: e.full_message(highlight: false) }
230
253
  end
231
254
 
232
255
  def resolve_association_target(params)
233
256
  const = ActiveSupport::Inflector.safe_constantize(params[:model_name])
234
- unless active_record_model?(const)
235
- return {
236
- result: nil,
237
- }
238
- end
257
+ return unless active_record_model?(const)
239
258
 
240
259
  association_klass = const.reflect_on_association(params[:association_name].intern).klass
241
-
242
260
  source_location = Object.const_source_location(association_klass.to_s)
243
261
 
244
- {
245
- result: {
246
- location: source_location.first + ":" + source_location.second.to_s,
247
- },
248
- }
262
+ { location: source_location.first + ":" + source_location.second.to_s }
249
263
  rescue NameError
250
- {
251
- result: nil,
252
- }
264
+ nil
253
265
  end
254
266
 
255
267
  def active_record_model?(const)
@@ -282,6 +294,14 @@ module RubyLsp
282
294
 
283
295
  { message: stdout, status: status.exitstatus }
284
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
285
305
  end
286
306
  end
287
307
  end
@@ -3,6 +3,6 @@
3
3
 
4
4
  module RubyLsp
5
5
  module Rails
6
- VERSION = "0.3.26"
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.26
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-08 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