1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00
rails--rails/actionview/lib/action_view/render_parser.rb
HParker 897b9bf6e2 Replace dependency tracker with ripper based tracker
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>
2021-07-12 10:33:09 -07:00

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