ruby-lsp-rails 0.3.10 → 0.3.12

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: f90b7d238d437e9ad3c29cad137e14e3094340243802f3d0f5cb7ddf68e582e1
4
- data.tar.gz: cb64d09552cf7a0862ceeb8fb27c0c2b40ba596a267bec6aebdaf3ab2db139a4
3
+ metadata.gz: 9c4bfe664b0783f9ae11cc912ec6eff3cd8c2d9c5c9c51731a85d0ed7de2fbf1
4
+ data.tar.gz: 1f3f74de8ec6891464b84a8d88fd339e33ce1ddcdc770a6a34177c60d4001ebe
5
5
  SHA512:
6
- metadata.gz: 8721f1e2bb8a2fc0acff6a3caab8b216c0225b9fdd69eb883944b10d0159619d35c501b7d3761b9fb086e0662e082dedc28c759cca3481d65ea97b3dae157e23
7
- data.tar.gz: 7c329b8da4692954f7382069f8f046672675192536c79ee3c58f0239ee87b82c5aa7083b1e786dc3827a713b9e08232d7c8e92d8513bb2f89a37e97957116070
6
+ metadata.gz: 2e4493fb0f18638b208e3d9f40671846e351670936214420131ae0482fe7d6410f74afc5b808a707dd616471f83233eb201be51a25e29152817daa1ec0380e73
7
+ data.tar.gz: 9eb01978fc74c0d212a65240ff2f5874d38768abf85b647943c7eba0aee84116912a0ebe9c6206ddf84b6ee7b48f60535f1a288cd66c538ff56a40c501adc4ee
@@ -13,6 +13,7 @@ require_relative "hover"
13
13
  require_relative "code_lens"
14
14
  require_relative "document_symbol"
15
15
  require_relative "definition"
16
+ require_relative "indexing_enhancement"
16
17
 
17
18
  module RubyLsp
18
19
  module Rails
@@ -35,6 +36,8 @@ module RubyLsp
35
36
  # Start booting the real client in a background thread. Until this completes, the client will be a NullClient
36
37
  Thread.new { @client = RunnerClient.create_client }
37
38
  register_additional_file_watchers(global_state: global_state, message_queue: message_queue)
39
+
40
+ T.must(@global_state).index.register_enhancement(IndexingEnhancement.new)
38
41
  end
39
42
 
40
43
  sig { override.void }
@@ -51,9 +54,7 @@ module RubyLsp
51
54
  ).void
52
55
  end
53
56
  def create_code_lens_listener(response_builder, uri, dispatcher)
54
- return unless T.must(@global_state).test_library == "rails"
55
-
56
- CodeLens.new(@client, response_builder, uri, dispatcher)
57
+ CodeLens.new(@client, T.must(@global_state), response_builder, uri, dispatcher)
57
58
  end
58
59
 
59
60
  sig do
@@ -79,7 +80,9 @@ module RubyLsp
79
80
 
80
81
  sig do
81
82
  override.params(
82
- response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::Location],
83
+ response_builder: ResponseBuilders::CollectionResponseBuilder[T.any(
84
+ Interface::Location, Interface::LocationLink
85
+ )],
83
86
  uri: URI::Generic,
84
87
  node_context: NodeContext,
85
88
  dispatcher: Prism::Dispatcher,
@@ -79,20 +79,30 @@ module RubyLsp
79
79
  sig do
80
80
  params(
81
81
  client: RunnerClient,
82
+ global_state: GlobalState,
82
83
  response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::CodeLens],
83
84
  uri: URI::Generic,
84
85
  dispatcher: Prism::Dispatcher,
85
86
  ).void
86
87
  end
87
- def initialize(client, response_builder, uri, dispatcher)
88
+ def initialize(client, global_state, response_builder, uri, dispatcher)
88
89
  @client = client
90
+ @global_state = global_state
89
91
  @response_builder = response_builder
90
92
  @path = T.let(uri.to_standardized_path, T.nilable(String))
91
93
  @group_id = T.let(1, Integer)
92
94
  @group_id_stack = T.let([], T::Array[Integer])
93
95
  @constant_name_stack = T.let([], T::Array[[String, T.nilable(String)]])
94
96
 
95
- dispatcher.register(self, :on_call_node_enter, :on_class_node_enter, :on_def_node_enter, :on_class_node_leave)
97
+ dispatcher.register(
98
+ self,
99
+ :on_call_node_enter,
100
+ :on_class_node_enter,
101
+ :on_def_node_enter,
102
+ :on_class_node_leave,
103
+ :on_module_node_enter,
104
+ :on_module_node_leave,
105
+ )
96
106
  end
97
107
 
98
108
  sig { params(node: Prism::CallNode).void }
