Skip to content

Commit ced1f6d

Browse files
author
Aryan Soni
committed
Implement document symbol for validations
1 parent 800a1d5 commit ced1f6d

File tree

3 files changed

+141
-16
lines changed

3 files changed

+141
-16
lines changed

lib/ruby_lsp/ruby_lsp_rails/document_symbol.rb

Lines changed: 78 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -93,25 +93,29 @@ def on_call_node_enter(node)
9393
)
9494
end
9595

96-
extract_callbacks(node)
97-
end
98-
99-
private
100-
101-
sig { params(node: Prism::CallNode).void }
102-
def extract_callbacks(node)
10396
receiver = node.receiver
10497
return if receiver && !receiver.is_a?(Prism::SelfNode)
10598

106-
message_value = node.message
99+
message = node.message
100+
case message
101+
when *CALLBACKS, "validate"
102+
handle_all_arg_types(node, T.must(message))
103+
when "validates", "validates!", "validates_each"
104+
handle_symbol_and_string_arg_types(node, T.must(message))
105+
when "validates_with"
106+
handle_class_arg_types(node, T.must(message))
107+
end
108+
end
107109

108-
return unless CALLBACKS.include?(message_value)
110+
private
109111

112+
sig { params(node: Prism::CallNode, message: String).void }
113+
def handle_all_arg_types(node, message)
110114
block = node.block
111115

112116
if block
113117
append_document_symbol(
114-
name: "#{message_value}(<anonymous>)",
118+
name: "#{message}(<anonymous>)",
115119
range: range_from_location(node.location),
116120
selection_range: range_from_location(block.location),
117121
)
@@ -128,7 +132,7 @@ def extract_callbacks(node)
128132
next unless name
129133

130134
append_document_symbol(
131-
name: "#{message_value}(#{name})",
135+
name: "#{message}(#{name})",
132136
range: range_from_location(argument.location),
133137
selection_range: range_from_location(T.must(argument.value_loc)),
134138
)
@@ -137,13 +141,13 @@ def extract_callbacks(node)
137141
next if name.empty?
138142

139143
append_document_symbol(
140-
name: "#{message_value}(#{name})",
144+
name: "#{message}(#{name})",
141145
range: range_from_location(argument.location),
142146
selection_range: range_from_location(argument.content_loc),
143147
)
144148
when Prism::LambdaNode
145149
append_document_symbol(
146-
name: "#{message_value}(<anonymous>)",
150+
name: "#{message}(<anonymous>)",
147151
range: range_from_location(node.location),
148152
selection_range: range_from_location(argument.location),
149153
)
@@ -157,7 +161,7 @@ def extract_callbacks(node)
157161
next unless name
158162

159163
append_document_symbol(
160-
name: "#{message_value}(#{name})",
164+
name: "#{message}(#{name})",
161165
range: range_from_location(argument.location),
162166
selection_range: range_from_location(argument.location),
163167
)
@@ -166,7 +170,65 @@ def extract_callbacks(node)
166170
next if name.empty?
167171

168172
append_document_symbol(
169-
name: "#{message_value}(#{name})",
173+
name: "#{message}(#{name})",
174+
range: range_from_location(argument.location),
175+
selection_range: range_from_location(argument.location),
176+
)
177+
when Prism::ConstantPathNode
178+
name = argument.full_name
179+
next if name.empty?
180+
181+
append_document_symbol(
182+
name: "#{message}(#{name})",
183+
range: range_from_location(argument.location),
184+
selection_range: range_from_location(argument.location),
185+
)
186+
end
187+
end
188+
end
189+
190+
sig { params(node: Prism::CallNode, message: String).void }
191+
def handle_symbol_and_string_arg_types(node, message)
192+
arguments = node.arguments&.arguments
193+
return unless arguments&.any?
194+
195+
arguments.each do |argument|
196+
case argument
197+
when Prism::SymbolNode
198+
name = argument.value
199+
next unless name
200+
201+
append_document_symbol(
202+
name: "#{message}(#{name})",
203+
range: range_from_location(argument.location),
204+
selection_range: range_from_location(T.must(argument.value_loc)),
205+
)
206+
when Prism::StringNode
207+
name = argument.content
208+
next if name.empty?
209+
210+
append_document_symbol(
211+
name: "#{message}(#{name})",
212+
range: range_from_location(argument.location),
213+
selection_range: range_from_location(argument.content_loc),
214+
)
215+
end
216+
end
217+
end
218+
219+
sig { params(node: Prism::CallNode, message: String).void }
220+
def handle_class_arg_types(node, message)
221+
arguments = node.arguments&.arguments
222+
return unless arguments&.any?
223+
224+
arguments.each do |argument|
225+
case argument
226+
when Prism::ConstantReadNode
227+
name = argument.name
228+
next if name.empty?
229+
230+
append_document_symbol(
231+
name: "#{message}(#{name})",
170232
range: range_from_location(argument.location),
171233
selection_range: range_from_location(argument.location),
172234
)
@@ -175,7 +237,7 @@ def extract_callbacks(node)
175237
next if name.empty?
176238

