ruby-lsp-rails 0.3.8 → 0.3.13

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: accc5f9539ddceaa7fde3dfdb64fcc74f697e9d17178c45e9271df463cad4f18
4
- data.tar.gz: d594ee73741270b0d6837680a5bacc6e912c1ca856aeaa5806e7d1ad9e96aeb8
3
+ metadata.gz: e2543aa7d39f0a6b231112f9ddb365692c7e8d06eb14681da551ab55fd8f5a4d
4
+ data.tar.gz: e466ccd6d90934e083e638efdcedfb5d6b8f85fbce70b139e1542eeb2ab8a139
5
5
  SHA512:
6
- metadata.gz: 955347d73c343311571e62d7c0bb656a3cc9176f8e6ff87ab010287345d754b49f1889c436c716ce0a40c2aa45938f094124cd18315f787c854db7aed2172ee1
7
- data.tar.gz: 65731a4414f5b68884ea6003097b5b1e67caaaaf2a954b0d25de0ed937cc7600d1b1e4e8d2c25a28b25f526c72fc05e6f9a1996874f95584c4450a4212ef9269
6
+ metadata.gz: 8f7c82462682d93fd6b944c2705c8b62b424b4abc3636d1f2540eeee1ab9f7e2896eb77d338697bb65a0a64752529085f38e49bd5a75bfc668fa9d31dd5b0d10
7
+ data.tar.gz: a8978fdebb07ba8b76dd10a725b3b3c736961f103cefa806fbc0302b494516e0a196105bf7fac9d114c963a62c8a706959d53f1509a4ed343e2e9d24633c4372
data/README.md CHANGED
@@ -9,6 +9,8 @@ Ruby LSP Rails is a [Ruby LSP](https://github.com/Shopify/ruby-lsp) addon for ex
9
9
  * Navigate to associations, validations, callbacks and test cases using your editor's "Go to Symbol" feature, or outline view.
10
10
  * Jump to the definition of callbacks using your editor's "Go to Definition" feature.
11
11
  * Jump to the declaration of a route.
12
+ * Code Lens allowing fast-forwarding or rewinding of migrations.
13
+ * Code Lens showing the path that a route action corresponds to.
12
14
 
13
15
  ## Installation
14
16
 
@@ -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(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,
@@ -5,11 +5,22 @@ module RubyLsp
5
5
  module Rails
6
6
  # ![CodeLens demo](../../code_lens.gif)
7
7
  #
8
- # This feature adds several CodeLens features for Rails applications using Active Support test cases:
8
+ # This feature adds Code Lens features for Rails applications.
9
+ #
10
+ # For Active Support test cases:
9
11
  #
10
12
  # - Run tests in the VS Terminal
11
13
  # - Run tests in the VS Code Test Explorer
12
14
  # - Debug tests
15
+ # - Run migrations in the VS Terminal
16
+ #
17
+ # For Rails controllers:
18
+ #
19
+ # - See the path corresponding to an action
20
+ # - Click on the action's Code Lens to jump to its declaration in the routes.
21
+ #
22
+ # Note: This depends on a support for the `rubyLsp.openFile` command.
23
+ # For the VS Code extension this is built-in, but for other editors this may require some custom configuration.
13
24
  #
14
25
  # The
15
26
  # [code lens](https://microsoft.github.io/language-server-protocol/specification#textDocument_codeLens)
@@ -32,12 +43,34 @@ module RubyLsp
32
43
  # # ...
33
44
  # end
34
45
  # end
35
- # ````
46
+ # ```
47
+ #
48
+ # # Example:
49
+ # ```ruby
50
+ # Run
51
+ # class AddFirstNameToUsers < ActiveRecord::Migration[7.1]
52
+ # # ...
53
+ # end
54
+ # ```
36
55
  #
37
56
  # The code lenses will be displayed above the class and above each test method.
38
57
  #
39
58
  # Note: When using the Test Explorer view, if your code contains a statement to pause execution (e.g. `debugger`) it
40
59
  # will cause the test runner to hang.
60
+ #
61
+ # For the following code, assuming the routing contains `resources :users`, a Code Lens will be seen above each
62
+ # action.
63
+ #
64
+ # ```ruby
65
+ # class UsersController < ApplicationController
66
+ # GET /users(.:format)
67
+ # def index # <- Will show code lens above for the path
68
+ # end
69
+ # end
70
+ # ```
71
+ #
72
+ # Note: Complex routing configurations may not be supported.
73
+ #
41
74
  class CodeLens
42
75
  extend T::Sig
43
76
  include Requests::Support::Common
@@ -45,18 +78,31 @@ module RubyLsp
45
78
 
46
79
  sig do
47
80
  params(
81
+ client: RunnerClient,
82
+ global_state: GlobalState,
48
83
  response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::CodeLens],
49
84
  uri: URI::Generic,
50
85
  dispatcher: Prism::Dispatcher,
51
86
  ).void
52
87
  end
53
- def initialize(response_builder, uri, dispatcher)
88
+ def initialize(client, global_state, response_builder, uri, dispatcher)
89
+ @client = client
90
+ @global_state = global_state
54
91
  @response_builder = response_builder
55
92
  @path = T.let(uri.to_standardized_path, T.nilable(String))
56
93
  @group_id = T.let(1, Integer)
57
94
  @group_id_stack = T.let([], T::Array[Integer])
95
+ @constant_name_stack = T.let([], T::Array[[String, T.nilable(String)]])
58
96
 
59
- 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
+ )
60
106
  end
