Skip to content

Commit 9146188

Browse files
author
Aryan Soni
committed
Integrate document symbol for callbacks
1 parent e2449c0 commit 9146188

File tree

2 files changed

+180
-5
lines changed

2 files changed

+180
-5
lines changed

lib/ruby_lsp/ruby_lsp_rails/document_symbol.rb

Lines changed: 126 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,61 @@ class DocumentSymbol
1212
include Requests::Support::Common
1313
include ActiveSupportTestCaseHelper
1414

15+
MODEL_CALLBACKS = T.let(
16+
[
17+
"before_validation",
18+
"after_validation",
19+
"before_save",
20+
"around_save",
21+
"before_create",
22+
"around_create",
23+
"after_create",
24+
"after_save",
25+
"after_commit",
26+
"after_rollback",
27+
"before_update",
28+
"around_update",
29+
"after_update",
30+
"before_destroy",
31+
"around_destroy",
32+
"after_destroy",
33+
"after_initialize",
34+
"after_find",
35+
"after_touch",
36+
].freeze,
37+
T::Array[String],
38+
)
39+
40+
CONTROLLER_CALLBACKS = T.let(
41+
[
42+
"after_action",
43+
"append_after_action",
44+
"append_around_action",
45+
"append_before_action",
46+
"around_action",
47+
"before_action",
48+
"prepend_after_action",
49+
"prepend_around_action",
50+
"prepend_before_action",
51+
"skip_after_action",
52+
"skip_around_action",
53+
"skip_before_action",
54+
].freeze,
55+
T::Array[String],
56+
)
57+
58+
JOB_CALLBACKS = T.let(
59+
[
60+
"after_enqueue",
61+
"after_perform",
62+
"around_enqueue",
63+
"around_perform",
64+
"before_enqueue",
65+
"before_perform",
66+
].freeze,
67+
T::Array[String],
68+
)
69+
1570
sig do
1671
params(
1772
response_builder: ResponseBuilders::DocumentSymbol,
@@ -28,13 +83,79 @@ def initialize(response_builder, dispatcher)
2883
def on_call_node_enter(node)
2984
content = extract_test_case_name(node)
3085

31-
return unless content
86+
if content
87+
append_document_symbol(
88+
name: content,
89+
selection_range: range_from_node(node),
90+
range: range_from_node(node),
91+
)
92+
end
93+
94+
extract_callbacks(node)
95+
end
96+
97+
private
98+
99+
sig { params(node: Prism::CallNode).void }
100+
def extract_callbacks(node)
101+
receiver = node.receiver
102+
return if receiver && !receiver.is_a?(Prism::SelfNode)
103+
104+
message_value = node.message
105+
callbacks = [MODEL_CALLBACKS, CONTROLLER_CALLBACKS, JOB_CALLBACKS]
106+
107+
return unless callbacks.any? { |callback| callback.include?(message_value) }
108+
109+
block = node.block
32110

111+
if block
112+
append_document_symbol(
113+
name: "#{message_value}(<anonymous>)",
114+
range: range_from_location(node.location),
115+
selection_range: range_from_location(block.location),
116+
)
117+
return
118+
end
119+
120+
arguments = node.arguments&.arguments
121+
return unless arguments&.any?
122+
123+
arguments.each do |argument|
124+
if argument.is_a?(Prism::SymbolNode)
125+
name = argument.value
126+
next unless name
127+
128+
append_document_symbol(
129+
name: "#{message_value}(#{name})",
130+
range: range_from_location(argument.location),
131+
selection_range: range_from_location(T.must(argument.value_loc)),
132+
)
133+
elsif argument.is_a?(Prism::StringNode)
134+
name = argument.content
135+
next if name.empty?
136+
137+
append_document_symbol(
138+
name: "#{message_value}(#{name})",
139+
range: range_from_location(argument.location),
140+
selection_range: range_from_location(argument.content_loc),
141+
)
142+
end
143+
end
144+
end
145+
146+
sig do
147+
params(
148+
name: String,
149+
range: RubyLsp::Interface::Range,
150+
selection_range: RubyLsp::Interface::Range,
151+
).void
152+
end
153+
def append_document_symbol(name:, range:, selection_range:)
33154
@response_builder.last.children << RubyLsp::Interface::DocumentSymbol.new(
34-
name: content,
35-
kind: LanguageServer::Protocol::Constant::SymbolKind::METHOD,
36-
selection_range: range_from_node(node),
37-
range: range_from_node(node),
155+
name: name,
156+
kind: RubyLsp::Constant::SymbolKind::METHOD,
157+
range: range,
158+
selection_range: selection_range,
38159
)
39160
end
40161
end

test/ruby_lsp_rails/document_symbol_test.rb

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,60 @@ class NestedTest < ActiveSupport::TestCase
163163
assert_equal("back to the same level", response[0].children[2].name)
164164
end
165165

166+
test "correctly handles model callbacks with multiple Prism::StringNode arguments" do
167+
response = generate_document_symbols_for_source(<<~RUBY)
168+
class FooModel < ApplicationRecord
169+
before_save "foo_method", "bar_method", on: :update
170+
end
171+
RUBY
172+
173+
assert_equal(1, response.size)
174+
assert_equal("FooModel", response[0].name)
175+
assert_equal(2, response[0].children.size)
176+
assert_equal("before_save(foo_method)", response[0].children[0].name)
177+
assert_equal("before_save(bar_method)", response[0].children[1].name)
178+
end
179+
180+
test "correctly handles controller callback with block" do
181+
response = generate_document_symbols_for_source(<<~RUBY)
182+
class FooController < ApplicationController
183+
before_action do
184+
# block body
185+
end
186+
end
187+
RUBY
188+
189+
assert_equal(1, response.size)
190+
assert_equal("FooController", response[0].name)
191+
assert_equal(1, response[0].children.size)
192+
assert_equal("before_action(<anonymous>)", response[0].children[0].name)
193+
end
194+
195+
test "correctly handles job callback with Prism::SymbolNode argument" do
196+
response = generate_document_symbols_for_source(<<~RUBY)
197+
class FooJob < ApplicationJob
198+
before_perform :foo_method
199+
end
200+
RUBY
201+
202+
assert_equal(1, response.size)
203+
assert_equal("FooJob", response[0].name)
204+
assert_equal(1, response[0].children.size)
205+
assert_equal("before_perform(foo_method)", response[0].children[0].name)
206+
end
207+
208+
test "ignore unrecognized callback" do
209+
response = generate_document_symbols_for_source(<<~RUBY)
210+
class FooJob < ApplicationJob
211+
unrecognized_callback :foo_method
212+
end
213+
RUBY
214+
215+
assert_equal(1, response.size)
216+
assert_equal("FooJob", response[0].name)
217+
assert_empty(response[0].children)
218+
end
219+
166220
private
167221

168222
def generate_document_symbols_for_source(source)

0 commit comments

Comments
 (0)