@@ -119,6 +129,7 @@ module RubyLsp
119
129
 
120
130
  if controller?
121
131
  add_route_code_lens_to_action(node)
132
+ add_jump_to_view(node)
122
133
  end
123
134
  end
124
135
 
@@ -156,6 +167,16 @@ module RubyLsp
156
167
  @constant_name_stack.pop
157
168
  end
158
169
 
170
+ sig { params(node: Prism::ModuleNode).void }
171
+ def on_module_node_enter(node)
172
+ @constant_name_stack << [node.constant_path.slice, nil]
173
+ end
174
+
175
+ sig { params(node: Prism::ModuleNode).void }
176
+ def on_module_node_leave(node)
177
+ @constant_name_stack.pop
178
+ end
179
+
159
180
  private
160
181
 
161
182
  sig { returns(T.nilable(T::Boolean)) }
@@ -167,34 +188,42 @@ module RubyLsp
167
188
  end
168
189
 
169
190
  sig { params(node: Prism::DefNode).void }
170
- def add_route_code_lens_to_action(node)
171
- class_name, _ = T.must(@constant_name_stack.last)
172
- route = @client.route(
173
- controller: class_name,
174
- action: node.name.to_s,
191
+ def add_jump_to_view(node)
192
+ class_name = @constant_name_stack.map(&:first).join("::")
193
+ action_name = node.name
194
+ controller_name = class_name
195
+ .delete_suffix("Controller")
196
+ .gsub(/([a-z])([A-Z])/, "\\1_\\2")
197
+ .gsub("::", "/")
198
+ .downcase
199
+
200
+ view_uris = Dir.glob("#{@client.rails_root}/app/views/#{controller_name}/#{action_name}*").map! do |path|
201
+ URI::Generic.from_path(path: path).to_s
202
+ end
203
+ return if view_uris.empty?
204
+
205
+ @response_builder << create_code_lens(
206
+ node,
207
+ title: "Jump to view",
208
+ command_name: "rubyLsp.openFile",
209
+ arguments: [view_uris],
210
+ data: { type: "file" },
175
211
  )
212
+ end
176
213
 
214
+ sig { params(node: Prism::DefNode).void }
215
+ def add_route_code_lens_to_action(node)
216
+ class_name, _ = T.must(@constant_name_stack.last)
217
+ route = @client.route(controller: class_name, action: node.name.to_s)
177
218
  return unless route
178
219
 
179
- path = route[:path]
180
- verb = route[:verb]
181
- source_location = route[:source_location]
182
-
183
- arguments = [
184
- source_location,
185
- {
186
- start_line: node.location.start_line - 1,
187
- start_column: node.location.start_column,
188
- end_line: node.location.end_line - 1,
189
- end_column: node.location.end_column,
190
- },
191
- ]
220
+ file_path, line = route[:source_location]
192
221
 
193
222
  @response_builder << create_code_lens(
194
223
  node,
195
- title: [verb, path].join(" "),
224
+ title: "#{route[:verb]} #{route[:path]}",
196
225
  command_name: "rubyLsp.openFile",
197
- arguments: arguments,
226
+ arguments: [["file://#{file_path}#L#{line}"]],
198
227
  data: { type: "file" },
199
228
  )
200
229
  end
@@ -230,6 +259,7 @@ module RubyLsp
230
259
  sig { params(node: Prism::Node, name: String, command: String, kind: Symbol).void }
231
260
  def add_test_code_lens(node, name:, command:, kind:)
232
261
  return unless @path
262
+ return unless @global_state.test_library == "rails"
233
263
 
234
264
  arguments = [
235
265
  @path,
@@ -17,7 +17,7 @@ module RubyLsp
17
17
  # # Example
18
18
  #
19
19
  # ```ruby
20
- # before_action :foo # <- Go to definition on this symbol will jump to the method if it is defined in the same class
20
+ # before_action :foo # <- Go to definition on this symbol will jump to the method
21
21
  # ```
22
22
  #
23
23
  # Notes for named routes:
@@ -34,7 +34,9 @@ module RubyLsp
34
34
  sig do
35
35
  params(
36
36
  client: RunnerClient,
37
- response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::Location],
37
+ response_builder: RubyLsp::ResponseBuilders::CollectionResponseBuilder[T.any(
38
+ Interface::Location, Interface::LocationLink
39
+ )],
38
40
  node_context: NodeContext,
39
41
  index: RubyIndexer::Index,
40
42
  dispatcher: Prism::Dispatcher,
@@ -0,0 +1,113 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ module Rails
6
+ class IndexingEnhancement
7
+ extend T::Sig
8
+ include RubyIndexer::Enhancement
9
+
10
+ sig do
11
+ override.params(
12
+ index: RubyIndexer::Index,
13
+ owner: T.nilable(RubyIndexer::Entry::Namespace),
14
+ node: Prism::CallNode,
15
+ file_path: String,
16
+ ).void
17
+ end
18
+ def on_call_node(index, owner, node, file_path)
19
+ return unless owner
20
+
21
+ name = node.name
22
+
23
+ case name
24
+ when :extend
25
+ handle_concern_extend(index, owner, node)
26
+ when :has_one, :has_many, :belongs_to, :has_and_belongs_to_many
27
+ handle_association(index, owner, node, file_path)
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ sig do
34
+ params(
35
+ index: RubyIndexer::Index,
36
+ owner: RubyIndexer::Entry::Namespace,
37
+ node: Prism::CallNode,
38
+ file_path: String,
39
+ ).void
40
+ end
41
+ def handle_association(index, owner, node, file_path)
42
+ arguments = node.arguments&.arguments
43
+ return unless arguments
44
+
45
+ name_arg = arguments.first
46
+
47
+ name = case name_arg
48
+ when Prism::StringNode
49
+ name_arg.content
50
+ when Prism::SymbolNode
51
+ name_arg.value
52
+ end
53
+
54
+ return unless name
55
+
56
+ # Reader
57
+ index.add(RubyIndexer::Entry::Method.new(
58
+ name,
59
+ file_path,
60
+ name_arg.location,
61
+ name_arg.location,
62
+ [],
63
+ [RubyIndexer::Entry::Signature.new([])],
64
+ RubyIndexer::Entry::Visibility::PUBLIC,
65
+ owner,
66
+ ))
67
+
68
+ # Writer
69
+ index.add(RubyIndexer::Entry::Method.new(
70
+ "#{name}=",
71
+ file_path,
72
+ name_arg.location,
73
+ name_arg.location,
74
+ [],
75
+ [RubyIndexer::Entry::Signature.new([RubyIndexer::Entry::RequiredParameter.new(name: name.to_sym)])],
76
+ RubyIndexer::Entry::Visibility::PUBLIC,
77
+ owner,
78
+ ))
79
+ end
80
+
81
+ sig do
82
+ params(
83
+ index: RubyIndexer::Index,
84
+ owner: RubyIndexer::Entry::Namespace,
85
+ node: Prism::CallNode,
86
+ ).void
87
+ end
88
+ def handle_concern_extend(index, owner, node)
89
+ arguments = node.arguments&.arguments
90
+ return unless arguments
91
+
92
+ arguments.each do |node|
93
+ next unless node.is_a?(Prism::ConstantReadNode) || node.is_a?(Prism::ConstantPathNode)
94
+
95
+ module_name = node.full_name
96
+ next unless module_name == "ActiveSupport::Concern"
97
+
98
+ index.register_included_hook(owner.name) do |index, base|
99
+ class_methods_name = "#{owner.name}::ClassMethods"
100
+
101
+ if index.indexed?(class_methods_name)
102
+ singleton = index.existing_or_new_singleton_class(base.name)
103
+ singleton.mixin_operations << RubyIndexer::Entry::Include.new(class_methods_name)
104
+ end
105
+ end
106
+ rescue Prism::ConstantPathNode::DynamicPartsInConstantPathError,
107
+ Prism::ConstantPathNode::MissingNodesInConstantPathError
108
+ # Do nothing
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
@@ -36,6 +36,9 @@ module RubyLsp
36
36
 
37
37
  extend T::Sig
38
38
 
39
+ sig { returns(String) }
40
+ attr_reader :rails_root
41
+
39
42
  sig { void }
40
43
  def initialize
41
44
  # Spring needs a Process session ID. It uses this ID to "attach" itself to the parent process, so that when the
@@ -67,7 +70,8 @@ module RubyLsp
67
70
 
68
71
  begin
69
72
  count += 1
70
- read_response
73
+ initialize_response = T.must(read_response)
74
+ @rails_root = T.let(initialize_response[:root], String)
71
75
  rescue EmptyMessageError
72
76
  $stderr.puts("Ruby LSP Rails is retrying initialize (#{count})")
73
77
  retry if count < MAX_RETRIES
@@ -80,9 +84,7 @@ module RubyLsp
80
84
  if @wait_thread.alive?
81
85
  $stderr.puts("Ruby LSP Rails is force killing the server")
82
86
  sleep(0.5) # give the server a bit of time if we already issued a shutdown notification
83
-
84
- # Windows does not support the `TERM` signal, so we're forced to use `KILL` here
85
- Process.kill(T.must(Signal.list["KILL"]), @wait_thread.pid)
87
+ force_kill
86
88
  end
87
89
  end
88
90
  end
@@ -145,6 +147,9 @@ module RubyLsp
145
147
  send_message("shutdown")
146
148
  sleep(0.5) # give the server a bit of time to shutdown
147
149
  [@stdin, @stdout, @stderr].each(&:close)
150
+ rescue IOError
151
+ # The server connection may have died
152
+ force_kill
148
153
  end
149
154
 
150
155
  sig { returns(T::Boolean) }
@@ -165,7 +170,7 @@ module RubyLsp
165
170
  read_response
166
171
  end
167
172
 
168
- sig { params(request: String, params: T.nilable(T::Hash[Symbol, T.untyped])).void }
173
+ sig { overridable.params(request: String, params: T.nilable(T::Hash[Symbol, T.untyped])).void }
169
174
  def send_message(request, params = nil)
170
175
  message = { method: request }
171
176
  message[:params] = params if params
@@ -176,9 +181,11 @@ module RubyLsp
176
181
  # The server connection died
177
182
  end
178
183
 
179
- alias_method :send_notification, :send_message
184
+ # Notifications are like messages, but one-way, with no response sent back.
185
+ sig { params(request: String, params: T.nilable(T::Hash[Symbol, T.untyped])).void }
186
+ def send_notification(request, params = nil) = send_message(request, params)
180
187
 
181
- sig { returns(T.nilable(T::Hash[Symbol, T.untyped])) }
188
+ sig { overridable.returns(T.nilable(T::Hash[Symbol, T.untyped])) }
182
189
  def read_response
183
190
  headers = @stdout.gets("\r\n\r\n")
184
191
  raise IncompleteMessageError unless headers
@@ -199,6 +206,12 @@ module RubyLsp
199
206
  # The server connection died
200
207
  nil
201
208
  end
209
+
210
+ sig { void }
211
+ def force_kill
212
+ # Windows does not support the `TERM` signal, so we're forced to use `KILL` here
213
+ Process.kill(T.must(Signal.list["KILL"]), @wait_thread.pid)
214
+ end
202
215
  end
203
216
 
204
217
  class NullClient < RunnerClient
@@ -218,6 +231,11 @@ module RubyLsp
218
231
  true
219
232
  end
220
233
 
234
+ sig { override.returns(String) }
235
+ def rails_root
236
+ Dir.pwd
237
+ end
238
+
221
239
  private
222
240
 
223
241
  sig { override.params(request: String, params: T.nilable(T::Hash[Symbol, T.untyped])).void }
@@ -24,7 +24,7 @@ module RubyLsp
24
24
  routes_reloader = ::Rails.application.routes_reloader
25
25
  routes_reloader.execute_unless_loaded if routes_reloader&.respond_to?(:execute_unless_loaded)
26
26
 
27
- initialize_result = { result: { message: "ok" } }.to_json
27
+ initialize_result = { result: { message: "ok", root: ::Rails.root.to_s } }.to_json
28
28
  $stdout.write("Content-Length: #{initialize_result.length}\r\n\r\n#{initialize_result}")
29
29
 
30
30
  while @running
@@ -3,6 +3,6 @@
3
3
 
4
4
  module RubyLsp
5
5
  module Rails
6
- VERSION = "0.3.10"
6
+ VERSION = "0.3.12"
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.10
4
+ version: 0.3.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-07-11 00:00:00.000000000 Z
11
+ date: 2024-08-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ruby-lsp
@@ -16,7 +16,7 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 0.17.2
19
+ version: 0.17.12
20
20
  - - "<"
21
21
  - !ruby/object:Gem::Version
22
22
  version: 0.18.0
@@ -26,7 +26,7 @@ dependencies:
26
26
  requirements:
27
27
  - - ">="
28
28
  - !ruby/object:Gem::Version
29
- version: 0.17.2
29
+ version: 0.17.12
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
32
  version: 0.18.0
@@ -46,6 +46,7 @@ files:
46
46
  - lib/ruby_lsp/ruby_lsp_rails/definition.rb
47
47
  - lib/ruby_lsp/ruby_lsp_rails/document_symbol.rb
48
48
  - lib/ruby_lsp/ruby_lsp_rails/hover.rb
49
+ - lib/ruby_lsp/ruby_lsp_rails/indexing_enhancement.rb
49
50
  - lib/ruby_lsp/ruby_lsp_rails/runner_client.rb
50
51
  - lib/ruby_lsp/ruby_lsp_rails/server.rb
51
52
  - lib/ruby_lsp/ruby_lsp_rails/support/active_support_test_case_helper.rb
@@ -79,7 +80,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
79
80
  - !ruby/object:Gem::Version
80
81
  version: '0'
81
82
  requirements: []
82
- rubygems_version: 3.5.14
83
+ rubygems_version: 3.5.16
83
84
  signing_key:
84
85
  specification_version: 4
85
86
  summary: A Ruby LSP addon for Rails