61
107
 
62
108
  sig { params(node: Prism::CallNode).void }
@@ -74,46 +120,146 @@ module RubyLsp
74
120
  sig { params(node: Prism::DefNode).void }
75
121
  def on_def_node_enter(node)
76
122
  method_name = node.name.to_s
123
+
77
124
  if method_name.start_with?("test_")
78
125
  line_number = node.location.start_line
79
126
  command = "#{test_command} #{@path}:#{line_number}"
80
127
  add_test_code_lens(node, name: method_name, command: command, kind: :example)
81
128
  end
129
+
130
+ if controller?
131
+ add_route_code_lens_to_action(node)
132
+ add_jump_to_view(node)
133
+ end
82
134
  end
83
135
 
84
136
  sig { params(node: Prism::ClassNode).void }
85
137
  def on_class_node_enter(node)
86
138
  class_name = node.constant_path.slice
139
+ superclass_name = node.superclass&.slice
140
+
87
141
  if class_name.end_with?("Test")
88
142
  command = "#{test_command} #{@path}"
89
143
  add_test_code_lens(node, name: class_name, command: command, kind: :group)
90
144
  @group_id_stack.push(@group_id)
91
145
  @group_id += 1
92
146
  end
147
+
148
+ if superclass_name&.start_with?("ActiveRecord::Migration")
149
+ command = "#{migrate_command} VERSION=#{migration_version}"
150
+ add_migrate_code_lens(node, name: class_name, command: command)
151
+ end
152
+
153
+ # We need to use a stack because someone could define a nested class
154
+ # inside a controller. When we exit that nested class declaration, we are
155
+ # back in a controller context. This part is used in other places in the LSP
156
+ @constant_name_stack << [class_name, superclass_name]
93
157
  end
94
158
 
95
159
  sig { params(node: Prism::ClassNode).void }
96
160
  def on_class_node_leave(node)
97
161
  class_name = node.constant_path.slice
162
+
98
163
  if class_name.end_with?("Test")
99
164
  @group_id_stack.pop
100
165
  end
166
+
167
+ @constant_name_stack.pop
168
+ end
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
101
178
  end
102
179
 
103
180
  private
104
181
 
182
+ sig { returns(T.nilable(T::Boolean)) }
183
+ def controller?
184
+ class_name, superclass_name = @constant_name_stack.last
185
+ return false unless class_name && superclass_name
186
+
187
+ class_name.end_with?("Controller") && superclass_name.end_with?("Controller")
188
+ end
189
+
190
+ sig { params(node: Prism::DefNode).void }
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" },
211
+ )
212
+ end
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)
218
+ return unless route
219
+
220
+ file_path, line = route[:source_location]
221
+
222
+ @response_builder << create_code_lens(
223
+ node,
224
+ title: "#{route[:verb]} #{route[:path]}",
225
+ command_name: "rubyLsp.openFile",
226
+ arguments: [["file://#{file_path}#L#{line}"]],
227
+ data: { type: "file" },
228
+ )
229
+ end
230
+
105
231
  sig { returns(String) }
