ruby-lsp-rails 0.3.10 → 0.3.12

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