|
1 | 1 | # typed: strict |
2 | 2 | # frozen_string_literal: true |
3 | 3 |
|
4 | | -require "pathname" |
5 | | - |
6 | 4 | module RubyLsp |
7 | 5 | module Rails |
8 | 6 | #  |
@@ -33,8 +31,8 @@ class Definition |
33 | 31 | include Requests::Support::Common |
34 | 32 | include Inflections |
35 | 33 |
|
36 | | - #: (RunnerClient client, RubyLsp::ResponseBuilders::CollectionResponseBuilder[(Interface::Location | Interface::LocationLink)] response_builder, NodeContext node_context, RubyIndexer::Index index, Prism::Dispatcher dispatcher) -> void |
37 | | - def initialize(client, response_builder, uri, node_context, index, dispatcher) |
| 34 | + #: (RunnerClient client, RubyLsp::ResponseBuilders::CollectionResponseBuilder[(Interface::Location | Interface::LocationLink)] response_builder, URI::Generic uri, NodeContext node_context, RubyIndexer::Index index, Prism::Dispatcher dispatcher) -> void |
| 35 | + def initialize(client, response_builder, uri, node_context, index, dispatcher) # rubocop:disable Metrics/ParameterLists |
38 | 36 | @client = client |
39 | 37 | @response_builder = response_builder |
40 | 38 | @path = uri.to_standardized_path #: String? |
@@ -147,45 +145,37 @@ def handle_association(node) |
147 | 145 | @response_builder << Support::LocationBuilder.line_location_from_s(result.fetch(:location)) |
148 | 146 | end |
149 | 147 |
|
| 148 | + #: (Prism::StringNode node) -> void |
150 | 149 | def handle_possible_render(node) |
151 | 150 | return unless @path&.end_with?(".html.erb") |
152 | 151 |
|
153 | 152 | call_node = @node_context.call_node |
154 | 153 | return unless call_node |
155 | 154 | return unless self_receiver?(call_node) |
156 | 155 |
|
157 | | - message = call_node.message |
158 | | - return unless message == "render" |
| 156 | + return unless call_node.message == "render" |
159 | 157 |
|
160 | 158 | arguments = call_node.arguments&.arguments |
161 | 159 | return unless arguments |
162 | 160 |
|
163 | 161 | argument = view_template_argument(arguments, node) |
164 | 162 | return unless argument |
165 | 163 |
|
166 | | - template = node.content |
167 | | - template_options = view_template_options(arguments) |
168 | | - |
169 | | - formats_pattern = template_options[:formats] ? "{#{template_options[:formats].join(",")}}" : "html" |
170 | | - variants_pattern = "{#{template_options[:variants].map { |variant| "+#{variant}" }.join(",")},}" if template_options[:variants] |
171 | | - handlers_pattern = template_options[:handlers] ? "{#{template_options[:handlers].join(",")}}" : "*" |
| 164 | + controller_name = controller_for_template(@path) |
| 165 | + return unless controller_name |
172 | 166 |
|
173 | | - extension_pattern = "#{formats_pattern}#{variants_pattern}.#{handlers_pattern}" |
| 167 | + template_name = node.content |
| 168 | + template_details = view_template_details(arguments) |
174 | 169 |
|
175 | | - template_pattern = if argument == "template" |
176 | | - File.join(@client.views_dir, "#{template}.#{extension_pattern}") |
177 | | - elsif template.include?("/") |
178 | | - *partial_dir, partial_name = template.split("/") |
179 | | - |
180 | | - File.join(@client.views_dir, *partial_dir, "_#{partial_name}.#{extension_pattern}") |
181 | | - else |
182 | | - File.join(@client.views_dir, "{#{view_prefixes.join(",")}}", "_#{template}.#{extension_pattern}") |
183 | | - end |
184 | | - |
185 | | - template_path = Dir.glob(template_pattern).first |
186 | | - return unless template_path |
| 170 | + template = @client.find_template( |
| 171 | + controller_name: controller_name, |
| 172 | + template_name: template_name, |
| 173 | + partial: argument != "template", |
| 174 | + details: template_details, |
| 175 | + ) |
| 176 | + return unless template |
187 | 177 |
|
188 | | - @response_builder << Support::LocationBuilder.line_location_from_s("#{template_path}:1") |
| 178 | + @response_builder << Support::LocationBuilder.line_location_from_s("#{template[:path]}:1") |
189 | 179 | end |
190 | 180 |
|
191 | 181 | #: (Prism::CallNode node) -> void |
@@ -241,48 +231,69 @@ def handle_if_unless_conditional(node, call_node, arguments) |
241 | 231 | collect_definitions(method_name) |
242 | 232 | end |
243 | 233 |
|
| 234 | + #: (Array[Prism::Node] arguments, Prism::StringNode node) -> String? |
244 | 235 | def view_template_argument(arguments, node) |
245 | 236 | return "partial" if arguments.first == node |
246 | 237 |
|
247 | | - kwargs = arguments.find { |argument| argument.is_a?(Prism::KeywordHashNode) } |
248 | | - return unless kwargs |
| 238 | + keyword_arguments = arguments.find { |argument| argument.is_a?(Prism::KeywordHashNode) } #: as Prism::KeywordHashNode? |
| 239 | + return unless keyword_arguments |
249 | 240 |
|
250 | | - kwarg = kwargs.elements.find do |pair| |
251 | | - ["partial", "layout", "spacer_template", "template"].include?(pair.key.value) && pair.value == node |
252 | | - end |
| 241 | + element = keyword_arguments.elements.find do |element| |
| 242 | + next unless element.is_a?(Prism::AssocNode) |
| 243 | + |
| 244 | + key = element.key |
| 245 | + next unless key.is_a?(Prism::SymbolNode) |
| 246 | + |
| 247 | + next unless element.value == node |
253 | 248 |
|
254 | | - kwarg&.key&.value |
| 249 | + ["partial", "layout", "spacer_template", "template"].include?(key.value) |
| 250 | + end #: as Prism::AssocNode? |
| 251 | + |
| 252 | + return unless element |
| 253 | + |
| 254 | + key = element.key #: as Prism::SymbolNode |
| 255 | + key.value |
255 | 256 | end |
256 | 257 |
|
257 | | - def view_template_options(arguments) |
258 | | - kwargs = arguments.find { |argument| argument.is_a?(Prism::KeywordHashNode) } |
259 | | - return {} unless kwargs |
| 258 | + #: (Array[Prism::Node] arguments) -> Hash[String, (String | Array[String])] |
| 259 | + def view_template_details(arguments) |
| 260 | + keyword_arguments = arguments.find { |argument| argument.is_a?(Prism::KeywordHashNode) } #: as Prism::KeywordHashNode? |
| 261 | + return {} unless keyword_arguments |
| 262 | + |
| 263 | + keyword_arguments.elements.each_with_object({}) do |element, options| |
| 264 | + next unless element.is_a?(Prism::AssocNode) |
| 265 | + |
| 266 | + key = element.key |
| 267 | + next unless key.is_a?(Prism::SymbolNode) |
260 | 268 |
|
261 | | - kwargs.elements.each_with_object({}) do |pair, options| |
262 | | - next unless ["formats", "variants", "handlers"].include?(pair.key.value) |
| 269 | + key_value = key.value |
| 270 | + next unless ["formats", "variants", "handlers"].include?(key_value) |
263 | 271 |
|
264 | | - value = [pair.value.value] if pair.value.is_a?(Prism::SymbolNode) |
265 | | - value = pair.value.elements.map(&:value) if pair.value.is_a?(Prism::ArrayNode) |
| 272 | + value = element.value |
266 | 273 |
|
267 | | - options[pair.key.value.to_sym] = value |
| 274 | + if value.is_a?(Prism::SymbolNode) |
| 275 | + options[key_value] = value.value |
| 276 | + elsif value.is_a?(Prism::ArrayNode) && value.elements.all?(Prism::SymbolNode) |
| 277 | + elements = value.elements #: as Array[Prism::SymbolNode] |
| 278 | + options[key_value] = elements.map(&:value) |
| 279 | + end |
268 | 280 | end |
269 | 281 | end |
270 | 282 |
|
271 | | - # Resolve available directories from which the controller can render relative |
272 | | - # partials based on its ancestry chain. |
273 | | - def view_prefixes |
274 | | - controller_dir = Pathname(@path).dirname.relative_path_from(@client.views_dir).to_s |
275 | | - controller_class = "#{camelize(controller_dir)}Controller" |
276 | | - controller_ancestors = [controller_class] |
277 | | - |
278 | | - controller_entry = @index.resolve(controller_class, [])&.find(&:parent_class) |
279 | | - while controller_entry |
280 | | - controller_entry = @index.resolve(controller_entry.parent_class, controller_entry.nesting)&.find(&:parent_class) |
281 | | - break unless controller_entry && not_in_dependencies?(controller_entry.file_path) |
282 | | - controller_ancestors << controller_entry.name |
283 | | - end |
| 283 | + #: (String template_path) -> String? |
| 284 | + def controller_for_template(template_path) |
| 285 | + controller_info = @client.controller("ActionController::Base") |
| 286 | + return unless controller_info |
| 287 | + |
| 288 | + view_paths = controller_info[:view_paths] |
| 289 | + template_directory = File.dirname(template_path) |
| 290 | + |
| 291 | + view_path = view_paths.find { |path| template_directory.start_with?(path + "/") } |
| 292 | + return unless view_path |
| 293 | + |
| 294 | + controller_path = template_directory.delete_prefix(view_path + "/") |
284 | 295 |
|
285 | | - controller_ancestors.map { |ancestor| underscore(ancestor.delete_suffix("Controller")) } |
| 296 | + camelize(controller_path) + "Controller" |
286 | 297 | end |
287 | 298 | end |
288 | 299 | end |
|
0 commit comments