actionview 5.2.7.1 → 6.1.4.6

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.

Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +250 -112
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +5 -3
  5. data/lib/action_view/base.rb +81 -15
  6. data/lib/action_view/buffers.rb +15 -0
  7. data/lib/action_view/cache_expiry.rb +52 -0
  8. data/lib/action_view/context.rb +5 -9
  9. data/lib/action_view/dependency_tracker.rb +10 -4
  10. data/lib/action_view/digestor.rb +15 -22
  11. data/lib/action_view/flows.rb +0 -1
  12. data/lib/action_view/gem_version.rb +4 -4
  13. data/lib/action_view/helpers/active_model_helper.rb +0 -1
  14. data/lib/action_view/helpers/asset_tag_helper.rb +64 -47
  15. data/lib/action_view/helpers/asset_url_helper.rb +9 -6
  16. data/lib/action_view/helpers/atom_feed_helper.rb +2 -1
  17. data/lib/action_view/helpers/cache_helper.rb +23 -22
  18. data/lib/action_view/helpers/capture_helper.rb +4 -0
  19. data/lib/action_view/helpers/csp_helper.rb +4 -2
  20. data/lib/action_view/helpers/csrf_helper.rb +1 -1
  21. data/lib/action_view/helpers/date_helper.rb +73 -30
  22. data/lib/action_view/helpers/form_helper.rb +305 -37
  23. data/lib/action_view/helpers/form_options_helper.rb +23 -23
  24. data/lib/action_view/helpers/form_tag_helper.rb +19 -16
  25. data/lib/action_view/helpers/javascript_helper.rb +12 -11
  26. data/lib/action_view/helpers/number_helper.rb +14 -8
  27. data/lib/action_view/helpers/output_safety_helper.rb +1 -1
  28. data/lib/action_view/helpers/rendering_helper.rb +17 -7
  29. data/lib/action_view/helpers/sanitize_helper.rb +12 -18
  30. data/lib/action_view/helpers/tag_helper.rb +100 -55
  31. data/lib/action_view/helpers/tags/base.rb +18 -11
  32. data/lib/action_view/helpers/tags/check_box.rb +0 -1
  33. data/lib/action_view/helpers/tags/collection_check_boxes.rb +0 -1
  34. data/lib/action_view/helpers/tags/collection_helpers.rb +0 -1
  35. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +0 -1
  36. data/lib/action_view/helpers/tags/color_field.rb +1 -2
  37. data/lib/action_view/helpers/tags/date_field.rb +1 -2
  38. data/lib/action_view/helpers/tags/date_select.rb +2 -3
  39. data/lib/action_view/helpers/tags/datetime_field.rb +0 -1
  40. data/lib/action_view/helpers/tags/datetime_local_field.rb +1 -2
  41. data/lib/action_view/helpers/tags/label.rb +4 -1
  42. data/lib/action_view/helpers/tags/month_field.rb +1 -2
  43. data/lib/action_view/helpers/tags/radio_button.rb +0 -1
  44. data/lib/action_view/helpers/tags/select.rb +1 -2
  45. data/lib/action_view/helpers/tags/text_field.rb +0 -1
  46. data/lib/action_view/helpers/tags/time_field.rb +1 -2
  47. data/lib/action_view/helpers/tags/translator.rb +1 -6
  48. data/lib/action_view/helpers/tags/week_field.rb +1 -2
  49. data/lib/action_view/helpers/text_helper.rb +4 -5
  50. data/lib/action_view/helpers/translation_helper.rb +94 -54
  51. data/lib/action_view/helpers/url_helper.rb +136 -28
  52. data/lib/action_view/helpers.rb +0 -2
  53. data/lib/action_view/layouts.rb +8 -10
  54. data/lib/action_view/log_subscriber.rb +30 -15
  55. data/lib/action_view/lookup_context.rb +63 -35
  56. data/lib/action_view/path_set.rb +3 -12
  57. data/lib/action_view/railtie.rb +42 -26
  58. data/lib/action_view/record_identifier.rb +2 -3
  59. data/lib/action_view/renderer/abstract_renderer.rb +142 -11
  60. data/lib/action_view/renderer/collection_renderer.rb +196 -0
  61. data/lib/action_view/renderer/object_renderer.rb +34 -0
  62. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +61 -16
  63. data/lib/action_view/renderer/partial_renderer.rb +21 -273
  64. data/lib/action_view/renderer/renderer.rb +59 -4
  65. data/lib/action_view/renderer/streaming_template_renderer.rb +10 -8
  66. data/lib/action_view/renderer/template_renderer.rb +35 -27
  67. data/lib/action_view/rendering.rb +54 -33
  68. data/lib/action_view/routing_url_for.rb +13 -12
  69. data/lib/action_view/template/error.rb +30 -15
  70. data/lib/action_view/template/handlers/builder.rb +2 -2
  71. data/lib/action_view/template/handlers/erb/erubi.rb +15 -9
  72. data/lib/action_view/template/handlers/erb.rb +16 -11
  73. data/lib/action_view/template/handlers/html.rb +1 -1
  74. data/lib/action_view/template/handlers/raw.rb +2 -2
  75. data/lib/action_view/template/handlers.rb +1 -1
  76. data/lib/action_view/template/html.rb +5 -6
  77. data/lib/action_view/template/inline.rb +22 -0
  78. data/lib/action_view/template/raw_file.rb +25 -0
  79. data/lib/action_view/template/renderable.rb +24 -0
  80. data/lib/action_view/template/resolver.rb +191 -150
  81. data/lib/action_view/template/sources/file.rb +17 -0
  82. data/lib/action_view/template/sources.rb +13 -0
  83. data/lib/action_view/template/text.rb +2 -3
  84. data/lib/action_view/template.rb +66 -75
  85. data/lib/action_view/test_case.rb +21 -29
  86. data/lib/action_view/testing/resolvers.rb +18 -27
  87. data/lib/action_view/unbound_template.rb +31 -0
  88. data/lib/action_view/view_paths.rb +59 -38
  89. data/lib/action_view.rb +7 -2
  90. data/lib/assets/compiled/rails-ujs.js +32 -6
  91. metadata +29 -18
  92. data/lib/action_view/helpers/record_tag_helper.rb +0 -23