106
232
  def test_command
107
- if Gem.win_platform?
108
- "ruby bin/rails test"
109
- else
110
- "bin/rails test"
111
- end
233
+ "#{RbConfig.ruby} bin/rails test"
234
+ end
235
+
236
+ sig { returns(String) }
237
+ def migrate_command
238
+ "#{RbConfig.ruby} bin/rails db:migrate"
239
+ end
240
+
241
+ sig { returns(T.nilable(String)) }
242
+ def migration_version
243
+ File.basename(T.must(@path)).split("_").first
244
+ end
245
+
246
+ sig { params(node: Prism::Node, name: String, command: String).void }
247
+ def add_migrate_code_lens(node, name:, command:)
248
+ return unless @path
249
+
250
+ @response_builder << create_code_lens(
251
+ node,
252
+ title: "Run",
253
+ command_name: "rubyLsp.runTask",
254
+ arguments: [command],
255
+ data: { type: "migrate" },
256
+ )
112
257
  end
113
258
 
114
259
  sig { params(node: Prism::Node, name: String, command: String, kind: Symbol).void }
115
260
  def add_test_code_lens(node, name:, command:, kind:)
116
261
  return unless @path
262
+ return unless @global_state.test_library == "rails"
117
263
 
118
264
  arguments = [
119
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,8 +36,12 @@ 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
44
+ @mutex = T.let(Mutex.new, Mutex)
41
45
  # Spring needs a Process session ID. It uses this ID to "attach" itself to the parent process, so that when the
42
46
  # parent ends, the spring process ends as well. If this is not set, Spring will throw an error while trying to
43
47
  # set its own session ID
@@ -67,7 +71,8 @@ module RubyLsp
67
71
 
68
72
  begin
69
73
  count += 1
70
- read_response
74
+ initialize_response = T.must(read_response)
75
+ @rails_root = T.let(initialize_response[:root], String)
71
76
  rescue EmptyMessageError
72
77
  $stderr.puts("Ruby LSP Rails is retrying initialize (#{count})")
73
78
  retry if count < MAX_RETRIES
@@ -80,9 +85,7 @@ module RubyLsp
80
85
  if @wait_thread.alive?
81
86
  $stderr.puts("Ruby LSP Rails is force killing the server")
82
87
  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)
88
+ force_kill
86
89
  end
87
90
  end
88
91
  end
@@ -122,6 +125,14 @@ module RubyLsp
122
125
  nil
123
126
  end
124
127
 
128
+ sig { params(controller: String, action: String).returns(T.nilable(T::Hash[Symbol, T.untyped])) }
129
+ def route(controller:, action:)
130
+ make_request("route_info", controller: controller, action: action)
131
+ rescue IncompleteMessageError
132
+ $stderr.puts("Ruby LSP Rails failed to get route information: #{@stderr.read}")
133
+ nil
134
+ end
135
+
125
136
  sig { void }
126
137
  def trigger_reload
127
138
  $stderr.puts("Reloading Rails application")
@@ -137,6 +148,9 @@ module RubyLsp
137
148
  send_message("shutdown")
138
149
  sleep(0.5) # give the server a bit of time to shutdown
139
150
  [@stdin, @stdout, @stderr].each(&:close)
151
+ rescue IOError
152
+ # The server connection may have died
153
+ force_kill
140
154
  end
141
155
 
142
156
  sig { returns(T::Boolean) }
@@ -157,26 +171,35 @@ module RubyLsp
157
171
  read_response
158
172
  end
159
173
 
160
- sig { params(request: String, params: T.nilable(T::Hash[Symbol, T.untyped])).void }
174
+ sig { overridable.params(request: String, params: T.nilable(T::Hash[Symbol, T.untyped])).void }
161
175
  def send_message(request, params = nil)
162
176
  message = { method: request }
163
177
  message[:params] = params if params
164
178
  json = message.to_json
165
179
 
166
- @stdin.write("Content-Length: #{json.length}\r\n\r\n", json)
180
+ @mutex.synchronize do
181
+ @stdin.write("Content-Length: #{json.length}\r\n\r\n", json)
182
+ end
183
+ rescue Errno::EPIPE
184
+ # The server connection died
167
185
  end
