mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
897b9bf6e2
Instead of using the ERBTracker, we can use RipperTracker which is extracted from https://github.com/jhawthorn/actionview_precompiler. Using a parser finds dependencies that would otherwise be difficult to find with the regular expressions. It should also theoretically work with other template systems since it operates on the compiled template instead of the contents of the file. Co-authored-by: John Hawthorn <john@hawthorn.email>
188 lines
4.8 KiB
Ruby
188 lines
4.8 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require "action_view/ripper_ast_parser"
|
|
|
|
module ActionView
|
|
class RenderParser # :nodoc:
|
|
def initialize(name, code)
|
|
@name = name
|
|
@code = code
|
|
@parser = RipperASTParser
|
|
end
|
|
|
|
def render_calls
|
|
render_nodes = @parser.parse_render_nodes(@code)
|
|
|
|
render_nodes.map do |method, nodes|
|
|
nodes.map { |n| send(:parse_render, n) }
|
|
end.flatten.compact
|
|
end
|
|
|
|
private
|
|
def directory
|
|
File.dirname(@name)
|
|
end
|
|
|
|
def resolve_path_directory(path)
|
|
if path.include?("/")
|
|
path
|
|
else
|
|
"#{directory}/#{path}"
|
|
end
|
|
end
|
|
|
|
# Convert
|
|
# render("foo", ...)
|
|
# into either
|
|
# render(template: "foo", ...)
|
|
# or
|
|
# render(partial: "foo", ...)
|
|
def normalize_args(string, options_hash)
|
|
if options_hash
|
|
{ partial: string, locals: options_hash }
|
|
else
|
|
{ partial: string }
|
|
end
|
|
end
|
|
|
|
def parse_render(node)
|
|
node = node.argument_nodes
|
|
|
|
if (node.length == 1 || node.length == 2) && !node[0].hash?
|
|
if node.length == 1
|
|
options = normalize_args(node[0], nil)
|
|
elsif node.length == 2
|
|
options = normalize_args(node[0], node[1])
|
|
end
|
|
|
|
return nil unless options
|
|
|
|
parse_render_from_options(options)
|
|
elsif node.length == 1 && node[0].hash?
|
|
options = parse_hash_to_symbols(node[0])
|
|
|
|
return nil unless options
|
|
|
|
parse_render_from_options(options)
|
|
else
|
|
nil
|
|
end
|
|
end
|
|
|
|
def parse_hash(node)
|
|
node.hash? && node.to_hash
|
|
end
|
|
|
|
def parse_hash_to_symbols(node)
|
|
hash = parse_hash(node)
|
|
|
|
return unless hash
|
|
|
|
hash.transform_keys do |key_node|
|
|
key = parse_sym(key_node)
|
|
|
|
return unless key
|
|
|
|
key
|
|
end
|
|
end
|
|
|
|
ALL_KNOWN_KEYS = [:partial, :template, :layout, :formats, :locals, :object, :collection, :as, :status, :content_type, :location, :spacer_template]
|
|
|
|
RENDER_TYPE_KEYS =
|
|
[:partial, :template, :layout]
|
|
|
|
def parse_render_from_options(options_hash)
|
|
renders = []
|
|
keys = options_hash.keys
|
|
|
|
if (keys & RENDER_TYPE_KEYS).size < 1
|
|
# Must have at least one of render keys
|
|
return nil
|
|
end
|
|
|
|
if (keys - ALL_KNOWN_KEYS).any?
|
|
# de-opt in case of unknown option
|
|
return nil
|
|
end
|
|
|
|
render_type = (keys & RENDER_TYPE_KEYS)[0]
|
|
|
|
node = options_hash[render_type]
|
|
|
|
if node.string?
|
|
template = resolve_path_directory(node.to_string)
|
|
else
|
|
if node.variable_reference?
|
|
dependency = node.variable_name.sub(/\A(?:\$|@{1,2})/, "")
|
|
elsif node.vcall?
|
|
dependency = node.variable_name
|
|
elsif node.call?
|
|
dependency = node.call_method_name
|
|
else
|
|
return
|
|
end
|
|
|
|
object_template = true
|
|
template = "#{dependency.pluralize}/#{dependency.singularize}"
|
|
end
|
|
|
|
return unless template
|
|
|
|
if spacer_template = render_template_with_spacer?(options_hash)
|
|
virtual_path = partial_to_virtual_path(:partial, spacer_template)
|
|
renders << virtual_path
|
|
end
|
|
|
|
if options_hash.key?(:object) || options_hash.key?(:collection) || object_template
|
|
return nil if options_hash.key?(:object) && options_hash.key?(:collection)
|
|
return nil unless options_hash.key?(:partial)
|
|
end
|
|
|
|
virtual_path = partial_to_virtual_path(render_type, template)
|
|
renders << virtual_path
|
|
|
|
# Support for rendering multiple templates (i.e. a partial with a layout)
|
|
if layout_template = render_template_with_layout?(render_type, options_hash)
|
|
virtual_path = partial_to_virtual_path(:layout, layout_template)
|
|
|
|
renders << virtual_path
|
|
end
|
|
|
|
renders
|
|
end
|
|
|
|
def parse_str(node)
|
|
node.string? && node.to_string
|
|
end
|
|
|
|
def parse_sym(node)
|
|
node.symbol? && node.to_symbol
|
|
end
|
|
|
|
private
|
|
def render_template_with_layout?(render_type, options_hash)
|
|
if render_type != :layout && options_hash.key?(:layout)
|
|
parse_str(options_hash[:layout])
|
|
end
|
|
end
|
|
|
|
def render_template_with_spacer?(options_hash)
|
|
if options_hash.key?(:spacer_template)
|
|
parse_str(options_hash[:spacer_template])
|
|
end
|
|
end
|
|
|
|
def partial_to_virtual_path(render_type, partial_path)
|
|
if render_type == :partial || render_type == :layout
|
|
partial_path.gsub(%r{(/|^)([^/]*)\z}, '\1_\2')
|
|
else
|
|
partial_path
|
|
end
|
|
end
|
|
|
|
def layout_to_virtual_path(layout_path)
|
|
"layouts/#{layout_path}"
|
|
end
|
|
end
|
|
end
|