actionview 7.2.2.2 → 8.0.3
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 +69 -78
- data/README.rdoc +1 -1
- data/lib/action_view/base.rb +6 -9
- data/lib/action_view/dependency_tracker/erb_tracker.rb +36 -27
- data/lib/action_view/dependency_tracker/ruby_tracker.rb +2 -19
- data/lib/action_view/dependency_tracker/wildcard_resolver.rb +32 -0
- data/lib/action_view/dependency_tracker.rb +1 -0
- data/lib/action_view/digestor.rb +6 -2
- data/lib/action_view/gem_version.rb +4 -4
- data/lib/action_view/helpers/asset_tag_helper.rb +2 -2
- data/lib/action_view/helpers/atom_feed_helper.rb +1 -3
- data/lib/action_view/helpers/cache_helper.rb +10 -2
- data/lib/action_view/helpers/date_helper.rb +11 -4
- data/lib/action_view/helpers/form_helper.rb +104 -103
- data/lib/action_view/helpers/form_options_helper.rb +31 -25
- data/lib/action_view/helpers/form_tag_helper.rb +20 -17
- data/lib/action_view/helpers/output_safety_helper.rb +1 -2
- data/lib/action_view/helpers/rendering_helper.rb +160 -50
- data/lib/action_view/helpers/sanitize_helper.rb +6 -0
- data/lib/action_view/helpers/tag_helper.rb +26 -39
- data/lib/action_view/helpers/tags/base.rb +9 -9
- data/lib/action_view/helpers/tags/check_box.rb +2 -2
- data/lib/action_view/helpers/tags/collection_check_boxes.rb +4 -3
- data/lib/action_view/helpers/tags/collection_helpers.rb +2 -1
- data/lib/action_view/helpers/tags/file_field.rb +1 -1
- data/lib/action_view/helpers/tags/label.rb +3 -10
- data/lib/action_view/helpers/tags/radio_button.rb +1 -1
- data/lib/action_view/helpers/tags/select_renderer.rb +1 -1
- data/lib/action_view/helpers/tags/text_area.rb +1 -1
- data/lib/action_view/helpers/tags/text_field.rb +1 -1
- data/lib/action_view/helpers/text_helper.rb +10 -3
- data/lib/action_view/helpers/url_helper.rb +2 -4
- data/lib/action_view/layouts.rb +7 -7
- data/lib/action_view/record_identifier.rb +1 -1
- data/lib/action_view/render_parser/prism_render_parser.rb +13 -1
- data/lib/action_view/render_parser/ripper_render_parser.rb +10 -1
- data/lib/action_view/renderer/partial_renderer.rb +2 -2
- data/lib/action_view/renderer/streaming_template_renderer.rb +8 -2
- data/lib/action_view/renderer/template_renderer.rb +3 -3
- data/lib/action_view/rendering.rb +2 -3
- data/lib/action_view/template/error.rb +11 -0
- data/lib/action_view/template/handlers/erb/erubi.rb +1 -1
- data/lib/action_view/template/handlers/erb.rb +45 -37
- data/lib/action_view/template/raw_file.rb +4 -0
- data/lib/action_view/template/resolver.rb +0 -1
- data/lib/action_view/template.rb +1 -2
- data/lib/action_view/test_case.rb +0 -1
- data/lib/action_view.rb +1 -0
- metadata +12 -11
|
@@ -80,27 +80,27 @@ module ActionView
|
|
|
80
80
|
end
|
|
81
81
|
end
|
|
82
82
|
|
|
83
|
-
def
|
|
83
|
+
def add_default_name_and_field_for_value(tag_value, options, field = "id")
|
|
84
84
|
if tag_value.nil?
|
|
85
|
-
|
|
85
|
+
add_default_name_and_field(options, field)
|
|
86
86
|
else
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
specified_field = options[field]
|
|
88
|
+
add_default_name_and_field(options, field)
|
|
89
89
|
|
|
90
|
-
if
|
|
91
|
-
options[
|
|
90
|
+
if specified_field.blank? && options[field].present?
|
|
91
|
+
options[field] += "_#{sanitized_value(tag_value)}"
|
|
92
92
|
end
|
|
93
93
|
end
|
|
94
94
|
end
|
|
95
95
|
|
|
96
|
-
def
|
|
96
|
+
def add_default_name_and_field(options, field = "id")
|
|
97
97
|
index = name_and_id_index(options)
|
|
98
98
|
options["name"] = options.fetch("name") { tag_name(options["multiple"], index) }
|
|
99
99
|
|
|
100
100
|
if generate_ids?
|
|
101
|
-
options[
|
|
101
|
+
options[field] = options.fetch(field) { tag_id(index, options.delete("namespace")) }
|
|
102
102
|
if namespace = options.delete("namespace")
|
|
103
|
-
options[
|
|
103
|
+
options[field] = options[field] ? "#{namespace}_#{options[field]}" : namespace
|
|
104
104
|
end
|
|
105
105
|
end
|
|
106
106
|
end
|
|
@@ -21,10 +21,10 @@ module ActionView
|
|
|
21
21
|
options["checked"] = "checked" if input_checked?(options)
|
|
22
22
|
|
|
23
23
|
if options["multiple"]
|
|
24
|
-
|
|
24
|
+
add_default_name_and_field_for_value(@checked_value, options)
|
|
25
25
|
options.delete("multiple")
|
|
26
26
|
else
|
|
27
|
-
|
|
27
|
+
add_default_name_and_field(options)
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
include_hidden = options.delete("include_hidden") { true }
|
|
@@ -10,12 +10,13 @@ module ActionView
|
|
|
10
10
|
include FormOptionsHelper
|
|
11
11
|
|
|
12
12
|
class CheckBoxBuilder < Builder # :nodoc:
|
|
13
|
-
def
|
|
13
|
+
def checkbox(extra_html_options = {})
|
|
14
14
|
html_options = extra_html_options.merge(@input_html_options)
|
|
15
15
|
html_options[:multiple] = true
|
|
16
16
|
html_options[:skip_default_ids] = false
|
|
17
|
-
@template_object.
|
|
17
|
+
@template_object.checkbox(@object_name, @method_name, html_options, @value, nil)
|
|
18
18
|
end
|
|
19
|
+
alias_method :check_box, :checkbox
|
|
19
20
|
end
|
|
20
21
|
|
|
21
22
|
def render(&block)
|
|
@@ -24,7 +25,7 @@ module ActionView
|
|
|
24
25
|
|
|
25
26
|
private
|
|
26
27
|
def render_component(builder)
|
|
27
|
-
builder.
|
|
28
|
+
builder.checkbox + builder.label
|
|
28
29
|
end
|
|
29
30
|
|
|
30
31
|
def hidden_field_name
|
|
@@ -106,7 +106,8 @@ module ActionView
|
|
|
106
106
|
|
|
107
107
|
def hidden_field
|
|
108
108
|
hidden_name = @html_options[:name] || hidden_field_name
|
|
109
|
-
|
|
109
|
+
options = { id: nil, form: @html_options[:form] }
|
|
110
|
+
@template_object.hidden_field_tag(hidden_name, "", options)
|
|
110
111
|
end
|
|
111
112
|
|
|
112
113
|
def hidden_field_name
|
|
@@ -7,7 +7,7 @@ module ActionView
|
|
|
7
7
|
def render
|
|
8
8
|
include_hidden = @options.delete(:include_hidden)
|
|
9
9
|
options = @options.stringify_keys
|
|
10
|
-
|
|
10
|
+
add_default_name_and_field(options)
|
|
11
11
|
|
|
12
12
|
if options["multiple"] && include_hidden
|
|
13
13
|
hidden_field_for_multiple_file(options) + super
|
|
@@ -48,18 +48,11 @@ module ActionView
|
|
|
48
48
|
def render(&block)
|
|
49
49
|
options = @options.stringify_keys
|
|
50
50
|
tag_value = options.delete("value")
|
|
51
|
-
name_and_id = options.dup
|
|
52
51
|
|
|
53
|
-
|
|
54
|
-
name_and_id["id"] = name_and_id["for"]
|
|
55
|
-
else
|
|
56
|
-
name_and_id.delete("id")
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
add_default_name_and_id_for_value(tag_value, name_and_id)
|
|
52
|
+
add_default_name_and_field_for_value(tag_value, options, "for")
|
|
60
53
|
options.delete("index")
|
|
54
|
+
options.delete("name")
|
|
61
55
|
options.delete("namespace")
|
|
62
|
-
options["for"] = name_and_id["id"] unless options.key?("for")
|
|
63
56
|
|
|
64
57
|
builder = LabelBuilder.new(@template_object, @object_name, @method_name, @object, tag_value)
|
|
65
58
|
|
|
@@ -71,7 +64,7 @@ module ActionView
|
|
|
71
64
|
render_component(builder)
|
|
72
65
|
end
|
|
73
66
|
|
|
74
|
-
label_tag(
|
|
67
|
+
label_tag(options["for"], content, options)
|
|
75
68
|
end
|
|
76
69
|
|
|
77
70
|
private
|
|
@@ -18,7 +18,7 @@ module ActionView
|
|
|
18
18
|
options["type"] = "radio"
|
|
19
19
|
options["value"] = @tag_value
|
|
20
20
|
options["checked"] = "checked" if input_checked?(options)
|
|
21
|
-
|
|
21
|
+
add_default_name_and_field_for_value(@tag_value, options)
|
|
22
22
|
tag("input", options)
|
|
23
23
|
end
|
|
24
24
|
|
|
@@ -11,7 +11,7 @@ module ActionView
|
|
|
11
11
|
html_options[prop.to_s] = options.delete(prop) if options.key?(prop) && !html_options.key?(prop.to_s)
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
add_default_name_and_field(html_options)
|
|
15
15
|
|
|
16
16
|
if placeholder_required?(html_options)
|
|
17
17
|
raise ArgumentError, "include_blank cannot be false for a required field." if options[:include_blank] == false
|
|
@@ -10,7 +10,7 @@ module ActionView
|
|
|
10
10
|
|
|
11
11
|
def render
|
|
12
12
|
options = @options.stringify_keys
|
|
13
|
-
|
|
13
|
+
add_default_name_and_field(options)
|
|
14
14
|
|
|
15
15
|
if size = options.delete("size")
|
|
16
16
|
options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
|
|
@@ -13,7 +13,7 @@ module ActionView
|
|
|
13
13
|
options["size"] = options["maxlength"] unless options.key?("size")
|
|
14
14
|
options["type"] ||= field_type
|
|
15
15
|
options["value"] = options.fetch("value") { value_before_type_cast } unless field_type == "file"
|
|
16
|
-
|
|
16
|
+
add_default_name_and_field(options)
|
|
17
17
|
tag("input", options)
|
|
18
18
|
end
|
|
19
19
|
|
|
@@ -260,7 +260,14 @@ module ActionView
|
|
|
260
260
|
prefix, first_part = cut_excerpt_part(:first, first_part, separator, options)
|
|
261
261
|
postfix, second_part = cut_excerpt_part(:second, second_part, separator, options)
|
|
262
262
|
|
|
263
|
-
affix = [
|
|
263
|
+
affix = [
|
|
264
|
+
first_part,
|
|
265
|
+
!first_part.empty? ? separator : "",
|
|
266
|
+
phrase,
|
|
267
|
+
!second_part.empty? ? separator : "",
|
|
268
|
+
second_part
|
|
269
|
+
].join.strip
|
|
270
|
+
|
|
264
271
|
[prefix, affix, postfix].join
|
|
265
272
|
end
|
|
266
273
|
|
|
@@ -271,7 +278,7 @@ module ActionView
|
|
|
271
278
|
#
|
|
272
279
|
# The word will be pluralized using rules defined for the locale
|
|
273
280
|
# (you must define your own inflection rules for languages other than English).
|
|
274
|
-
# See ActiveSupport::Inflector.pluralize
|
|
281
|
+
# See ActiveSupport::Inflector.pluralize.
|
|
275
282
|
#
|
|
276
283
|
# pluralize(1, 'person')
|
|
277
284
|
# # => "1 person"
|
|
@@ -346,7 +353,7 @@ module ActionView
|
|
|
346
353
|
# ==== Options
|
|
347
354
|
# * <tt>:sanitize</tt> - If +false+, does not sanitize +text+.
|
|
348
355
|
# * <tt>:sanitize_options</tt> - Any extra options you want appended to the sanitize.
|
|
349
|
-
# * <tt>:wrapper_tag</tt> - String representing the wrapper tag, defaults to <tt>"p"</tt
|
|
356
|
+
# * <tt>:wrapper_tag</tt> - String representing the wrapper tag, defaults to <tt>"p"</tt>.
|
|
350
357
|
#
|
|
351
358
|
# ==== Examples
|
|
352
359
|
# my_text = "Here is some basic text...\n...with a line break."
|
|
@@ -500,8 +500,6 @@ module ActionView
|
|
|
500
500
|
content_tag("a", name || email_address, html_options, &block)
|
|
501
501
|
end
|
|
502
502
|
|
|
503
|
-
RFC2396_PARSER = defined?(URI::RFC2396_PARSER) ? URI::RFC2396_PARSER : URI::RFC2396_Parser.new
|
|
504
|
-
|
|
505
503
|
# True if the current request URI was generated by the given +options+.
|
|
506
504
|
#
|
|
507
505
|
# ==== Examples
|
|
@@ -558,14 +556,14 @@ module ActionView
|
|
|
558
556
|
|
|
559
557
|
options ||= options_as_kwargs
|
|
560
558
|
check_parameters ||= options.is_a?(Hash) && options.delete(:check_parameters)
|
|
561
|
-
url_string = RFC2396_PARSER.unescape(url_for(options)).force_encoding(Encoding::BINARY)
|
|
559
|
+
url_string = URI::RFC2396_PARSER.unescape(url_for(options)).force_encoding(Encoding::BINARY)
|
|
562
560
|
|
|
563
561
|
# We ignore any extra parameters in the request_uri if the
|
|
564
562
|
# submitted URL doesn't have any either. This lets the function
|
|
565
563
|
# work with things like ?order=asc
|
|
566
564
|
# the behavior can be disabled with check_parameters: true
|
|
567
565
|
request_uri = url_string.index("?") || check_parameters ? request.fullpath : request.path
|
|
568
|
-
request_uri = RFC2396_PARSER.unescape(request_uri).force_encoding(Encoding::BINARY)
|
|
566
|
+
request_uri = URI::RFC2396_PARSER.unescape(request_uri).force_encoding(Encoding::BINARY)
|
|
569
567
|
|
|
570
568
|
if %r{^\w+://}.match?(url_string)
|
|
571
569
|
request_uri = +"#{request.protocol}#{request.host_with_port}#{request_uri}"
|
data/lib/action_view/layouts.rb
CHANGED
|
@@ -284,7 +284,7 @@ module ActionView
|
|
|
284
284
|
silence_redefinition_of_method(:_layout)
|
|
285
285
|
|
|
286
286
|
prefixes = /\blayouts/.match?(_implied_layout_name) ? [] : ["layouts"]
|
|
287
|
-
default_behavior = "lookup_context.find_all('#{_implied_layout_name}', #{prefixes.inspect}, false,
|
|
287
|
+
default_behavior = "lookup_context.find_all('#{_implied_layout_name}', #{prefixes.inspect}, false, keys, { formats: formats }).first || super"
|
|
288
288
|
name_clause = if name
|
|
289
289
|
default_behavior
|
|
290
290
|
else
|
|
@@ -325,7 +325,7 @@ module ActionView
|
|
|
325
325
|
|
|
326
326
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
327
327
|
# frozen_string_literal: true
|
|
328
|
-
def _layout(lookup_context, formats)
|
|
328
|
+
def _layout(lookup_context, formats, keys)
|
|
329
329
|
if _conditional_layout?
|
|
330
330
|
#{layout_definition}
|
|
331
331
|
else
|
|
@@ -347,7 +347,7 @@ module ActionView
|
|
|
347
347
|
end
|
|
348
348
|
end
|
|
349
349
|
|
|
350
|
-
def
|
|
350
|
+
def _process_render_template_options(options) # :nodoc:
|
|
351
351
|
super
|
|
352
352
|
|
|
353
353
|
if _include_layout?(options)
|
|
@@ -389,8 +389,8 @@ module ActionView
|
|
|
389
389
|
case name
|
|
390
390
|
when String then _normalize_layout(name)
|
|
391
391
|
when Proc then name
|
|
392
|
-
when true then Proc.new { |lookup_context, formats| _default_layout(lookup_context, formats, true) }
|
|
393
|
-
when :default then Proc.new { |lookup_context, formats| _default_layout(lookup_context, formats, false) }
|
|
392
|
+
when true then Proc.new { |lookup_context, formats, keys| _default_layout(lookup_context, formats, keys, true) }
|
|
393
|
+
when :default then Proc.new { |lookup_context, formats, keys| _default_layout(lookup_context, formats, keys, false) }
|
|
394
394
|
when false, nil then nil
|
|
395
395
|
else
|
|
396
396
|
raise ArgumentError,
|
|
@@ -412,9 +412,9 @@ module ActionView
|
|
|
412
412
|
#
|
|
413
413
|
# ==== Returns
|
|
414
414
|
# * <tt>template</tt> - The template object for the default layout (or +nil+)
|
|
415
|
-
def _default_layout(lookup_context, formats, require_layout = false)
|
|
415
|
+
def _default_layout(lookup_context, formats, keys, require_layout = false)
|
|
416
416
|
begin
|
|
417
|
-
value = _layout(lookup_context, formats) if action_has_layout?
|
|
417
|
+
value = _layout(lookup_context, formats, keys) if action_has_layout?
|
|
418
418
|
rescue NameError => e
|
|
419
419
|
raise e, "Could not render layout: #{e.message}"
|
|
420
420
|
end
|
|
@@ -97,9 +97,21 @@ module ActionView
|
|
|
97
97
|
def render_call_template(node)
|
|
98
98
|
object_template = false
|
|
99
99
|
template =
|
|
100
|
-
|
|
100
|
+
case node.type
|
|
101
|
+
when :string_node
|
|
101
102
|
path = node.unescaped
|
|
102
103
|
path.include?("/") ? path : "#{directory}/#{path}"
|
|
104
|
+
when :interpolated_string_node
|
|
105
|
+
node.parts.map do |node|
|
|
106
|
+
case node.type
|
|
107
|
+
when :string_node
|
|
108
|
+
node.unescaped
|
|
109
|
+
when :embedded_statements_node
|
|
110
|
+
"*"
|
|
111
|
+
else
|
|
112
|
+
return
|
|
113
|
+
end
|
|
114
|
+
end.join("")
|
|
103
115
|
else
|
|
104
116
|
dependency =
|
|
105
117
|
case node.type
|
|
@@ -66,7 +66,16 @@ module ActionView
|
|
|
66
66
|
|
|
67
67
|
def to_string
|
|
68
68
|
raise unless string?
|
|
69
|
-
|
|
69
|
+
|
|
70
|
+
# s(:string_literal, s(:string_content, map))
|
|
71
|
+
self[0].map do |node|
|
|
72
|
+
case node.type
|
|
73
|
+
when :@tstring_content
|
|
74
|
+
node[0]
|
|
75
|
+
when :string_embexpr
|
|
76
|
+
"*"
|
|
77
|
+
end
|
|
78
|
+
end.join("")
|
|
70
79
|
end
|
|
71
80
|
|
|
72
81
|
def hash?
|
|
@@ -94,7 +94,7 @@ module ActionView
|
|
|
94
94
|
# # <%= render partial: "accounts/account", locals: { account: @account} %>
|
|
95
95
|
# <%= render partial: @account %>
|
|
96
96
|
#
|
|
97
|
-
# # @posts is an array of Post instances, so every post record returns 'posts/post' on
|
|
97
|
+
# # @posts is an array of Post instances, so every post record returns 'posts/post' on #to_partial_path,
|
|
98
98
|
# # that's why we can replace:
|
|
99
99
|
# # <%= render partial: "posts/post", collection: @posts %>
|
|
100
100
|
# <%= render partial: @posts %>
|
|
@@ -114,7 +114,7 @@ module ActionView
|
|
|
114
114
|
# # <%= render partial: "accounts/account", locals: { account: @account} %>
|
|
115
115
|
# <%= render @account %>
|
|
116
116
|
#
|
|
117
|
-
# # @posts is an array of Post instances, so every post record returns 'posts/post' on
|
|
117
|
+
# # @posts is an array of Post instances, so every post record returns 'posts/post' on #to_partial_path,
|
|
118
118
|
# # that's why we can replace:
|
|
119
119
|
# # <%= render partial: "posts/post", collection: @posts %>
|
|
120
120
|
# <%= render @posts %>
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "fiber"
|
|
4
3
|
|
|
5
4
|
module ActionView
|
|
6
5
|
# == TODO
|
|
@@ -26,6 +25,13 @@ module ActionView
|
|
|
26
25
|
self
|
|
27
26
|
end
|
|
28
27
|
|
|
28
|
+
# Returns the complete body as a string.
|
|
29
|
+
def body
|
|
30
|
+
buffer = String.new
|
|
31
|
+
each { |part| buffer << part }
|
|
32
|
+
buffer
|
|
33
|
+
end
|
|
34
|
+
|
|
29
35
|
private
|
|
30
36
|
# This is the same logging logic as in ShowExceptions middleware.
|
|
31
37
|
def log_error(exception)
|
|
@@ -43,7 +49,7 @@ module ActionView
|
|
|
43
49
|
# object that responds to each. This object is initialized with a block
|
|
44
50
|
# that knows how to render the template.
|
|
45
51
|
def render_template(view, template, layout_name = nil, locals = {}) # :nodoc:
|
|
46
|
-
return [super.body] unless
|
|
52
|
+
return [super.body] unless template.supports_streaming?
|
|
47
53
|
|
|
48
54
|
locals ||= {}
|
|
49
55
|
layout = find_layout(layout_name, locals.keys, [formats.first])
|
|
@@ -99,14 +99,14 @@ module ActionView
|
|
|
99
99
|
if layout.start_with?("/")
|
|
100
100
|
raise ArgumentError, "Rendering layouts from an absolute path is not supported."
|
|
101
101
|
else
|
|
102
|
-
@lookup_context.find_template(layout, nil, false,
|
|
102
|
+
@lookup_context.find_template(layout, nil, false, keys, details)
|
|
103
103
|
end
|
|
104
104
|
rescue ActionView::MissingTemplate
|
|
105
105
|
all_details = @details.merge(formats: @lookup_context.default_formats)
|
|
106
|
-
raise unless template_exists?(layout, nil, false,
|
|
106
|
+
raise unless template_exists?(layout, nil, false, keys, **all_details)
|
|
107
107
|
end
|
|
108
108
|
when Proc
|
|
109
|
-
resolve_layout(layout.call(@lookup_context, formats), keys, formats)
|
|
109
|
+
resolve_layout(layout.call(@lookup_context, formats, keys), keys, formats)
|
|
110
110
|
else
|
|
111
111
|
layout
|
|
112
112
|
end
|
|
@@ -118,6 +118,7 @@ module ActionView
|
|
|
118
118
|
|
|
119
119
|
def render_to_body(options = {})
|
|
120
120
|
_process_options(options)
|
|
121
|
+
_process_render_template_options(options)
|
|
121
122
|
_render_template(options)
|
|
122
123
|
end
|
|
123
124
|
|
|
@@ -173,8 +174,7 @@ module ActionView
|
|
|
173
174
|
end
|
|
174
175
|
|
|
175
176
|
# Normalize options.
|
|
176
|
-
def
|
|
177
|
-
options = super(options)
|
|
177
|
+
def _process_render_template_options(options)
|
|
178
178
|
if options[:partial] == true
|
|
179
179
|
options[:partial] = action_name
|
|
180
180
|
end
|
|
@@ -184,7 +184,6 @@ module ActionView
|
|
|
184
184
|
end
|
|
185
185
|
|
|
186
186
|
options[:template] ||= (options[:action] || action_name).to_s
|
|
187
|
-
options
|
|
188
187
|
end
|
|
189
188
|
end
|
|
190
189
|
end
|
|
@@ -27,6 +27,17 @@ module ActionView
|
|
|
27
27
|
end
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
+
class StrictLocalsError < ArgumentError # :nodoc:
|
|
31
|
+
def initialize(argument_error, template)
|
|
32
|
+
message = argument_error.message.
|
|
33
|
+
gsub("unknown keyword:", "unknown local:").
|
|
34
|
+
gsub("missing keyword:", "missing local:").
|
|
35
|
+
gsub("no keywords accepted", "no locals accepted").
|
|
36
|
+
concat(" for #{template.short_identifier}")
|
|
37
|
+
super(message)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
30
41
|
class MissingTemplate < ActionViewError # :nodoc:
|
|
31
42
|
attr_reader :path, :paths, :prefixes, :partial
|
|
32
43
|
|
|
@@ -18,7 +18,7 @@ module ActionView
|
|
|
18
18
|
properties[:preamble] ||= ""
|
|
19
19
|
properties[:postamble] ||= "#{properties[:bufvar]}"
|
|
20
20
|
|
|
21
|
-
# Tell
|
|
21
|
+
# Tell Erubi whether the template will be compiled with `frozen_string_literal: true`
|
|
22
22
|
properties[:freeze_template_literals] = !Template.frozen_string_literal
|
|
23
23
|
|
|
24
24
|
properties[:escapefunc] = ""
|
|
@@ -42,7 +42,9 @@ module ActionView
|
|
|
42
42
|
# source location inside the template.
|
|
43
43
|
def translate_location(spot, backtrace_location, source)
|
|
44
44
|
# Tokenize the source line
|
|
45
|
-
|
|
45
|
+
source_lines = source.lines
|
|
46
|
+
return nil if source_lines.size < backtrace_location.lineno
|
|
47
|
+
tokens = ::ERB::Util.tokenize(source_lines[backtrace_location.lineno - 1])
|
|
46
48
|
new_first_column = find_offset(spot[:snippet], tokens, spot[:first_column])
|
|
47
49
|
lineno_delta = spot[:first_lineno] - backtrace_location.lineno
|
|
48
50
|
spot[:first_lineno] -= lineno_delta
|
|
@@ -51,7 +53,7 @@ module ActionView
|
|
|
51
53
|
column_delta = spot[:first_column] - new_first_column
|
|
52
54
|
spot[:first_column] -= column_delta
|
|
53
55
|
spot[:last_column] -= column_delta
|
|
54
|
-
spot[:script_lines] =
|
|
56
|
+
spot[:script_lines] = source_lines
|
|
55
57
|
|
|
56
58
|
spot
|
|
57
59
|
rescue NotImplementedError, LocationParsingError
|
|
@@ -105,51 +107,57 @@ module ActionView
|
|
|
105
107
|
raise WrongEncodingError.new(string, string.encoding)
|
|
106
108
|
end
|
|
107
109
|
|
|
110
|
+
# Find which token in the source template spans the byte range that
|
|
111
|
+
# contains the error_column, then return the offset compared to the
|
|
112
|
+
# original source template.
|
|
113
|
+
#
|
|
114
|
+
# Iterate consecutive pairs of CODE or TEXT tokens, requiring
|
|
115
|
+
# a match of the first token before matching either token.
|
|
116
|
+
#
|
|
117
|
+
# For example, if we want to find tokens A, B, C, we do the following:
|
|
118
|
+
# 1. Find a match for A: test error_column or advance scanner.
|
|
119
|
+
# 2. Find a match for B or A:
|
|
120
|
+
# a. If B: start over with next token set (B, C).
|
|
121
|
+
# b. If A: test error_column or advance scanner.
|
|
122
|
+
# c. Otherwise: Advance 1 byte
|
|
123
|
+
#
|
|
124
|
+
# Prioritize matching the next token over the current token once
|
|
125
|
+
# a match for the current token has been found. This is to prevent
|
|
126
|
+
# the current token from looping past the next token if they both
|
|
127
|
+
# match (i.e. if the current token is a single space character).
|
|
108
128
|
def find_offset(compiled, source_tokens, error_column)
|
|
109
129
|
compiled = StringScanner.new(compiled)
|
|
130
|
+
offset_source_tokens(source_tokens).each_cons(2) do |(name, str, offset), (_, next_str, _)|
|
|
131
|
+
matched_str = false
|
|
110
132
|
|
|
111
|
-
|
|
133
|
+
until compiled.eos?
|
|
134
|
+
if matched_str && next_str && compiled.match?(next_str)
|
|
135
|
+
break
|
|
136
|
+
elsif compiled.match?(str)
|
|
137
|
+
matched_str = true
|
|
112
138
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
when :TEXT
|
|
117
|
-
loop do
|
|
118
|
-
break if compiled.match?(str)
|
|
119
|
-
compiled.getch
|
|
120
|
-
end
|
|
121
|
-
raise LocationParsingError unless compiled.scan(str)
|
|
122
|
-
when :CODE
|
|
123
|
-
if compiled.pos > error_column
|
|
124
|
-
raise LocationParsingError, "We went too far"
|
|
125
|
-
end
|
|
139
|
+
if name == :CODE && compiled.pos <= error_column && compiled.pos + str.bytesize >= error_column
|
|
140
|
+
return error_column - compiled.pos + offset
|
|
141
|
+
end
|
|
126
142
|
|
|
127
|
-
|
|
128
|
-
offset = error_column - compiled.pos
|
|
129
|
-
return passed_tokens.map(&:last).join.bytesize + offset
|
|
143
|
+
compiled.pos += str.bytesize
|
|
130
144
|
else
|
|
131
|
-
|
|
132
|
-
raise LocationParsingError, "Couldn't find code snippet"
|
|
133
|
-
end
|
|
134
|
-
end
|
|
135
|
-
when :OPEN
|
|
136
|
-
next_tok = source_tokens.first.last
|
|
137
|
-
loop do
|
|
138
|
-
break if compiled.match?(next_tok)
|
|
139
|
-
compiled.getch
|
|
145
|
+
compiled.pos += 1
|
|
140
146
|
end
|
|
141
|
-
when :CLOSE
|
|
142
|
-
next_tok = source_tokens.first.last
|
|
143
|
-
loop do
|
|
144
|
-
break if compiled.match?(next_tok)
|
|
145
|
-
compiled.getch
|
|
146
|
-
end
|
|
147
|
-
else
|
|
148
|
-
raise LocationParsingError, "Not implemented: #{tok.first}"
|
|
149
147
|
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
raise LocationParsingError, "Couldn't find code snippet"
|
|
151
|
+
end
|
|
150
152
|
|
|
151
|
-
|
|
153
|
+
def offset_source_tokens(source_tokens)
|
|
154
|
+
source_offset = 0
|
|
155
|
+
with_offset = source_tokens.filter_map do |(name, str)|
|
|
156
|
+
result = [name, str, source_offset] if name == :CODE || name == :TEXT
|
|
157
|
+
source_offset += str.bytesize
|
|
158
|
+
result
|
|
152
159
|
end
|
|
160
|
+
with_offset << [:EOS, nil, source_offset]
|
|
153
161
|
end
|
|
154
162
|
end
|
|
155
163
|
end
|
data/lib/action_view/template.rb
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "thread"
|
|
4
3
|
require "delegate"
|
|
5
4
|
|
|
6
5
|
module ActionView
|
|
@@ -357,7 +356,7 @@ module ActionView
|
|
|
357
356
|
|
|
358
357
|
# This method is responsible for marking a template as having strict locals
|
|
359
358
|
# which means the template can only accept the locals defined in a magic
|
|
360
|
-
# comment. For example, if your template
|
|
359
|
+
# comment. For example, if your template accepts the locals +title+ and
|
|
361
360
|
# +comment_count+, add the following to your template file:
|
|
362
361
|
#
|
|
363
362
|
# <%# locals: (title: "Default title", comment_count: 0) %>
|
|
@@ -301,7 +301,6 @@ module ActionView
|
|
|
301
301
|
class RenderedViewContent < String # :nodoc:
|
|
302
302
|
end
|
|
303
303
|
|
|
304
|
-
# Need to experiment if this priority is the best one: rendered => output_buffer
|
|
305
304
|
class RenderedViewsCollection
|
|
306
305
|
def initialize
|
|
307
306
|
@rendered_views ||= Hash.new { |hash, key| hash[key] = [] }
|