@@ -19,10 +19,14 @@ module ActionView
19
19
 
20
20
  # Main render entry point shared by Action View and Action Controller.
21
21
  def render(context, options)
22
+ render_to_object(context, options).body
23
+ end
24
+
25
+ def render_to_object(context, options) # :nodoc:
22
26
  if options.key?(:partial)
23
- render_partial(context, options)
27
+ render_partial_to_object(context, options)
24
28
  else
25
- render_template(context, options)
29
+ render_template_to_object(context, options)
26
30
  end
27
31
  end
28
32
 
@@ -41,16 +45,67 @@ module ActionView
41
45
 
42
46
  # Direct access to template rendering.
43
47
  def render_template(context, options) #:nodoc:
44
- TemplateRenderer.new(@lookup_context).render(context, options)
48
+ render_template_to_object(context, options).body
45
49
  end
46
50
 
47
51
  # Direct access to partial rendering.
48
52
  def render_partial(context, options, &block) #:nodoc:
49
- PartialRenderer.new(@lookup_context).render(context, options, block)
53
+ render_partial_to_object(context, options, &block).body
50
54
  end
51
55
 
52
56
  def cache_hits # :nodoc:
53
57
  @cache_hits ||= {}
54
58
  end
59
+
60
+ def render_template_to_object(context, options) #:nodoc:
61
+ TemplateRenderer.new(@lookup_context).render(context, options)
62
+ end
63
+
64
+ def render_partial_to_object(context, options, &block) #:nodoc:
65
+ partial = options[:partial]
66
+ if String === partial
67
+ collection = collection_from_options(options)
68
+
69
+ if collection
70
+ # Collection + Partial
71
+ renderer = CollectionRenderer.new(@lookup_context, options)
72
+ renderer.render_collection_with_partial(collection, partial, context, block)
73
+ else
74
+ if options.key?(:object)
75
+ # Object + Partial
76
+ renderer = ObjectRenderer.new(@lookup_context, options)
77
+ renderer.render_object_with_partial(options[:object], partial, context, block)
78
+ else
79
+ # Partial
80
+ renderer = PartialRenderer.new(@lookup_context, options)
81
+ renderer.render(partial, context, block)
82
+ end
83
+ end
84
+ else
85
+ collection = collection_from_object(partial) || collection_from_options(options)
86
+
87
+ if collection
88
+ # Collection + Derived Partial
89
+ renderer = CollectionRenderer.new(@lookup_context, options)
90
+ renderer.render_collection_derive_partial(collection, context, block)
91
+ else
92
+ # Object + Derived Partial
93
+ renderer = ObjectRenderer.new(@lookup_context, options)
94
+ renderer.render_object_derive_partial(partial, context, block)
95
+ end
96
+ end
97
+ end
98
+
99
+ private
100
+ def collection_from_options(options)
101
+ if options.key?(:collection)
102
+ collection = options[:collection]
103
+ collection || []
104
+ end
105
+ end
106
+
107
+ def collection_from_object(object)
108
+ object if object.respond_to?(:to_ary)
109
+ end
55
110
  end
