actionview 7.0.8.1 → 7.2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +60 -425
- data/MIT-LICENSE +1 -1
- data/README.rdoc +1 -1
- data/app/assets/javascripts/rails-ujs.esm.js +686 -0
- data/app/assets/javascripts/rails-ujs.js +630 -0
- data/lib/action_view/base.rb +52 -14
- data/lib/action_view/buffers.rb +106 -8
- data/lib/action_view/cache_expiry.rb +44 -41
- data/lib/action_view/context.rb +1 -1
- data/lib/action_view/dependency_tracker/{ripper_tracker.rb → ruby_tracker.rb} +4 -3
- data/lib/action_view/dependency_tracker.rb +1 -1
- data/lib/action_view/deprecator.rb +7 -0
- data/lib/action_view/digestor.rb +1 -1
- data/lib/action_view/gem_version.rb +3 -3
- data/lib/action_view/helpers/active_model_helper.rb +1 -1
- data/lib/action_view/helpers/asset_tag_helper.rb +151 -55
- data/lib/action_view/helpers/asset_url_helper.rb +6 -5
- data/lib/action_view/helpers/atom_feed_helper.rb +5 -5
- data/lib/action_view/helpers/cache_helper.rb +7 -13
- data/lib/action_view/helpers/capture_helper.rb +30 -10
- data/lib/action_view/helpers/content_exfiltration_prevention_helper.rb +70 -0
- data/lib/action_view/helpers/controller_helper.rb +6 -0
- data/lib/action_view/helpers/csp_helper.rb +2 -2
- data/lib/action_view/helpers/csrf_helper.rb +3 -3
- data/lib/action_view/helpers/date_helper.rb +17 -19
- data/lib/action_view/helpers/debug_helper.rb +3 -3
- data/lib/action_view/helpers/form_helper.rb +248 -214
- data/lib/action_view/helpers/form_options_helper.rb +2 -1
- data/lib/action_view/helpers/form_tag_helper.rb +125 -58
- data/lib/action_view/helpers/javascript_helper.rb +1 -0
- data/lib/action_view/helpers/number_helper.rb +37 -330
- data/lib/action_view/helpers/output_safety_helper.rb +6 -6
- data/lib/action_view/helpers/rendering_helper.rb +1 -1
- data/lib/action_view/helpers/sanitize_helper.rb +51 -21
- data/lib/action_view/helpers/tag_helper.rb +210 -42
- data/lib/action_view/helpers/tags/base.rb +11 -52
- data/lib/action_view/helpers/tags/collection_check_boxes.rb +1 -0
- data/lib/action_view/helpers/tags/collection_radio_buttons.rb +1 -0
- data/lib/action_view/helpers/tags/collection_select.rb +3 -0
- data/lib/action_view/helpers/tags/date_field.rb +1 -1
- data/lib/action_view/helpers/tags/date_select.rb +2 -0
- data/lib/action_view/helpers/tags/datetime_field.rb +14 -6
- data/lib/action_view/helpers/tags/datetime_local_field.rb +11 -2
- data/lib/action_view/helpers/tags/grouped_collection_select.rb +3 -0
- data/lib/action_view/helpers/tags/month_field.rb +1 -1
- data/lib/action_view/helpers/tags/select.rb +3 -0
- data/lib/action_view/helpers/tags/select_renderer.rb +56 -0
- data/lib/action_view/helpers/tags/time_field.rb +1 -1
- data/lib/action_view/helpers/tags/time_zone_select.rb +3 -0
- data/lib/action_view/helpers/tags/week_field.rb +1 -1
- data/lib/action_view/helpers/tags/weekday_select.rb +3 -0
- data/lib/action_view/helpers/tags.rb +2 -0
- data/lib/action_view/helpers/text_helper.rb +157 -85
- data/lib/action_view/helpers/translation_helper.rb +3 -3
- data/lib/action_view/helpers/url_helper.rb +35 -80
- data/lib/action_view/helpers.rb +2 -0
- data/lib/action_view/layouts.rb +8 -8
- data/lib/action_view/log_subscriber.rb +57 -36
- data/lib/action_view/lookup_context.rb +29 -13
- data/lib/action_view/path_registry.rb +57 -0
- data/lib/action_view/path_set.rb +13 -14
- data/lib/action_view/railtie.rb +25 -3
- data/lib/action_view/record_identifier.rb +15 -8
- data/lib/action_view/render_parser/prism_render_parser.rb +127 -0
- data/lib/action_view/render_parser/ripper_render_parser.rb +341 -0
- data/lib/action_view/render_parser.rb +21 -169
- data/lib/action_view/renderer/abstract_renderer.rb +2 -2
- data/lib/action_view/renderer/collection_renderer.rb +10 -2
- data/lib/action_view/renderer/partial_renderer/collection_caching.rb +2 -1
- data/lib/action_view/renderer/partial_renderer.rb +2 -1
- data/lib/action_view/renderer/renderer.rb +34 -38
- data/lib/action_view/renderer/streaming_template_renderer.rb +3 -2
- data/lib/action_view/renderer/template_renderer.rb +3 -2
- data/lib/action_view/rendering.rb +26 -8
- data/lib/action_view/template/error.rb +14 -1
- data/lib/action_view/template/handlers/builder.rb +4 -4
- data/lib/action_view/template/handlers/erb/erubi.rb +23 -27
- data/lib/action_view/template/handlers/erb.rb +73 -1
- data/lib/action_view/template/handlers.rb +1 -1
- data/lib/action_view/template/html.rb +1 -1
- data/lib/action_view/template/raw_file.rb +1 -1
- data/lib/action_view/template/renderable.rb +8 -2
- data/lib/action_view/template/resolver.rb +9 -3
- data/lib/action_view/template/text.rb +1 -1
- data/lib/action_view/template/types.rb +25 -34
- data/lib/action_view/template.rb +278 -55
- data/lib/action_view/template_path.rb +2 -0
- data/lib/action_view/test_case.rb +181 -28
- data/lib/action_view/unbound_template.rb +17 -7
- data/lib/action_view/version.rb +1 -1
- data/lib/action_view/view_paths.rb +15 -24
- data/lib/action_view.rb +4 -1
- metadata +31 -31
- data/lib/action_view/ripper_ast_parser.rb +0 -198
- data/lib/assets/compiled/rails-ujs.js +0 -777
data/lib/action_view/railtie.rb
CHANGED
@@ -13,6 +13,7 @@ module ActionView
|
|
13
13
|
config.action_view.image_loading = nil
|
14
14
|
config.action_view.image_decoding = nil
|
15
15
|
config.action_view.apply_stylesheet_media_default = true
|
16
|
+
config.action_view.prepend_content_exfiltration_prevention = false
|
16
17
|
|
17
18
|
config.eager_load_namespaces << ActionView
|
18
19
|
|
@@ -40,6 +41,17 @@ module ActionView
|
|
40
41
|
end
|
41
42
|
end
|
42
43
|
|
44
|
+
config.after_initialize do |app|
|
45
|
+
prepend_content_exfiltration_prevention = app.config.action_view.delete(:prepend_content_exfiltration_prevention)
|
46
|
+
ActionView::Helpers::ContentExfiltrationPreventionHelper.prepend_content_exfiltration_prevention = prepend_content_exfiltration_prevention
|
47
|
+
end
|
48
|
+
|
49
|
+
config.after_initialize do |app|
|
50
|
+
if klass = app.config.action_view.delete(:sanitizer_vendor)
|
51
|
+
ActionView::Helpers::SanitizeHelper.sanitizer_vendor = klass
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
43
55
|
config.after_initialize do |app|
|
44
56
|
button_to_generates_button_tag = app.config.action_view.delete(:button_to_generates_button_tag)
|
45
57
|
unless button_to_generates_button_tag.nil?
|
@@ -67,6 +79,10 @@ module ActionView
|
|
67
79
|
end
|
68
80
|
end
|
69
81
|
|
82
|
+
initializer "action_view.deprecator", before: :load_environment_config do |app|
|
83
|
+
app.deprecators[:action_view] = ActionView.deprecator
|
84
|
+
end
|
85
|
+
|
70
86
|
initializer "action_view.logger" do
|
71
87
|
ActiveSupport.on_load(:action_view) { self.logger ||= Rails.logger }
|
72
88
|
end
|
@@ -74,7 +90,7 @@ module ActionView
|
|
74
90
|
initializer "action_view.caching" do |app|
|
75
91
|
ActiveSupport.on_load(:action_view) do
|
76
92
|
if app.config.action_view.cache_template_loading.nil?
|
77
|
-
ActionView::Resolver.caching = app.config.
|
93
|
+
ActionView::Resolver.caching = !app.config.reloading_enabled?
|
78
94
|
end
|
79
95
|
end
|
80
96
|
end
|
@@ -91,13 +107,19 @@ module ActionView
|
|
91
107
|
|
92
108
|
config.after_initialize do |app|
|
93
109
|
enable_caching = if app.config.action_view.cache_template_loading.nil?
|
94
|
-
app.config.
|
110
|
+
!app.config.reloading_enabled?
|
95
111
|
else
|
96
112
|
app.config.action_view.cache_template_loading
|
97
113
|
end
|
98
114
|
|
99
115
|
unless enable_caching
|
100
|
-
|
116
|
+
view_reloader = ActionView::CacheExpiry::ViewReloader.new(watcher: app.config.file_watcher)
|
117
|
+
|
118
|
+
app.reloaders << view_reloader
|
119
|
+
app.reloader.to_run do
|
120
|
+
require_unload_lock!
|
121
|
+
view_reloader.execute
|
122
|
+
end
|
101
123
|
end
|
102
124
|
end
|
103
125
|
|
@@ -4,6 +4,8 @@ require "active_support/core_ext/module"
|
|
4
4
|
require "action_view/model_naming"
|
5
5
|
|
6
6
|
module ActionView
|
7
|
+
# = Action View \Record \Identifier
|
8
|
+
#
|
7
9
|
# RecordIdentifier encapsulates methods used by various ActionView helpers
|
8
10
|
# to associate records with DOM elements.
|
9
11
|
#
|
@@ -31,6 +33,8 @@ module ActionView
|
|
31
33
|
# automatically generated, following naming conventions encapsulated by the
|
32
34
|
# RecordIdentifier methods #dom_id and #dom_class:
|
33
35
|
#
|
36
|
+
# dom_id(Post) # => "new_post"
|
37
|
+
# dom_class(Post) # => "post"
|
34
38
|
# dom_id(Post.new) # => "new_post"
|
35
39
|
# dom_class(Post.new) # => "post"
|
36
40
|
# dom_id(Post.find 42) # => "post_42"
|
@@ -79,18 +83,21 @@ module ActionView
|
|
79
83
|
# The DOM id convention is to use the singular form of an object or class with the id following an underscore.
|
80
84
|
# If no id is found, prefix with "new_" instead.
|
81
85
|
#
|
82
|
-
# dom_id(Post.find(45))
|
83
|
-
# dom_id(Post
|
86
|
+
# dom_id(Post.find(45)) # => "post_45"
|
87
|
+
# dom_id(Post) # => "new_post"
|
84
88
|
#
|
85
89
|
# If you need to address multiple instances of the same class in the same view, you can prefix the dom_id:
|
86
90
|
#
|
87
91
|
# dom_id(Post.find(45), :edit) # => "edit_post_45"
|
88
|
-
# dom_id(Post
|
89
|
-
def dom_id(
|
90
|
-
|
91
|
-
|
92
|
+
# dom_id(Post, :custom) # => "custom_post"
|
93
|
+
def dom_id(record_or_class, prefix = nil)
|
94
|
+
raise ArgumentError, "dom_id must be passed a record_or_class as the first argument, you passed #{record_or_class.inspect}" unless record_or_class
|
95
|
+
|
96
|
+
record_id = record_key_for_dom_id(record_or_class) unless record_or_class.is_a?(Class)
|
97
|
+
if record_id
|
98
|
+
"#{dom_class(record_or_class, prefix)}#{JOIN}#{record_id}"
|
92
99
|
else
|
93
|
-
dom_class(
|
100
|
+
dom_class(record_or_class, prefix || NEW)
|
94
101
|
end
|
95
102
|
end
|
96
103
|
|
@@ -105,7 +112,7 @@ module ActionView
|
|
105
112
|
# make sure yourself that your dom ids are valid, in case you override this method.
|
106
113
|
def record_key_for_dom_id(record) # :doc:
|
107
114
|
key = convert_to_model(record).to_key
|
108
|
-
key ? key.join(JOIN) :
|
115
|
+
key && key.all? ? key.join(JOIN) : nil
|
109
116
|
end
|
110
117
|
end
|
111
118
|
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionView
|
4
|
+
module RenderParser
|
5
|
+
class PrismRenderParser < Base # :nodoc:
|
6
|
+
def render_calls
|
7
|
+
queue = [Prism.parse(@code).value]
|
8
|
+
templates = []
|
9
|
+
|
10
|
+
while (node = queue.shift)
|
11
|
+
queue.concat(node.compact_child_nodes)
|
12
|
+
next unless node.is_a?(Prism::CallNode)
|
13
|
+
|
14
|
+
options = render_call_options(node)
|
15
|
+
next unless options
|
16
|
+
|
17
|
+
render_type = (options.keys & RENDER_TYPE_KEYS)[0]
|
18
|
+
template, object_template = render_call_template(options[render_type])
|
19
|
+
next unless template
|
20
|
+
|
21
|
+
if options.key?(:object) || options.key?(:collection) || object_template
|
22
|
+
next if options.key?(:object) && options.key?(:collection)
|
23
|
+
next unless options.key?(:partial)
|
24
|
+
end
|
25
|
+
|
26
|
+
if options[:spacer_template].is_a?(Prism::StringNode)
|
27
|
+
templates << partial_to_virtual_path(:partial, options[:spacer_template].unescaped)
|
28
|
+
end
|
29
|
+
|
30
|
+
templates << partial_to_virtual_path(render_type, template)
|
31
|
+
|
32
|
+
if render_type != :layout && options[:layout].is_a?(Prism::StringNode)
|
33
|
+
templates << partial_to_virtual_path(:layout, options[:layout].unescaped)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
templates
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
# Accept a call node and return a hash of options for the render call.
|
42
|
+
# If it doesn't match the expected format, return nil.
|
43
|
+
def render_call_options(node)
|
44
|
+
# We are only looking for calls to render or render_to_string.
|
45
|
+
name = node.name.to_sym
|
46
|
+
return if name != :render && name != :render_to_string
|
47
|
+
|
48
|
+
# We are only looking for calls with arguments.
|
49
|
+
arguments = node.arguments
|
50
|
+
return unless arguments
|
51
|
+
|
52
|
+
arguments = arguments.arguments
|
53
|
+
length = arguments.length
|
54
|
+
|
55
|
+
# Get rid of any parentheses to get directly to the contents.
|
56
|
+
arguments.map! do |argument|
|
57
|
+
current = argument
|
58
|
+
|
59
|
+
while current.is_a?(Prism::ParenthesesNode) &&
|
60
|
+
current.body.is_a?(Prism::StatementsNode) &&
|
61
|
+
current.body.body.length == 1
|
62
|
+
current = current.body.body.first
|
63
|
+
end
|
64
|
+
|
65
|
+
current
|
66
|
+
end
|
67
|
+
|
68
|
+
# We are only looking for arguments that are either a string with an
|
69
|
+
# array of locals or a keyword hash with symbol keys.
|
70
|
+
options =
|
71
|
+
if (length == 1 || length == 2) && !arguments[0].is_a?(Prism::KeywordHashNode)
|
72
|
+
{ partial: arguments[0], locals: arguments[1] }
|
73
|
+
elsif length == 1 &&
|
74
|
+
arguments[0].is_a?(Prism::KeywordHashNode) &&
|
75
|
+
arguments[0].elements.all? do |element|
|
76
|
+
element.is_a?(Prism::AssocNode) && element.key.is_a?(Prism::SymbolNode)
|
77
|
+
end
|
78
|
+
arguments[0].elements.to_h do |element|
|
79
|
+
[element.key.unescaped.to_sym, element.value]
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
return unless options
|
84
|
+
|
85
|
+
# Here we validate that the options have the keys we expect.
|
86
|
+
keys = options.keys
|
87
|
+
return if !keys.intersect?(RENDER_TYPE_KEYS)
|
88
|
+
return if (keys - ALL_KNOWN_KEYS).any?
|
89
|
+
|
90
|
+
# Finally, we can return a valid set of options.
|
91
|
+
options
|
92
|
+
end
|
93
|
+
|
94
|
+
# Accept the node that is being passed in the position of the template
|
95
|
+
# and return the template name and whether or not it is an object
|
96
|
+
# template.
|
97
|
+
def render_call_template(node)
|
98
|
+
object_template = false
|
99
|
+
template =
|
100
|
+
if node.is_a?(Prism::StringNode)
|
101
|
+
path = node.unescaped
|
102
|
+
path.include?("/") ? path : "#{directory}/#{path}"
|
103
|
+
else
|
104
|
+
dependency =
|
105
|
+
case node.type
|
106
|
+
when :class_variable_read_node
|
107
|
+
node.slice[2..]
|
108
|
+
when :instance_variable_read_node
|
109
|
+
node.slice[1..]
|
110
|
+
when :global_variable_read_node
|
111
|
+
node.slice[1..]
|
112
|
+
when :local_variable_read_node
|
113
|
+
node.slice
|
114
|
+
when :call_node
|
115
|
+
node.name.to_s
|
116
|
+
else
|
117
|
+
return
|
118
|
+
end
|
119
|
+
|
120
|
+
"#{dependency.pluralize}/#{dependency.singularize}"
|
121
|
+
end
|
122
|
+
|
123
|
+
[template, object_template]
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,341 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionView
|
4
|
+
module RenderParser
|
5
|
+
class RipperRenderParser < Base # :nodoc:
|
6
|
+
class Node < ::Array # :nodoc:
|
7
|
+
attr_reader :type
|
8
|
+
|
9
|
+
def initialize(type, arr, opts = {})
|
10
|
+
@type = type
|
11
|
+
super(arr)
|
12
|
+
end
|
13
|
+
|
14
|
+
def children
|
15
|
+
to_a
|
16
|
+
end
|
17
|
+
|
18
|
+
def inspect
|
19
|
+
typeinfo = type && type != :list ? ":" + type.to_s + ", " : ""
|
20
|
+
"s(" + typeinfo + map(&:inspect).join(", ") + ")"
|
21
|
+
end
|
22
|
+
|
23
|
+
def fcall?
|
24
|
+
type == :command || type == :fcall
|
25
|
+
end
|
26
|
+
|
27
|
+
def fcall_named?(name)
|
28
|
+
fcall? &&
|
29
|
+
self[0].type == :@ident &&
|
30
|
+
self[0][0] == name
|
31
|
+
end
|
32
|
+
|
33
|
+
def argument_nodes
|
34
|
+
raise unless fcall?
|
35
|
+
return [] if self[1].nil?
|
36
|
+
if self[1].last == false || self[1].last.type == :vcall
|
37
|
+
self[1][0...-1]
|
38
|
+
else
|
39
|
+
self[1][0..-1]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def string?
|
44
|
+
type == :string_literal
|
45
|
+
end
|
46
|
+
|
47
|
+
def variable_reference?
|
48
|
+
type == :var_ref
|
49
|
+
end
|
50
|
+
|
51
|
+
def vcall?
|
52
|
+
type == :vcall
|
53
|
+
end
|
54
|
+
|
55
|
+
def call?
|
56
|
+
type == :call
|
57
|
+
end
|
58
|
+
|
59
|
+
def variable_name
|
60
|
+
self[0][0]
|
61
|
+
end
|
62
|
+
|
63
|
+
def call_method_name
|
64
|
+
self[2].first
|
65
|
+
end
|
66
|
+
|
67
|
+
def to_string
|
68
|
+
raise unless string?
|
69
|
+
self[0][0][0]
|
70
|
+
end
|
71
|
+
|
72
|
+
def hash?
|
73
|
+
type == :bare_assoc_hash || type == :hash
|
74
|
+
end
|
75
|
+
|
76
|
+
def to_hash
|
77
|
+
if type == :bare_assoc_hash
|
78
|
+
hash_from_body(self[0])
|
79
|
+
elsif type == :hash && self[0] == nil
|
80
|
+
{}
|
81
|
+
elsif type == :hash && self[0].type == :assoclist_from_args
|
82
|
+
hash_from_body(self[0][0])
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def hash_from_body(body)
|
87
|
+
body.to_h do |hash_node|
|
88
|
+
return nil if hash_node.type != :assoc_new
|
89
|
+
|
90
|
+
[hash_node[0], hash_node[1]]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def symbol?
|
95
|
+
type == :@label || type == :symbol_literal
|
96
|
+
end
|
97
|
+
|
98
|
+
def to_symbol
|
99
|
+
if type == :@label && self[0] =~ /\A(.+):\z/
|
100
|
+
$1.to_sym
|
101
|
+
elsif type == :symbol_literal && self[0].type == :symbol && self[0][0].type == :@ident
|
102
|
+
self[0][0][0].to_sym
|
103
|
+
else
|
104
|
+
raise "not a symbol?: #{self.inspect}"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
class NodeParser < ::Ripper # :nodoc:
|
110
|
+
PARSER_EVENTS.each do |event|
|
111
|
+
arity = PARSER_EVENT_TABLE[event]
|
112
|
+
if arity == 0 && event.to_s.end_with?("_new")
|
113
|
+
module_eval(<<-eof, __FILE__, __LINE__ + 1)
|
114
|
+
def on_#{event}(*args)
|
115
|
+
Node.new(:list, args, lineno: lineno(), column: column())
|
116
|
+
end
|
117
|
+
eof
|
118
|
+
elsif event.to_s.match?(/_add(_.+)?\z/)
|
119
|
+
module_eval(<<-eof, __FILE__, __LINE__ + 1)
|
120
|
+
begin; undef on_#{event}; rescue NameError; end
|
121
|
+
def on_#{event}(list, item)
|
122
|
+
list.push(item)
|
123
|
+
list
|
124
|
+
end
|
125
|
+
eof
|
126
|
+
else
|
127
|
+
module_eval(<<-eof, __FILE__, __LINE__ + 1)
|
128
|
+
begin; undef on_#{event}; rescue NameError; end
|
129
|
+
def on_#{event}(*args)
|
130
|
+
Node.new(:#{event}, args, lineno: lineno(), column: column())
|
131
|
+
end
|
132
|
+
eof
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
SCANNER_EVENTS.each do |event|
|
137
|
+
module_eval(<<-End, __FILE__, __LINE__ + 1)
|
138
|
+
def on_#{event}(tok)
|
139
|
+
Node.new(:@#{event}, [tok], lineno: lineno(), column: column())
|
140
|
+
end
|
141
|
+
End
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
class RenderCallExtractor < NodeParser # :nodoc:
|
146
|
+
attr_reader :render_calls
|
147
|
+
|
148
|
+
METHODS_TO_PARSE = %w(render render_to_string)
|
149
|
+
|
150
|
+
def initialize(*args)
|
151
|
+
super
|
152
|
+
|
153
|
+
@render_calls = []
|
154
|
+
end
|
155
|
+
|
156
|
+
private
|
157
|
+
def on_fcall(name, *args)
|
158
|
+
on_render_call(super)
|
159
|
+
end
|
160
|
+
|
161
|
+
def on_command(name, *args)
|
162
|
+
on_render_call(super)
|
163
|
+
end
|
164
|
+
|
165
|
+
def on_render_call(node)
|
166
|
+
METHODS_TO_PARSE.each do |method|
|
167
|
+
if node.fcall_named?(method)
|
168
|
+
@render_calls << [method, node]
|
169
|
+
return node
|
170
|
+
end
|
171
|
+
end
|
172
|
+
node
|
173
|
+
end
|
174
|
+
|
175
|
+
def on_arg_paren(content)
|
176
|
+
content
|
177
|
+
end
|
178
|
+
|
179
|
+
def on_paren(content)
|
180
|
+
content.size == 1 ? content.first : content
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def render_calls
|
185
|
+
parser = RenderCallExtractor.new(@code)
|
186
|
+
parser.parse
|
187
|
+
|
188
|
+
parser.render_calls.group_by(&:first).to_h do |method, nodes|
|
189
|
+
[ method.to_sym, nodes.collect { |v| v[1] } ]
|
190
|
+
end.map do |method, nodes|
|
191
|
+
nodes.map { |n| parse_render(n) }
|
192
|
+
end.flatten.compact
|
193
|
+
end
|
194
|
+
|
195
|
+
private
|
196
|
+
def resolve_path_directory(path)
|
197
|
+
if path.include?("/")
|
198
|
+
path
|
199
|
+
else
|
200
|
+
"#{directory}/#{path}"
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
# Convert
|
205
|
+
# render("foo", ...)
|
206
|
+
# into either
|
207
|
+
# render(template: "foo", ...)
|
208
|
+
# or
|
209
|
+
# render(partial: "foo", ...)
|
210
|
+
def normalize_args(string, options_hash)
|
211
|
+
if options_hash
|
212
|
+
{ partial: string, locals: options_hash }
|
213
|
+
else
|
214
|
+
{ partial: string }
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def parse_render(node)
|
219
|
+
node = node.argument_nodes
|
220
|
+
|
221
|
+
if (node.length == 1 || node.length == 2) && !node[0].hash?
|
222
|
+
if node.length == 1
|
223
|
+
options = normalize_args(node[0], nil)
|
224
|
+
elsif node.length == 2
|
225
|
+
options = normalize_args(node[0], node[1])
|
226
|
+
end
|
227
|
+
|
228
|
+
return nil unless options
|
229
|
+
|
230
|
+
parse_render_from_options(options)
|
231
|
+
elsif node.length == 1 && node[0].hash?
|
232
|
+
options = parse_hash_to_symbols(node[0])
|
233
|
+
|
234
|
+
return nil unless options
|
235
|
+
|
236
|
+
parse_render_from_options(options)
|
237
|
+
else
|
238
|
+
nil
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
def parse_hash(node)
|
243
|
+
node.hash? && node.to_hash
|
244
|
+
end
|
245
|
+
|
246
|
+
def parse_hash_to_symbols(node)
|
247
|
+
hash = parse_hash(node)
|
248
|
+
|
249
|
+
return unless hash
|
250
|
+
|
251
|
+
hash.transform_keys do |key_node|
|
252
|
+
key = parse_sym(key_node)
|
253
|
+
|
254
|
+
return unless key
|
255
|
+
|
256
|
+
key
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
def parse_render_from_options(options_hash)
|
261
|
+
renders = []
|
262
|
+
keys = options_hash.keys
|
263
|
+
|
264
|
+
if (keys & RENDER_TYPE_KEYS).size < 1
|
265
|
+
# Must have at least one of render keys
|
266
|
+
return nil
|
267
|
+
end
|
268
|
+
|
269
|
+
if (keys - ALL_KNOWN_KEYS).any?
|
270
|
+
# de-opt in case of unknown option
|
271
|
+
return nil
|
272
|
+
end
|
273
|
+
|
274
|
+
render_type = (keys & RENDER_TYPE_KEYS)[0]
|
275
|
+
|
276
|
+
node = options_hash[render_type]
|
277
|
+
|
278
|
+
if node.string?
|
279
|
+
template = resolve_path_directory(node.to_string)
|
280
|
+
else
|
281
|
+
if node.variable_reference?
|
282
|
+
dependency = node.variable_name.sub(/\A(?:\$|@{1,2})/, "")
|
283
|
+
elsif node.vcall?
|
284
|
+
dependency = node.variable_name
|
285
|
+
elsif node.call?
|
286
|
+
dependency = node.call_method_name
|
287
|
+
else
|
288
|
+
return
|
289
|
+
end
|
290
|
+
|
291
|
+
object_template = true
|
292
|
+
template = "#{dependency.pluralize}/#{dependency.singularize}"
|
293
|
+
end
|
294
|
+
|
295
|
+
return unless template
|
296
|
+
|
297
|
+
if spacer_template = render_template_with_spacer?(options_hash)
|
298
|
+
virtual_path = partial_to_virtual_path(:partial, spacer_template)
|
299
|
+
renders << virtual_path
|
300
|
+
end
|
301
|
+
|
302
|
+
if options_hash.key?(:object) || options_hash.key?(:collection) || object_template
|
303
|
+
return nil if options_hash.key?(:object) && options_hash.key?(:collection)
|
304
|
+
return nil unless options_hash.key?(:partial)
|
305
|
+
end
|
306
|
+
|
307
|
+
virtual_path = partial_to_virtual_path(render_type, template)
|
308
|
+
renders << virtual_path
|
309
|
+
|
310
|
+
# Support for rendering multiple templates (i.e. a partial with a layout)
|
311
|
+
if layout_template = render_template_with_layout?(render_type, options_hash)
|
312
|
+
virtual_path = partial_to_virtual_path(:layout, layout_template)
|
313
|
+
|
314
|
+
renders << virtual_path
|
315
|
+
end
|
316
|
+
|
317
|
+
renders
|
318
|
+
end
|
319
|
+
|
320
|
+
def parse_str(node)
|
321
|
+
node.string? && node.to_string
|
322
|
+
end
|
323
|
+
|
324
|
+
def parse_sym(node)
|
325
|
+
node.symbol? && node.to_symbol
|
326
|
+
end
|
327
|
+
|
328
|
+
def render_template_with_layout?(render_type, options_hash)
|
329
|
+
if render_type != :layout && options_hash.key?(:layout)
|
330
|
+
parse_str(options_hash[:layout])
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
def render_template_with_spacer?(options_hash)
|
335
|
+
if options_hash.key?(:spacer_template)
|
336
|
+
parse_str(options_hash[:spacer_template])
|
337
|
+
end
|
338
|
+
end
|
339
|
+
end
|
340
|
+
end
|
341
|
+
end
|