168
186
 
169
- alias_method :send_notification, :send_message
187
+ # Notifications are like messages, but one-way, with no response sent back.
188
+ sig { params(request: String, params: T.nilable(T::Hash[Symbol, T.untyped])).void }
189
+ def send_notification(request, params = nil) = send_message(request, params)
170
190
 
171
- sig { returns(T.nilable(T::Hash[Symbol, T.untyped])) }
191
+ sig { overridable.returns(T.nilable(T::Hash[Symbol, T.untyped])) }
172
192
  def read_response
173
- headers = @stdout.gets("\r\n\r\n")
174
- raise IncompleteMessageError unless headers
193
+ raw_response = @mutex.synchronize do
194
+ headers = @stdout.gets("\r\n\r\n")
195
+ raise IncompleteMessageError unless headers
175
196
 
176
- content_length = headers[/Content-Length: (\d+)/i, 1].to_i
177
- raise EmptyMessageError if content_length.zero?
197
+ content_length = headers[/Content-Length: (\d+)/i, 1].to_i
198
+ raise EmptyMessageError if content_length.zero?
199
+
200
+ @stdout.read(content_length)
201
+ end
178
202
 
179
- raw_response = @stdout.read(content_length)
180
203
  response = JSON.parse(T.must(raw_response), symbolize_names: true)
181
204
 
182
205
  if response[:error]
@@ -185,6 +208,15 @@ module RubyLsp
185
208
  end
186
209
 
187
210
  response.fetch(:result)
211
+ rescue Errno::EPIPE
212
+ # The server connection died
213
+ nil
214
+ end
215
+
216
+ sig { void }
217
+ def force_kill
218
+ # Windows does not support the `TERM` signal, so we're forced to use `KILL` here
219
+ Process.kill(T.must(Signal.list["KILL"]), @wait_thread.pid)
188
220
  end
189
221
  end
190
222
 
@@ -205,6 +237,11 @@ module RubyLsp
205
237
  true
206
238
  end
207
239
 
240
+ sig { override.returns(String) }
241
+ def rails_root
242
+ Dir.pwd
243
+ end
244
+
208
245
  private
209
246
 
210
247
  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
@@ -54,6 +54,8 @@ module RubyLsp
54
54
  VOID
55
55
  when "route_location"
56
56
  route_location(params.fetch(:name))
57
+ when "route_info"
58
+ resolve_route_info(params)
57
59
  else
58
60
  VOID
59
61
  end
@@ -63,6 +65,32 @@ module RubyLsp
63
65
 
64
66
  private
65
67
 
68
+ def resolve_route_info(requirements)
69
+ if requirements[:controller]
70
+ requirements[:controller] = requirements.fetch(:controller).underscore.delete_suffix("_controller")
71
+ end
72
+
73
+ # In Rails 7.2 we can use `from_requirements, otherwise we fall back to a private API
74
+ route = if ::Rails.application.routes.respond_to?(:from_requirements)
75
+ ::Rails.application.routes.from_requirements(requirements)
76
+ else
77
+ ::Rails.application.routes.routes.find { |route| route.requirements == requirements }
78
+ end
79
+
80
+ if route&.source_location
81
+ file, _, line = route.source_location.rpartition(":")
82
+ body = {
83
+ source_location: [::Rails.root.join(file).to_s, line],
84
+ verb: route.verb,
85
+ path: route.path.spec.to_s,
86
+ }
87
+
88
+ { result: body }
89
+ else
90
+ { result: nil }
91
+ end
92
+ end
93
+
66
94
  # Older versions of Rails don't support `route_source_locations`.
67
95
  # We also check that it's enabled.
68
96
  if ActionDispatch::Routing::Mapper.respond_to?(:route_source_locations) &&
@@ -3,6 +3,6 @@
3
3
 
4
4
  module RubyLsp
5
5
  module Rails
6
- VERSION = "0.3.8"
6
+ VERSION = "0.3.13"
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.8
4
+ version: 0.3.13
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-02 00:00:00.000000000 Z
11
+ date: 2024-08-15 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.17
83
84
  signing_key:
84
85
  specification_version: 4
85
86
  summary: A Ruby LSP addon for Rails