56
111
  end
@@ -27,14 +27,13 @@ module ActionView
27
27
  end
28
28
 
29
29
  private
30
-
31
30
  # This is the same logging logic as in ShowExceptions middleware.
32
31
  def log_error(exception)
33
32
  logger = ActionView::Base.logger
34
33
  return unless logger
35
34
 
36
- message = "\n#{exception.class} (#{exception.message}):\n".dup
37
- message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
35
+ message = +"\n#{exception.class} (#{exception.message}):\n"
36
+ message << exception.annotated_source_code.to_s if exception.respond_to?(:annotated_source_code)
38
37
  message << " " << exception.backtrace.join("\n ")
39
38
  logger.fatal("#{message}\n\n")
40
39
  end
@@ -43,19 +42,18 @@ module ActionView
43
42
  # For streaming, instead of rendering a given a template, we return a Body
44
43
  # object that responds to each. This object is initialized with a block
45
44
  # that knows how to render the template.
46
- def render_template(template, layout_name = nil, locals = {}) #:nodoc:
47
- return [super] unless layout_name && template.supports_streaming?
45
+ def render_template(view, template, layout_name = nil, locals = {}) #:nodoc:
46
+ return [super.body] unless layout_name && template.supports_streaming?
48
47
 
49
48
  locals ||= {}
50
49
  layout = layout_name && find_layout(layout_name, locals.keys, [formats.first])
51
50
 
52
51
  Body.new do |buffer|
53
- delayed_render(buffer, template, layout, @view, locals)
52
+ delayed_render(buffer, template, layout, view, locals)
54
53
  end
55
54
  end
56
55
 
57
56
  private
58
-
59
57
  def delayed_render(buffer, template, layout, view, locals)
60
58
  # Wrap the given buffer in the StreamingBuffer and pass it to the
61
59
  # underlying template handler. Now, every time something is concatenated
@@ -64,7 +62,11 @@ module ActionView
64
62
  output = ActionView::StreamingBuffer.new(buffer)
65
63
  yielder = lambda { |*name| view._layout_for(*name) }
66
64
 
67
- instrument(:template, identifier: template.identifier, layout: layout.try(:virtual_path)) do
65
+ ActiveSupport::Notifications.instrument(
66
+ "render_template.action_view",
67
+ identifier: template.identifier,
68
+ layout: layout && layout.virtual_path
69
+ ) do
68
70
  outer_config = I18n.config
69
71
  fiber = Fiber.new do
70
72
  I18n.config = outer_config
@@ -1,23 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/core_ext/object/try"
4
-
5
3
  module ActionView
6
4
  class TemplateRenderer < AbstractRenderer #:nodoc:
7
5
  def render(context, options)
8
- @view = context
9
6
  @details = extract_details(options)
10
7
  template = determine_template(options)
11
8
 
