ruby-lsp-rails 0.3.26 → 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: 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