actionview 6.1.7.10 → 7.0.0.alpha1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of actionview might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +89 -409
- data/MIT-LICENSE +2 -1
- data/lib/action_view/base.rb +3 -3
- data/lib/action_view/buffers.rb +2 -2
- data/lib/action_view/cache_expiry.rb +46 -32
- data/lib/action_view/dependency_tracker/erb_tracker.rb +154 -0
- data/lib/action_view/dependency_tracker/ripper_tracker.rb +59 -0
- data/lib/action_view/dependency_tracker.rb +6 -147
- data/lib/action_view/digestor.rb +7 -4
- data/lib/action_view/flows.rb +4 -4
- data/lib/action_view/gem_version.rb +4 -4
- data/lib/action_view/helpers/active_model_helper.rb +1 -1
- data/lib/action_view/helpers/asset_tag_helper.rb +84 -29
- data/lib/action_view/helpers/asset_url_helper.rb +7 -7
- data/lib/action_view/helpers/atom_feed_helper.rb +3 -4
- data/lib/action_view/helpers/cache_helper.rb +51 -3
- data/lib/action_view/helpers/capture_helper.rb +2 -2
- data/lib/action_view/helpers/controller_helper.rb +2 -2
- data/lib/action_view/helpers/csp_helper.rb +1 -1
- data/lib/action_view/helpers/csrf_helper.rb +1 -1
- data/lib/action_view/helpers/date_helper.rb +6 -7
- data/lib/action_view/helpers/debug_helper.rb +3 -1
- data/lib/action_view/helpers/form_helper.rb +72 -12
- data/lib/action_view/helpers/form_options_helper.rb +65 -33
- data/lib/action_view/helpers/form_tag_helper.rb +75 -32
- data/lib/action_view/helpers/javascript_helper.rb +3 -5
- data/lib/action_view/helpers/number_helper.rb +3 -4
- data/lib/action_view/helpers/output_safety_helper.rb +2 -2
- data/lib/action_view/helpers/rendering_helper.rb +1 -1
- data/lib/action_view/helpers/sanitize_helper.rb +2 -2
- data/lib/action_view/helpers/tag_helper.rb +25 -44
- data/lib/action_view/helpers/tags/base.rb +3 -15
- data/lib/action_view/helpers/tags/check_box.rb +2 -2
- data/lib/action_view/helpers/tags/collection_select.rb +1 -1
- data/lib/action_view/helpers/tags/hidden_field.rb +0 -4
- data/lib/action_view/helpers/tags/time_field.rb +10 -1
- data/lib/action_view/helpers/tags/weekday_select.rb +27 -0
- data/lib/action_view/helpers/tags.rb +3 -2
- data/lib/action_view/helpers/text_helper.rb +24 -13
- data/lib/action_view/helpers/translation_helper.rb +1 -2
- data/lib/action_view/helpers/url_helper.rb +102 -77
- data/lib/action_view/helpers.rb +25 -25
- data/lib/action_view/lookup_context.rb +33 -52
- data/lib/action_view/model_naming.rb +1 -1
- data/lib/action_view/path_set.rb +16 -22
- data/lib/action_view/railtie.rb +14 -1
- data/lib/action_view/render_parser.rb +188 -0
- data/lib/action_view/renderer/abstract_renderer.rb +2 -2
- data/lib/action_view/renderer/partial_renderer.rb +0 -34
- data/lib/action_view/renderer/renderer.rb +4 -4
- data/lib/action_view/renderer/streaming_template_renderer.rb +3 -3
- data/lib/action_view/renderer/template_renderer.rb +6 -2
- data/lib/action_view/rendering.rb +2 -2
- data/lib/action_view/ripper_ast_parser.rb +198 -0
- data/lib/action_view/routing_url_for.rb +1 -1
- data/lib/action_view/template/error.rb +108 -13
- data/lib/action_view/template/handlers/erb.rb +6 -0
- data/lib/action_view/template/handlers.rb +3 -3
- data/lib/action_view/template/html.rb +3 -3
- data/lib/action_view/template/inline.rb +3 -3
- data/lib/action_view/template/raw_file.rb +3 -3
- data/lib/action_view/template/resolver.rb +84 -311
- data/lib/action_view/template/text.rb +3 -3
- data/lib/action_view/template/types.rb +14 -12
- data/lib/action_view/template.rb +10 -1
- data/lib/action_view/template_details.rb +66 -0
- data/lib/action_view/template_path.rb +64 -0
- data/lib/action_view/test_case.rb +6 -2
- data/lib/action_view/testing/resolvers.rb +11 -12
- data/lib/action_view/unbound_template.rb +33 -7
- data/lib/action_view.rb +3 -4
- data/lib/assets/compiled/rails-ujs.js +5 -36
- metadata +22 -16
data/lib/action_view/path_set.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
module ActionView
|
3
|
+
module ActionView # :nodoc:
|
4
4
|
# = Action View PathSet
|
5
5
|
#
|
6
6
|
# This class is used to store and access paths in Action View. A number of
|
@@ -8,7 +8,7 @@ module ActionView #:nodoc:
|
|
8
8
|
# set and also perform operations on other +PathSet+ objects.
|
9
9
|
#
|
10
10
|
# A +LookupContext+ will use a +PathSet+ to store the paths in its context.
|
11
|
-
class PathSet
|
11
|
+
class PathSet # :nodoc:
|
12
12
|
include Enumerable
|
13
13
|
|
14
14
|
attr_reader :paths
|
@@ -44,44 +44,38 @@ module ActionView #:nodoc:
|
|
44
44
|
METHOD
|
45
45
|
end
|
46
46
|
|
47
|
-
def find(
|
48
|
-
find_all(
|
47
|
+
def find(path, prefixes, partial, details, details_key, locals)
|
48
|
+
find_all(path, prefixes, partial, details, details_key, locals).first ||
|
49
|
+
raise(MissingTemplate.new(self, path, prefixes, partial, details, details_key, locals))
|
49
50
|
end
|
50
51
|
|
51
|
-
def find_all(path, prefixes
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
def exists?(path, prefixes, *args)
|
56
|
-
find_all(path, prefixes, *args).any?
|
57
|
-
end
|
58
|
-
|
59
|
-
def find_all_with_query(query) # :nodoc:
|
60
|
-
paths.each do |resolver|
|
61
|
-
templates = resolver.find_all_with_query(query)
|
52
|
+
def find_all(path, prefixes, partial, details, details_key, locals)
|
53
|
+
search_combinations(prefixes) do |resolver, prefix|
|
54
|
+
templates = resolver.find_all(path, prefix, partial, details, details_key, locals)
|
62
55
|
return templates unless templates.empty?
|
63
56
|
end
|
64
|
-
|
65
57
|
[]
|
66
58
|
end
|
67
59
|
|
60
|
+
def exists?(path, prefixes, partial, details, details_key, locals)
|
61
|
+
find_all(path, prefixes, partial, details, details_key, locals).any?
|
62
|
+
end
|
63
|
+
|
68
64
|
private
|
69
|
-
def
|
70
|
-
prefixes =
|
65
|
+
def search_combinations(prefixes)
|
66
|
+
prefixes = Array(prefixes)
|
71
67
|
prefixes.each do |prefix|
|
72
68
|
paths.each do |resolver|
|
73
|
-
|
74
|
-
return templates unless templates.empty?
|
69
|
+
yield resolver, prefix
|
75
70
|
end
|
76
71
|
end
|
77
|
-
[]
|
78
72
|
end
|
79
73
|
|
80
74
|
def typecast(paths)
|
81
75
|
paths.map do |path|
|
82
76
|
case path
|
83
77
|
when Pathname, String
|
84
|
-
|
78
|
+
FileSystemResolver.new path.to_s
|
85
79
|
else
|
86
80
|
path
|
87
81
|
end
|
data/lib/action_view/railtie.rb
CHANGED
@@ -10,6 +10,9 @@ module ActionView
|
|
10
10
|
config.action_view.embed_authenticity_token_in_remote_forms = nil
|
11
11
|
config.action_view.debug_missing_translation = true
|
12
12
|
config.action_view.default_enforce_utf8 = nil
|
13
|
+
config.action_view.image_loading = nil
|
14
|
+
config.action_view.image_decoding = nil
|
15
|
+
config.action_view.apply_stylesheet_media_default = true
|
13
16
|
|
14
17
|
config.eager_load_namespaces << ActionView
|
15
18
|
|
@@ -38,7 +41,17 @@ module ActionView
|
|
38
41
|
end
|
39
42
|
|
40
43
|
config.after_initialize do |app|
|
44
|
+
button_to_generates_button_tag = app.config.action_view.delete(:button_to_generates_button_tag)
|
45
|
+
unless button_to_generates_button_tag.nil?
|
46
|
+
ActionView::Helpers::UrlHelper.button_to_generates_button_tag = button_to_generates_button_tag
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
config.after_initialize do |app|
|
51
|
+
ActionView::Helpers::AssetTagHelper.image_loading = app.config.action_view.delete(:image_loading)
|
52
|
+
ActionView::Helpers::AssetTagHelper.image_decoding = app.config.action_view.delete(:image_decoding)
|
41
53
|
ActionView::Helpers::AssetTagHelper.preload_links_header = app.config.action_view.delete(:preload_links_header)
|
54
|
+
ActionView::Helpers::AssetTagHelper.apply_stylesheet_media_default = app.config.action_view.delete(:apply_stylesheet_media_default)
|
42
55
|
end
|
43
56
|
|
44
57
|
config.after_initialize do |app|
|
@@ -85,7 +98,7 @@ module ActionView
|
|
85
98
|
end
|
86
99
|
|
87
100
|
unless enable_caching
|
88
|
-
app.executor.
|
101
|
+
app.executor.register_hook ActionView::CacheExpiry::Executor.new(watcher: app.config.file_watcher)
|
89
102
|
end
|
90
103
|
end
|
91
104
|
|
@@ -0,0 +1,188 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "action_view/ripper_ast_parser"
|
4
|
+
|
5
|
+
module ActionView
|
6
|
+
class RenderParser # :nodoc:
|
7
|
+
def initialize(name, code)
|
8
|
+
@name = name
|
9
|
+
@code = code
|
10
|
+
@parser = RipperASTParser
|
11
|
+
end
|
12
|
+
|
13
|
+
def render_calls
|
14
|
+
render_nodes = @parser.parse_render_nodes(@code)
|
15
|
+
|
16
|
+
render_nodes.map do |method, nodes|
|
17
|
+
nodes.map { |n| send(:parse_render, n) }
|
18
|
+
end.flatten.compact
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
def directory
|
23
|
+
File.dirname(@name)
|
24
|
+
end
|
25
|
+
|
26
|
+
def resolve_path_directory(path)
|
27
|
+
if path.include?("/")
|
28
|
+
path
|
29
|
+
else
|
30
|
+
"#{directory}/#{path}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Convert
|
35
|
+
# render("foo", ...)
|
36
|
+
# into either
|
37
|
+
# render(template: "foo", ...)
|
38
|
+
# or
|
39
|
+
# render(partial: "foo", ...)
|
40
|
+
def normalize_args(string, options_hash)
|
41
|
+
if options_hash
|
42
|
+
{ partial: string, locals: options_hash }
|
43
|
+
else
|
44
|
+
{ partial: string }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def parse_render(node)
|
49
|
+
node = node.argument_nodes
|
50
|
+
|
51
|
+
if (node.length == 1 || node.length == 2) && !node[0].hash?
|
52
|
+
if node.length == 1
|
53
|
+
options = normalize_args(node[0], nil)
|
54
|
+
elsif node.length == 2
|
55
|
+
options = normalize_args(node[0], node[1])
|
56
|
+
end
|
57
|
+
|
58
|
+
return nil unless options
|
59
|
+
|
60
|
+
parse_render_from_options(options)
|
61
|
+
elsif node.length == 1 && node[0].hash?
|
62
|
+
options = parse_hash_to_symbols(node[0])
|
63
|
+
|
64
|
+
return nil unless options
|
65
|
+
|
66
|
+
parse_render_from_options(options)
|
67
|
+
else
|
68
|
+
nil
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def parse_hash(node)
|
73
|
+
node.hash? && node.to_hash
|
74
|
+
end
|
75
|
+
|
76
|
+
def parse_hash_to_symbols(node)
|
77
|
+
hash = parse_hash(node)
|
78
|
+
|
79
|
+
return unless hash
|
80
|
+
|
81
|
+
hash.transform_keys do |key_node|
|
82
|
+
key = parse_sym(key_node)
|
83
|
+
|
84
|
+
return unless key
|
85
|
+
|
86
|
+
key
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
ALL_KNOWN_KEYS = [:partial, :template, :layout, :formats, :locals, :object, :collection, :as, :status, :content_type, :location, :spacer_template]
|
91
|
+
|
92
|
+
RENDER_TYPE_KEYS =
|
93
|
+
[:partial, :template, :layout]
|
94
|
+
|
95
|
+
def parse_render_from_options(options_hash)
|
96
|
+
renders = []
|
97
|
+
keys = options_hash.keys
|
98
|
+
|
99
|
+
if (keys & RENDER_TYPE_KEYS).size < 1
|
100
|
+
# Must have at least one of render keys
|
101
|
+
return nil
|
102
|
+
end
|
103
|
+
|
104
|
+
if (keys - ALL_KNOWN_KEYS).any?
|
105
|
+
# de-opt in case of unknown option
|
106
|
+
return nil
|
107
|
+
end
|
108
|
+
|
109
|
+
render_type = (keys & RENDER_TYPE_KEYS)[0]
|
110
|
+
|
111
|
+
node = options_hash[render_type]
|
112
|
+
|
113
|
+
if node.string?
|
114
|
+
template = resolve_path_directory(node.to_string)
|
115
|
+
else
|
116
|
+
if node.variable_reference?
|
117
|
+
dependency = node.variable_name.sub(/\A(?:\$|@{1,2})/, "")
|
118
|
+
elsif node.vcall?
|
119
|
+
dependency = node.variable_name
|
120
|
+
elsif node.call?
|
121
|
+
dependency = node.call_method_name
|
122
|
+
else
|
123
|
+
return
|
124
|
+
end
|
125
|
+
|
126
|
+
object_template = true
|
127
|
+
template = "#{dependency.pluralize}/#{dependency.singularize}"
|
128
|
+
end
|
129
|
+
|
130
|
+
return unless template
|
131
|
+
|
132
|
+
if spacer_template = render_template_with_spacer?(options_hash)
|
133
|
+
virtual_path = partial_to_virtual_path(:partial, spacer_template)
|
134
|
+
renders << virtual_path
|
135
|
+
end
|
136
|
+
|
137
|
+
if options_hash.key?(:object) || options_hash.key?(:collection) || object_template
|
138
|
+
return nil if options_hash.key?(:object) && options_hash.key?(:collection)
|
139
|
+
return nil unless options_hash.key?(:partial)
|
140
|
+
end
|
141
|
+
|
142
|
+
virtual_path = partial_to_virtual_path(render_type, template)
|
143
|
+
renders << virtual_path
|
144
|
+
|
145
|
+
# Support for rendering multiple templates (i.e. a partial with a layout)
|
146
|
+
if layout_template = render_template_with_layout?(render_type, options_hash)
|
147
|
+
virtual_path = partial_to_virtual_path(:layout, layout_template)
|
148
|
+
|
149
|
+
renders << virtual_path
|
150
|
+
end
|
151
|
+
|
152
|
+
renders
|
153
|
+
end
|
154
|
+
|
155
|
+
def parse_str(node)
|
156
|
+
node.string? && node.to_string
|
157
|
+
end
|
158
|
+
|
159
|
+
def parse_sym(node)
|
160
|
+
node.symbol? && node.to_symbol
|
161
|
+
end
|
162
|
+
|
163
|
+
private
|
164
|
+
def render_template_with_layout?(render_type, options_hash)
|
165
|
+
if render_type != :layout && options_hash.key?(:layout)
|
166
|
+
parse_str(options_hash[:layout])
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def render_template_with_spacer?(options_hash)
|
171
|
+
if options_hash.key?(:spacer_template)
|
172
|
+
parse_str(options_hash[:spacer_template])
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def partial_to_virtual_path(render_type, partial_path)
|
177
|
+
if render_type == :partial || render_type == :layout
|
178
|
+
partial_path.gsub(%r{(/|^)([^/]*)\z}, '\1_\2')
|
179
|
+
else
|
180
|
+
partial_path
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def layout_to_virtual_path(layout_path)
|
185
|
+
"layouts/#{layout_path}"
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
@@ -18,7 +18,7 @@ module ActionView
|
|
18
18
|
# renderer object of the correct type is created, and the +render+ method on
|
19
19
|
# that new object is called in turn. This abstracts the set up and rendering
|
20
20
|
# into a separate classes for partials and templates.
|
21
|
-
class AbstractRenderer
|
21
|
+
class AbstractRenderer # :nodoc:
|
22
22
|
delegate :template_exists?, :any_templates?, :formats, to: :@lookup_context
|
23
23
|
|
24
24
|
def initialize(lookup_context)
|
@@ -158,7 +158,7 @@ module ActionView
|
|
158
158
|
|
159
159
|
def extract_details(options) # :doc:
|
160
160
|
details = nil
|
161
|
-
|
161
|
+
LookupContext.registered_details.each do |key|
|
162
162
|
value = options[key]
|
163
163
|
|
164
164
|
if value
|
@@ -217,40 +217,6 @@ module ActionView
|
|
217
217
|
# </div>
|
218
218
|
#
|
219
219
|
# As you can see, the <tt>:locals</tt> hash is shared between both the partial and its layout.
|
220
|
-
#
|
221
|
-
# If you pass arguments to "yield" then this will be passed to the block. One way to use this is to pass
|
222
|
-
# an array to layout and treat it as an enumerable.
|
223
|
-
#
|
224
|
-
# <%# app/views/users/_user.html.erb %>
|
225
|
-
# <div class="user">
|
226
|
-
# Budget: $<%= user.budget %>
|
227
|
-
# <%= yield user %>
|
228
|
-
# </div>
|
229
|
-
#
|
230
|
-
# <%# app/views/users/index.html.erb %>
|
231
|
-
# <%= render layout: @users do |user| %>
|
232
|
-
# Title: <%= user.title %>
|
233
|
-
# <% end %>
|
234
|
-
#
|
235
|
-
# This will render the layout for each user and yield to the block, passing the user, each time.
|
236
|
-
#
|
237
|
-
# You can also yield multiple times in one layout and use block arguments to differentiate the sections.
|
238
|
-
#
|
239
|
-
# <%# app/views/users/_user.html.erb %>
|
240
|
-
# <div class="user">
|
241
|
-
# <%= yield user, :header %>
|
242
|
-
# Budget: $<%= user.budget %>
|
243
|
-
# <%= yield user, :footer %>
|
244
|
-
# </div>
|
245
|
-
#
|
246
|
-
# <%# app/views/users/index.html.erb %>
|
247
|
-
# <%= render layout: @users do |user, section| %>
|
248
|
-
# <%- case section when :header -%>
|
249
|
-
# Title: <%= user.title %>
|
250
|
-
# <%- when :footer -%>
|
251
|
-
# Deadline: <%= user.deadline %>
|
252
|
-
# <%- end -%>
|
253
|
-
# <% end %>
|
254
220
|
class PartialRenderer < AbstractRenderer
|
255
221
|
include CollectionCaching
|
256
222
|
|
@@ -44,12 +44,12 @@ module ActionView
|
|
44
44
|
end
|
45
45
|
|
46
46
|
# Direct access to template rendering.
|
47
|
-
def render_template(context, options)
|
47
|
+
def render_template(context, options) # :nodoc:
|
48
48
|
render_template_to_object(context, options).body
|
49
49
|
end
|
50
50
|
|
51
51
|
# Direct access to partial rendering.
|
52
|
-
def render_partial(context, options, &block)
|
52
|
+
def render_partial(context, options, &block) # :nodoc:
|
53
53
|
render_partial_to_object(context, options, &block).body
|
54
54
|
end
|
55
55
|
|
@@ -57,11 +57,11 @@ module ActionView
|
|
57
57
|
@cache_hits ||= {}
|
58
58
|
end
|
59
59
|
|
60
|
-
def render_template_to_object(context, options)
|
60
|
+
def render_template_to_object(context, options) # :nodoc:
|
61
61
|
TemplateRenderer.new(@lookup_context).render(context, options)
|
62
62
|
end
|
63
63
|
|
64
|
-
def render_partial_to_object(context, options, &block)
|
64
|
+
def render_partial_to_object(context, options, &block) # :nodoc:
|
65
65
|
partial = options[:partial]
|
66
66
|
if String === partial
|
67
67
|
collection = collection_from_options(options)
|
@@ -7,11 +7,11 @@ module ActionView
|
|
7
7
|
#
|
8
8
|
# * Support streaming from child templates, partials and so on.
|
9
9
|
# * Rack::Cache needs to support streaming bodies
|
10
|
-
class StreamingTemplateRenderer < TemplateRenderer
|
10
|
+
class StreamingTemplateRenderer < TemplateRenderer # :nodoc:
|
11
11
|
# A valid Rack::Body (i.e. it responds to each).
|
12
12
|
# It is initialized with a block that, when called, starts
|
13
13
|
# rendering the template.
|
14
|
-
class Body
|
14
|
+
class Body # :nodoc:
|
15
15
|
def initialize(&start)
|
16
16
|
@start = start
|
17
17
|
end
|
@@ -42,7 +42,7 @@ module ActionView
|
|
42
42
|
# For streaming, instead of rendering a given a template, we return a Body
|
43
43
|
# object that responds to each. This object is initialized with a block
|
44
44
|
# that knows how to render the template.
|
45
|
-
def render_template(view, template, layout_name = nil, locals = {})
|
45
|
+
def render_template(view, template, layout_name = nil, locals = {}) # :nodoc:
|
46
46
|
return [super.body] unless layout_name && template.supports_streaming?
|
47
47
|
|
48
48
|
locals ||= {}
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ActionView
|
4
|
-
class TemplateRenderer < AbstractRenderer
|
4
|
+
class TemplateRenderer < AbstractRenderer # :nodoc:
|
5
5
|
def render(context, options)
|
6
6
|
@details = extract_details(options)
|
7
7
|
template = determine_template(options)
|
@@ -26,7 +26,11 @@ module ActionView
|
|
26
26
|
if File.exist?(options[:file])
|
27
27
|
Template::RawFile.new(options[:file])
|
28
28
|
else
|
29
|
-
|
29
|
+
if Pathname.new(options[:file]).absolute?
|
30
|
+
raise ArgumentError, "File #{options[:file]} does not exist"
|
31
|
+
else
|
32
|
+
raise ArgumentError, "`render file:` should be given the absolute path to a file. '#{options[:file]}' was given instead"
|
33
|
+
end
|
30
34
|
end
|
31
35
|
elsif options.key?(:inline)
|
32
36
|
handler = Template.handler_for_extension(options[:type] || "erb")
|
@@ -5,7 +5,7 @@ require "action_view/view_paths"
|
|
5
5
|
module ActionView
|
6
6
|
# This is a class to fix I18n global state. Whenever you provide I18n.locale during a request,
|
7
7
|
# it will trigger the lookup_context and consequently expire the cache.
|
8
|
-
class I18nProxy < ::I18n::Config
|
8
|
+
class I18nProxy < ::I18n::Config # :nodoc:
|
9
9
|
attr_reader :original_config, :lookup_context
|
10
10
|
|
11
11
|
def initialize(original_config, lookup_context)
|
@@ -34,7 +34,7 @@ module ActionView
|
|
34
34
|
end
|
35
35
|
|
36
36
|
# Overwrite process to set up I18n proxy.
|
37
|
-
def process(*)
|
37
|
+
def process(*) # :nodoc:
|
38
38
|
old_config, I18n.config = I18n.config, I18nProxy.new(I18n.config, lookup_context)
|
39
39
|
super
|
40
40
|
ensure
|
@@ -0,0 +1,198 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "ripper"
|
4
|
+
|
5
|
+
module ActionView
|
6
|
+
class RenderParser
|
7
|
+
module RipperASTParser # :nodoc:
|
8
|
+
class Node < ::Array # :nodoc:
|
9
|
+
attr_reader :type
|
10
|
+
|
11
|
+
def initialize(type, arr, opts = {})
|
12
|
+
@type = type
|
13
|
+
super(arr)
|
14
|
+
end
|
15
|
+
|
16
|
+
def children
|
17
|
+
to_a
|
18
|
+
end
|
19
|
+
|
20
|
+
def inspect
|
21
|
+
typeinfo = type && type != :list ? ":" + type.to_s + ", " : ""
|
22
|
+
"s(" + typeinfo + map(&:inspect).join(", ") + ")"
|
23
|
+
end
|
24
|
+
|
25
|
+
def fcall?
|
26
|
+
type == :command || type == :fcall
|
27
|
+
end
|
28
|
+
|
29
|
+
def fcall_named?(name)
|
30
|
+
fcall? &&
|
31
|
+
self[0].type == :@ident &&
|
32
|
+
self[0][0] == name
|
33
|
+
end
|
34
|
+
|
35
|
+
def argument_nodes
|
36
|
+
raise unless fcall?
|
37
|
+
return [] if self[1].nil?
|
38
|
+
if self[1].last == false || self[1].last.type == :vcall
|
39
|
+
self[1][0...-1]
|
40
|
+
else
|
41
|
+
self[1][0..-1]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def string?
|
46
|
+
type == :string_literal
|
47
|
+
end
|
48
|
+
|
49
|
+
def variable_reference?
|
50
|
+
type == :var_ref
|
51
|
+
end
|
52
|
+
|
53
|
+
def vcall?
|
54
|
+
type == :vcall
|
55
|
+
end
|
56
|
+
|
57
|
+
def call?
|
58
|
+
type == :call
|
59
|
+
end
|
60
|
+
|
61
|
+
def variable_name
|
62
|
+
self[0][0]
|
63
|
+
end
|
64
|
+
|
65
|
+
def call_method_name
|
66
|
+
self.last.first
|
67
|
+
end
|
68
|
+
|
69
|
+
def to_string
|
70
|
+
raise unless string?
|
71
|
+
self[0][0][0]
|
72
|
+
end
|
73
|
+
|
74
|
+
def hash?
|
75
|
+
type == :bare_assoc_hash || type == :hash
|
76
|
+
end
|
77
|
+
|
78
|
+
def to_hash
|
79
|
+
if type == :bare_assoc_hash
|
80
|
+
hash_from_body(self[0])
|
81
|
+
elsif type == :hash && self[0] == nil
|
82
|
+
{}
|
83
|
+
elsif type == :hash && self[0].type == :assoclist_from_args
|
84
|
+
hash_from_body(self[0][0])
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def hash_from_body(body)
|
89
|
+
body.map do |hash_node|
|
90
|
+
return nil if hash_node.type != :assoc_new
|
91
|
+
|
92
|
+
[hash_node[0], hash_node[1]]
|
93
|
+
end.to_h
|
94
|
+
end
|
95
|
+
|
96
|
+
def symbol?
|
97
|
+
type == :@label || type == :symbol_literal
|
98
|
+
end
|
99
|
+
|
100
|
+
def to_symbol
|
101
|
+
if type == :@label && self[0] =~ /\A(.+):\z/
|
102
|
+
$1.to_sym
|
103
|
+
elsif type == :symbol_literal && self[0].type == :symbol && self[0][0].type == :@ident
|
104
|
+
self[0][0][0].to_sym
|
105
|
+
else
|
106
|
+
raise "not a symbol?: #{self.inspect}"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
class NodeParser < ::Ripper # :nodoc:
|
112
|
+
PARSER_EVENTS.each do |event|
|
113
|
+
arity = PARSER_EVENT_TABLE[event]
|
114
|
+
if arity == 0 && event.to_s.end_with?("_new")
|
115
|
+
module_eval(<<-eof, __FILE__, __LINE__ + 1)
|
116
|
+
def on_#{event}(*args)
|
117
|
+
Node.new(:list, args, lineno: lineno(), column: column())
|
118
|
+
end
|
119
|
+
eof
|
120
|
+
elsif event.to_s.match?(/_add(_.+)?\z/)
|
121
|
+
module_eval(<<-eof, __FILE__, __LINE__ + 1)
|
122
|
+
begin; undef on_#{event}; rescue NameError; end
|
123
|
+
def on_#{event}(list, item)
|
124
|
+
list.push(item)
|
125
|
+
list
|
126
|
+
end
|
127
|
+
eof
|
128
|
+
else
|
129
|
+
module_eval(<<-eof, __FILE__, __LINE__ + 1)
|
130
|
+
begin; undef on_#{event}; rescue NameError; end
|
131
|
+
def on_#{event}(*args)
|
132
|
+
Node.new(:#{event}, args, lineno: lineno(), column: column())
|
133
|
+
end
|
134
|
+
eof
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
SCANNER_EVENTS.each do |event|
|
139
|
+
module_eval(<<-End, __FILE__, __LINE__ + 1)
|
140
|
+
def on_#{event}(tok)
|
141
|
+
Node.new(:@#{event}, [tok], lineno: lineno(), column: column())
|
142
|
+
end
|
143
|
+
End
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
class RenderCallExtractor < NodeParser # :nodoc:
|
148
|
+
attr_reader :render_calls
|
149
|
+
|
150
|
+
METHODS_TO_PARSE = %w(render render_to_string)
|
151
|
+
|
152
|
+
def initialize(*args)
|
153
|
+
super
|
154
|
+
|
155
|
+
@render_calls = []
|
156
|
+
end
|
157
|
+
|
158
|
+
private
|
159
|
+
def on_fcall(name, *args)
|
160
|
+
on_render_call(super)
|
161
|
+
end
|
162
|
+
|
163
|
+
def on_command(name, *args)
|
164
|
+
on_render_call(super)
|
165
|
+
end
|
166
|
+
|
167
|
+
def on_render_call(node)
|
168
|
+
METHODS_TO_PARSE.each do |method|
|
169
|
+
if node.fcall_named?(method)
|
170
|
+
@render_calls << [method, node]
|
171
|
+
return node
|
172
|
+
end
|
173
|
+
end
|
174
|
+
node
|
175
|
+
end
|
176
|
+
|
177
|
+
def on_arg_paren(content)
|
178
|
+
content
|
179
|
+
end
|
180
|
+
|
181
|
+
def on_paren(content)
|
182
|
+
content
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
extend self
|
187
|
+
|
188
|
+
def parse_render_nodes(code)
|
189
|
+
parser = RenderCallExtractor.new(code)
|
190
|
+
parser.parse
|
191
|
+
|
192
|
+
parser.render_calls.group_by(&:first).collect do |method, nodes|
|
193
|
+
[ method.to_sym, nodes.collect { |v| v[1] } ]
|
194
|
+
end.to_h
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|