12
- prepend_formats(template.formats)
13
-
14
- @lookup_context.rendered_format ||= (template.formats.first || formats.first)
9
+ prepend_formats(template.format)
15
10
 
16
- render_template(template, options[:layout], options[:locals])
11
+ render_template(context, template, options[:layout], options[:locals] || {})
17
12
  end
18
13
 
19
14
  private
20
-
21
15
  # Determine the template to be rendered using the given options.
22
16
  def determine_template(options)
23
17
  keys = options.has_key?(:locals) ? options[:locals].keys : []
@@ -29,15 +23,26 @@ module ActionView
29
23
  elsif options.key?(:html)
30
24
  Template::HTML.new(options[:html], formats.first)
31
25
  elsif options.key?(:file)
32
- with_fallbacks { find_file(options[:file], nil, false, keys, @details) }
26
+ if File.exist?(options[:file])
27
+ Template::RawFile.new(options[:file])
28
+ else
29
+ raise ArgumentError, "`render file:` should be given the absolute path to a file. '#{options[:file]}' was given instead"
30
+ end
33
31
  elsif options.key?(:inline)
34
32
  handler = Template.handler_for_extension(options[:type] || "erb")
35
- Template.new(options[:inline], "inline template", handler, locals: keys)
33
+ format = if handler.respond_to?(:default_format)
34
+ handler.default_format
35
+ else
36
+ @lookup_context.formats.first
37
+ end
38
+ Template::Inline.new(options[:inline], "inline template", handler, locals: keys, format: format)
39
+ elsif options.key?(:renderable)
40
+ Template::Renderable.new(options[:renderable])
36
41
  elsif options.key?(:template)
37
42
  if options[:template].respond_to?(:render)
38
43
  options[:template]
39
44
  else
40
- find_template(options[:template], options[:prefixes], false, keys, @details)
45
+ @lookup_context.find_template(options[:template], options[:prefixes], false, keys, @details)
41
46
  end
42
47
  else
43
48
  raise ArgumentError, "You invoked render but did not give any of :partial, :template, :inline, :file, :plain, :html or :body option."
@@ -46,27 +51,30 @@ module ActionView
46
51
 
47
52
  # Renders the given template. A string representing the layout can be
48
53
  # supplied as well.
49
- def render_template(template, layout_name = nil, locals = nil)
50
- view, locals = @view, locals || {}
51
-
52
- render_with_layout(layout_name, locals) do |layout|
53
- instrument(:template, identifier: template.identifier, layout: layout.try(:virtual_path)) do
54
+ def render_template(view, template, layout_name, locals)
55
+ render_with_layout(view, template, layout_name, locals) do |layout|
56
+ ActiveSupport::Notifications.instrument(
57
+ "render_template.action_view",
58
+ identifier: template.identifier,
59
+ layout: layout && layout.virtual_path
60
+ ) do
54
61
  template.render(view, locals) { |*name| view._layout_for(*name) }
55
62
  end
56
63
  end
57
64
  end
58
65
 
59
- def render_with_layout(path, locals)
66
+ def render_with_layout(view, template, path, locals)
60
67
  layout = path && find_layout(path, locals.keys, [formats.first])
61
- content = yield(layout)
62
68
 
63
- if layout
64
- view = @view
65
- view.view_flow.set(:layout, content)
66
- layout.render(view, locals) { |*name| view._layout_for(*name) }
69
+ body = if layout
70
+ ActiveSupport::Notifications.instrument("render_layout.action_view", identifier: layout.identifier) do
71
+ view.view_flow.set(:layout, yield(layout))
72
+ layout.render(view, locals) { |*name| view._layout_for(*name) }
73
+ end
67
74
  else
68
- content
75
+ yield
69
76
  end
77
+ build_rendered_template(body, template)
70
78
  end
71
79
 
72
80
  # This is the method which actually finds the layout using details in the lookup
@@ -84,16 +92,16 @@ module ActionView
84
92
  when String
85
93
  begin
86
94
  if layout.start_with?("/")
87
- with_fallbacks { find_template(layout, nil, false, [], details) }
95
+ raise ArgumentError, "Rendering layouts from an absolute path is not supported."
88
96
  else