177239
append_document_symbol(
178-
name: "#{message_value}(#{name})",
240+
name: "#{message}(#{name})",
179241
range: range_from_location(argument.location),
180242
selection_range: range_from_location(argument.location),
181243
)

test/dummy/app/models/user.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@
33

44
class User < ApplicationRecord
55
before_create :foo_arg, -> () {}
6+
validates :name, presence: true
67
end

test/ruby_lsp_rails/document_symbol_test.rb

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,68 @@ class FooJob < ApplicationJob
287287
assert_empty(response[0].children)
288288
end
289289

290+
test "correctly handles validate method with all argument types" do
291+
response = generate_document_symbols_for_source(<<~RUBY)
292+
class FooModel < ApplicationRecord
293+
validate "foo_arg", :bar_arg, -> () {}, Foo::BazClass.new("blah"), FooClass, Foo::BarClass
294+
end
295+
RUBY
296+
297+
assert_equal(1, response.size)
298+
assert_equal("FooModel", response[0].name)
299+
assert_equal(6, response[0].children.size)
300+
assert_equal("validate(foo_arg)", response[0].children[0].name)
301+
assert_equal("validate(bar_arg)", response[0].children[1].name)
302+
assert_equal("validate(<anonymous>)", response[0].children[2].name)
303+
assert_equal("validate(Foo::BazClass)", response[0].children[3].name)
304+
assert_equal("validate(FooClass)", response[0].children[4].name)
305+
assert_equal("validate(Foo::BarClass)", response[0].children[5].name)
306+
end
307+
308+
test "correctly handles validates method with Prism::StringNode and Prism::SymbolNode argument types" do
309+
response = generate_document_symbols_for_source(<<~RUBY)
310+
class FooModel < ApplicationRecord
311+
validates "foo_arg", :bar_arg
312+
end
313+
RUBY
314+
315+
assert_equal(1, response.size)
316+
assert_equal("FooModel", response[0].name)
317+
assert_equal(2, response[0].children.size)
318+
assert_equal("validates(foo_arg)", response[0].children[0].name)
319+
assert_equal("validates(bar_arg)", response[0].children[1].name)
320+
end
321+
322+
test "correctly handles validates_each method with Prism::StringNode and Prism::SymbolNode argument types" do
323+
response = generate_document_symbols_for_source(<<~RUBY)
324+
class FooModel < ApplicationRecord
325+
validates_each "foo_arg", :bar_arg do
326+
puts "Foo"
327+
end
328+
end
329+
RUBY
330+
331+
assert_equal(1, response.size)
332+
assert_equal("FooModel", response[0].name)
333+
assert_equal(2, response[0].children.size)
334+
assert_equal("validates_each(foo_arg)", response[0].children[0].name)
335+
assert_equal("validates_each(bar_arg)", response[0].children[1].name)
336+
end
337+
338+
test "correctly handles validates_with method with Prism::ConstantReadNode and Prism::ConstantPathNode argument types" do
339+
response = generate_document_symbols_for_source(<<~RUBY)
340+
class FooModel < ApplicationRecord
341+
validates_with FooClass, Foo::BarClass
342+
end
343+
RUBY
344+
345+
assert_equal(1, response.size)
346+
assert_equal("FooModel", response[0].name)
347+
assert_equal(2, response[0].children.size)
348+
assert_equal("validates_with(FooClass)", response[0].children[0].name)
349+
assert_equal("validates_with(Foo::BarClass)", response[0].children[1].name)
350+
end
351+
290352
private
291353

292354
def generate_document_symbols_for_source(source)

0 commit comments

Comments
 (0)