Skip to content

Commit ec31864

Browse files
authored
Add go to definition in validations and if/unless conditionals (#638)
Add go to definition in validations
1 parent dd4df19 commit ec31864

File tree

4 files changed

+311
-45
lines changed

4 files changed

+311
-45
lines changed

lib/ruby_lsp/ruby_lsp_rails/addon.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
require_relative "support/active_support_test_case_helper"
88
require_relative "support/associations"
99
require_relative "support/callbacks"
10+
require_relative "support/validations"
1011
require_relative "support/location_builder"
1112
require_relative "runner_client"
1213
require_relative "hover"

lib/ruby_lsp/ruby_lsp_rails/definition.rb

Lines changed: 82 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -51,55 +51,78 @@ def on_string_node_enter(node)
5151
handle_possible_dsl(node)
5252
end
5353

54-
#: ((Prism::SymbolNode | Prism::StringNode) node) -> void
55-
def handle_possible_dsl(node)
56-
node = @node_context.call_node
57-
return unless node
54+
#: (Prism::CallNode node) -> void
55+
def on_call_node_enter(node)
5856
return unless self_receiver?(node)
5957

6058
message = node.message
6159

6260
return unless message
6361

64-
if Support::Associations::ALL.include?(message)
65-
handle_association(node)
66-
elsif Support::Callbacks::ALL.include?(message)
67-
handle_callback(node)
62+
if message.end_with?("_path") || message.end_with?("_url")
63+
handle_route(node)
6864
end
6965
end
7066

71-
#: (Prism::CallNode node) -> void
72-
def on_call_node_enter(node)
73-
return unless self_receiver?(node)
67+
private
7468

75-
message = node.message
69+
#: ((Prism::SymbolNode | Prism::StringNode) node) -> void
70+
def handle_possible_dsl(node)
71+
call_node = @node_context.call_node
72+
return unless call_node
73+
return unless self_receiver?(call_node)
74+
75+
message = call_node.message
7676

7777
return unless message
7878

79-
if message.end_with?("_path") || message.end_with?("_url")
80-
handle_route(node)
79+
arguments = call_node.arguments&.arguments
80+
return unless arguments
81+
82+
if Support::Associations::ALL.include?(message)
83+
handle_association(call_node)
84+
elsif Support::Callbacks::ALL.include?(message)
85+
handle_callback(node, call_node, arguments)
86+
handle_if_unless_conditional(node, call_node, arguments)
87+
elsif Support::Validations::ALL.include?(message)
88+
handle_validation(node, call_node, arguments)
89+
handle_if_unless_conditional(node, call_node, arguments)
8190
end
8291
end
8392

84-
private
93+
#: ((Prism::SymbolNode | Prism::StringNode) node, Prism::CallNode call_node, Array[Prism::Node] arguments) -> void
94+
def handle_callback(node, call_node, arguments)
95+
focus_argument = arguments.find { |argument| argument == node }
8596

86-
#: (Prism::CallNode node) -> void
87-
def handle_callback(node)
88-
arguments = node.arguments&.arguments
89-
return unless arguments&.any?
97+
name = case focus_argument
98+
when Prism::SymbolNode
99+
focus_argument.value
100+
when Prism::StringNode
101+
focus_argument.content
102+
end
103+
104+
return unless name
90105

91-
arguments.each do |argument|
92-
name = case argument
93-
when Prism::SymbolNode
94-
argument.value
95-
when Prism::StringNode
96-
argument.content
97-
end
106+
collect_definitions(name)
107+
end
98108

99-
next unless name
109+
#: ((Prism::SymbolNode | Prism::StringNode) node, Prism::CallNode call_node, Array[Prism::Node] arguments) -> void
110+
def handle_validation(node, call_node, arguments)
111+
message = call_node.message
112+
return unless message
100113

101-
collect_definitions(name)
102-
end
114+
focus_argument = arguments.find { |argument| argument == node }
115+
return unless focus_argument
116+
117+
return unless node.is_a?(Prism::SymbolNode)
118+
119+
name = node.value
120+
return unless name
121+
122+
# validates_with uses constants, not symbols - skip (handled by constant resolution)
123+
return if message == "validates_with"
124+
125+
collect_definitions(name)
103126
end
104127

105128
#: (Prism::CallNode node) -> void
@@ -141,6 +164,36 @@ def collect_definitions(name)
141164
)
142165
end
143166
end
167+
168+
#: ((Prism::SymbolNode | Prism::StringNode) node, Prism::CallNode call_node, Array[Prism::Node] arguments) -> void
169+
def handle_if_unless_conditional(node, call_node, arguments)
170+
keyword_arguments = arguments.find { |argument| argument.is_a?(Prism::KeywordHashNode) } #: as Prism::KeywordHashNode?
171+
return unless keyword_arguments
172+
173+
element = keyword_arguments.elements.find do |element|
174+
next false unless element.is_a?(Prism::AssocNode)
175+
176+
key = element.key
177+
next false unless key.is_a?(Prism::SymbolNode)
178+
179+
key_value = key.value
180+
next false unless key_value == "if" || key_value == "unless"
181+
182+
value = element.value
183+
next false unless value.is_a?(Prism::SymbolNode)
184+
185+
value == node
186+
end #: as Prism::AssocNode?
187+
188+
return unless element
189+
190+
value = element.value #: as Prism::SymbolNode
191+
method_name = value.value
192+
193+
return unless method_name
194+
195+
collect_definitions(method_name)
196+
end
144197
end
145198
end
146199
end
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# typed: strict
2+
# frozen_string_literal: true
3+
4+
module RubyLsp
5+
module Rails
6+
module Support
7+
module Validations
8+
ALL = [
9+
"validate",
10+
"validates",
11+
"validates!",
12+
"validates_each",
13+
"validates_with",
14+
"validates_absence_of",
15+
"validates_acceptance_of",
16+
"validates_comparison_of",
17+
"validates_confirmation_of",
18+
"validates_exclusion_of",
19+
"validates_format_of",
20+
"validates_inclusion_of",
21+
"validates_length_of",
22+
"validates_numericality_of",
23+
"validates_presence_of",
24+
"validates_size_of",
25+
].freeze
26+
end
27+
end
28+
end
29+
end

0 commit comments

Comments
 (0)