89
- find_template(layout, nil, false, [], details)
97
+ @lookup_context.find_template(layout, nil, false, [], details)
90
98
  end
91
99
  rescue ActionView::MissingTemplate
92
100
  all_details = @details.merge(formats: @lookup_context.default_formats)
93
- raise unless template_exists?(layout, nil, false, [], all_details)
101
+ raise unless template_exists?(layout, nil, false, [], **all_details)
94
102
  end
95
103
  when Proc
96
- resolve_layout(layout.call(formats), keys, formats)
104
+ resolve_layout(layout.call(@lookup_context, formats), keys, formats)
97
105
  else
98
106
  layout
99
107
  end
@@ -26,7 +26,14 @@ module ActionView
26
26
  extend ActiveSupport::Concern
27
27
  include ActionView::ViewPaths
28
28
 
29
- # Overwrite process to setup I18n proxy.
29
+ attr_reader :rendered_format
30
+
31
+ def initialize
32
+ @rendered_format = nil
33
+ super
34
+ end
35
+
36
+ # Overwrite process to set up I18n proxy.
30
37
  def process(*) #:nodoc:
31
38
  old_config, I18n.config = I18n.config, I18nProxy.new(I18n.config, lookup_context)
32
39
  super
@@ -35,47 +42,59 @@ module ActionView
35
42
  end
36
43
 
37
44
  module ClassMethods
38
- def view_context_class
39
- @view_context_class ||= begin
40
- supports_path = supports_path?
41
- routes = respond_to?(:_routes) && _routes
42
- helpers = respond_to?(:_helpers) && _helpers
43
-
44
- Class.new(ActionView::Base) do
45
- if routes
46
- include routes.url_helpers(supports_path)
47
- include routes.mounted_helpers
48
- end
49
-
50
- if helpers
51
- include helpers
52
- end
45
+ def _routes
46
+ end
47
+
48
+ def _helpers
49
+ end
50
+
51
+ def build_view_context_class(klass, supports_path, routes, helpers)
52
+ Class.new(klass) do
53
+ if routes
54
+ include routes.url_helpers(supports_path)
55
+ include routes.mounted_helpers
56
+ end
57
+
58
+ if helpers
59
+ include helpers
53
60
  end
54
61
  end
55
62
  end
56
- end
57
63
 
58
- attr_internal_writer :view_context_class
64
+ def view_context_class
65
+ klass = ActionView::LookupContext::DetailsKey.view_context_class(ActionView::Base)
66
+
67
+ @view_context_class ||= build_view_context_class(klass, supports_path?, _routes, _helpers)
68
+
69
+ if klass.changed?(@view_context_class)
70
+ @view_context_class = build_view_context_class(klass, supports_path?, _routes, _helpers)
71
+ end
72
+
73
+ @view_context_class
74
+ end
75
+ end
59
76
 
60
77
  def view_context_class
61
- @_view_context_class ||= self.class.view_context_class
78
+ self.class.view_context_class
62
79
  end
63
80
 
64
81
  # An instance of a view class. The default view class is ActionView::Base.
65
82
  #
66
83
  # The view class must have the following methods:
67
- # View.new[lookup_context, assigns, controller]
68
- # Create a new ActionView instance for a controller and we can also pass the arguments.
69
- # View#render(option)
70
- # Returns String with the rendered template
84
+ #
85
+ # * <tt>View.new(lookup_context, assigns, controller)</tt> Create a new
86
+ # ActionView instance for a controller and we can also pass the arguments.
87
+ #
88
+ # * <tt>View#render(option)</tt> — Returns String with the rendered template.
71
89
  #
72
90
  # Override this method in a module to change the default behavior.
73
91
  def view_context
74
- view_context_class.new(view_renderer, view_assigns, self)
92
+ view_context_class.new(lookup_context, view_assigns, self)
75
93
  end
76
94
 
77
95
  # Returns an object that is able to render templates.
78
96
  def view_renderer # :nodoc:
97
+ # Lifespan: Per controller
79
98
  @_view_renderer ||= ActionView::Renderer.new(lookup_context)
80
99
  end
81
100
 
@@ -84,12 +103,7 @@ module ActionView
84
103
  _render_template(options)
85
104
  end
86
105
 
87
- def rendered_format
88
- Template::Types[lookup_context.rendered_format]
89
- end
90
-
91
106
  private
92
-
93
107
  # Find and render a template based on the options given.
94
108
  def _render_template(options)
95
109
  variant = options.delete(:variant)
@@ -97,17 +111,22 @@ module ActionView
97
111
  context = view_context
98
112
 
99
113
  context.assign assigns if assigns
100
- lookup_context.rendered_format = nil if options[:formats]
101
114
  lookup_context.variants = variant if variant
102
115
 
103
- view_renderer.render(context, options)
116
+ rendered_template = context.in_rendering_context(options) do |renderer|
117
+ renderer.render_to_object(context, options)
118
+ end
119
+
120
+ rendered_format = rendered_template.format || lookup_context.formats.first
121
+ @rendered_format = Template::Types[rendered_format]
122
+
123
+ rendered_template.body
104
124
  end
105
125
 
106
126
  # Assign the rendered format to look up context.
107
127
  def _process_format(format)
108
128
  super
109
- lookup_context.formats = [format.to_sym]
110
- lookup_context.rendered_format = lookup_context.formats.first
129
+ lookup_context.formats = [format.to_sym] if format.to_sym
111
130
  end
112
131
 
113
132
  # Normalize args by converting render "foo" to render :action => "foo" and
@@ -125,6 +144,8 @@ module ActionView
125
144
  else
126
145
  if action.respond_to?(:permitted?) && action.permitted?
127
146
  options = action
147
+ elsif action.respond_to?(:render_in)
148
+ options[:renderable] = action
128
149
  else
129
150
  options[:partial] = action
130
151
  end
@@ -84,29 +84,28 @@ module ActionView
84
84
  super(only_path: _generate_paths_by_default)
85
85
  when Hash
86
86
  options = options.symbolize_keys
87
- unless options.key?(:only_path)
88
- options[:only_path] = only_path?(options[:host])
89
- end
87
+ ensure_only_path_option(options)
90
88
 
91
89
  super(options)
92
90
  when ActionController::Parameters
93
- unless options.key?(:only_path)
94
- options[:only_path] = only_path?(options[:host])
95
- end
91
+ ensure_only_path_option(options)
96
92
 
97
93
  super(options)
98
94
  when :back
99
95
  _back_url
100
96
  when Array
101
97
  components = options.dup
102
- if _generate_paths_by_default
103
- polymorphic_path(components, components.extract_options!)
98
+ options = components.extract_options!
99
+ ensure_only_path_option(options)
100
+
101
+ if options[:only_path]
102
+ polymorphic_path(components, options)
104
103
  else
105
- polymorphic_url(components, components.extract_options!)
104
+ polymorphic_url(components, options)
106
105
  end
107
106
  else
108
107
  method = _generate_paths_by_default ? :path : :url
109
- builder = ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.send(method)
108
+ builder = ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.public_send(method)
110
109
 
111
110
  case options
112
111
  when Symbol
@@ -138,8 +137,10 @@ module ActionView
138
137
  true
139
138
  end
140
139
 
141
- def only_path?(host)
142
- _generate_paths_by_default unless host
140
+ def ensure_only_path_option(options)
141
+ unless options.key?(:only_path)
142
+ options[:only_path] = _generate_paths_by_default unless options[:host]
143
+ end
143
144
  end
144
145
  end
145
146
  end
@@ -81,19 +81,19 @@ module ActionView
81
81
  end
82
82
  end
83
83
 
84
- def source_extract(indentation = 0, output = :console)
85
- return unless num = line_number
84
+ def source_extract(indentation = 0)
85
+ return [] unless num = line_number
86
86
  num = num.to_i
87
87
 
88
- source_code = @template.source.split("\n")
88
+ source_code = @template.encode!.split("\n")
89
89
 
90
90
  start_on_line = [ num - SOURCE_CODE_RADIUS - 1, 0 ].max
91
91
  end_on_line = [ num + SOURCE_CODE_RADIUS - 1, source_code.length].min
92
92
 
93
93
  indent = end_on_line.to_s.size + indentation
94
- return unless source_code = source_code[start_on_line..end_on_line]
94
+ return [] unless source_code = source_code[start_on_line..end_on_line]
95
95
 
96
- formatted_code_for(source_code, start_on_line, indent, output)
96
+ formatted_code_for(source_code, start_on_line, indent)
97
97
  end
98
98
 
99
99
  def sub_template_of(template_path)
@@ -109,12 +109,11 @@ module ActionView
109
109
  end
110
110
  end
111
111
 
112
- def annoted_source_code
112
+ def annotated_source_code
113
113
  source_extract(4)
114
114
  end
115
115
 
116
116
  private
117
-
118
117
  def source_location
119
118
  if line_number
120
119
  "on line ##{line_number} of "
@@ -123,19 +122,35 @@ module ActionView
123
122
  end + file_name
124
123
  end
125
124
 
126
- def formatted_code_for(source_code, line_counter, indent, output)
127
- start_value = (output == :html) ? {} : []
128
- source_code.inject(start_value) do |result, line|
125
+ def formatted_code_for(source_code, line_counter, indent)
126
+ indent_template = "%#{indent}s: %s"
127
+ source_code.map do |line|
129
128
  line_counter += 1
130
- if output == :html
131
- result.update(line_counter.to_s => "%#{indent}s %s\n" % ["", line])
132
- else
133
- result << "%#{indent}s: %s" % [line_counter, line]
134
- end
129
+ indent_template % [line_counter, line]
135
130
  end
136
131
  end
137
132
  end
138
133
  end
139
134
 
140
135
  TemplateError = Template::Error
136
+
137
+ class SyntaxErrorInTemplate < TemplateError #:nodoc
138
+ def initialize(template, offending_code_string)
139
+ @offending_code_string = offending_code_string
140
+ super(template)
141
+ end
142
+
143
+ def message
144
+ <<~MESSAGE
145
+ Encountered a syntax error while rendering template: check #{@offending_code_string}
146
+ MESSAGE
147
+ end
148
+
149
+ def annotated_source_code
150
+ @offending_code_string.split("\n").map.with_index(1) { |line, index|
151
+ indentation = " " * 4
152
+ "#{index}:#{indentation}#{line}"
153
+ }
154
+ end
155
+ end
141
156
  end
@@ -5,11 +5,11 @@ module ActionView
5
5
  class Builder
6
6
  class_attribute :default_format, default: :xml
7
7
 
8
- def call(template)
8
+ def call(template, source)
9
9
  require_engine
10
10
  "xml = ::Builder::XmlMarkup.new(:indent => 2);" \
11
11
  "self.output_buffer = xml.target!;" +
12
- template.source +
12
+ source +
13
13
  ";xml.target!;"
14
14
  end
15
15
 
@@ -13,17 +13,23 @@ module ActionView
13
13
 
14
14
  # Dup properties so that we don't modify argument
15
15
  properties = Hash[properties]
16
- properties[:preamble] = "@output_buffer = output_buffer || ActionView::OutputBuffer.new;"
17
- properties[:postamble] = "@output_buffer.to_s"
18
- properties[:bufvar] = "@output_buffer"
16
+
17
+ properties[:bufvar] ||= "@output_buffer"
18
+ properties[:preamble] ||= ""
19
+ properties[:postamble] ||= "#{properties[:bufvar]}.to_s"
20
+
19
21
  properties[:escapefunc] = ""
20
22
 
21
23
  super
22
24
  end
23
25
 
24
26
  def evaluate(action_view_erb_handler_context)
25
- pr = eval("proc { #{@src} }", binding, @filename || "(erubi)")
26
- action_view_erb_handler_context.instance_eval(&pr)
27
+ src = @src
28
+ view = Class.new(ActionView::Base) {
29
+ include action_view_erb_handler_context._routes.url_helpers
30
+ class_eval("define_method(:_template) { |local_assigns, output_buffer| #{src} }", defined?(@filename) ? @filename : "(erubi)", 0)
31
+ }.empty
32
+ view._run(:_template, nil, {}, ActionView::OutputBuffer.new)
27
33
  end
28
34
 
29
35
  private
@@ -33,7 +39,7 @@ module ActionView
33
39
  if text == "\n"
34
40
  @newline_pending += 1
35
41
  else
36
- src << "@output_buffer.safe_append='"
42
+ src << bufvar << ".safe_append='"
37
43
  src << "\n" * @newline_pending if @newline_pending > 0
38
44
  src << text.gsub(/['\\]/, '\\\\\&')
39
45
  src << "'.freeze;"
@@ -48,9 +54,9 @@ module ActionView
48
54
  flush_newline_if_pending(src)
49
55
 
50
56
  if (indicator == "==") || @escape
51
- src << "@output_buffer.safe_expr_append="
57
+ src << bufvar << ".safe_expr_append="
52
58
  else
53
- src << "@output_buffer.append="
59
+ src << bufvar << ".append="
54
60
  end
55
61
 
56
62
  if BLOCK_EXPR.match?(code)
@@ -72,7 +78,7 @@ module ActionView
72
78
 
73
79
  def flush_newline_if_pending(src)
74
80
  if @newline_pending > 0
75
- src << "@output_buffer.safe_append='#{"\n" * @newline_pending}'.freeze;"
81
+ src << bufvar << ".safe_append='#{"\n" * @newline_pending}'.freeze;"
76
82
  @newline_pending = 0
77
83
  end
78
84
  end
@@ -14,12 +14,12 @@ module ActionView
14
14
  class_attribute :erb_implementation, default: Erubi
15
15
 
16
16
  # Do not escape templates of these mime types.
17
- class_attribute :escape_whitelist, default: ["text/plain"]
17
+ class_attribute :escape_ignore_list, default: ["text/plain"]
18
18
 
19
19
  ENCODING_TAG = Regexp.new("\\A(<%#{ENCODING_FLAG}-?%>)[ \\t]*")
20
20
 
21
- def self.call(template)
22
- new.call(template)
21
+ def self.call(template, source)
22
+ new.call(template, source)
23
23
  end
24
24
 
25
25
  def supports_streaming?
@@ -30,30 +30,35 @@ module ActionView
30
30
  true
31
31
  end
32
32
 
33
- def call(template)
33
+ def call(template, source)
34
34
  # First, convert to BINARY, so in case the encoding is
35
35
  # wrong, we can still find an encoding tag
36
36
  # (<%# encoding %>) inside the String using a regular
37
37
  # expression
38
- template_source = template.source.dup.force_encoding(Encoding::ASCII_8BIT)
38
+ template_source = source.b
39
39
 
40
40
  erb = template_source.gsub(ENCODING_TAG, "")
41
41
  encoding = $2
42
42
 
43
- erb.force_encoding valid_encoding(template.source.dup, encoding)
43
+ erb.force_encoding valid_encoding(source.dup, encoding)
44
44
 
45
45
  # Always make sure we return a String in the default_internal
46
46
  erb.encode!
47
47
 
48
- self.class.erb_implementation.new(
49
- erb,
50
- escape: (self.class.escape_whitelist.include? template.type),
48
+ options = {
49
+ escape: (self.class.escape_ignore_list.include? template.type),
51
50
  trim: (self.class.erb_trim_mode == "-")
52
- ).src
51
+ }
52
+
53
+ if ActionView::Base.annotate_rendered_view_with_filenames && template.format == :html
54
+ options[:preamble] = "@output_buffer.safe_append='<!-- BEGIN #{template.short_identifier} -->';"
55
+ options[:postamble] = "@output_buffer.safe_append='<!-- END #{template.short_identifier} -->';@output_buffer.to_s"
56
+ end
57
+
58
+ self.class.erb_implementation.new(erb, options).src
53
59
  end
54
60
 
55
61
  private
56
-
57
62
  def valid_encoding(string, encoding)
58
63
  # If a magic encoding comment was found, tag the
59
64
  # String with this encoding. This is for a case