actionview 7.0.8.6 → 7.1.0.beta1

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.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +235 -387
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  5. data/app/assets/javascripts/rails-ujs.esm.js +668 -0
  6. data/app/assets/javascripts/rails-ujs.js +606 -0
  7. data/lib/action_view/base.rb +28 -7
  8. data/lib/action_view/buffers.rb +106 -8
  9. data/lib/action_view/cache_expiry.rb +40 -43
  10. data/lib/action_view/context.rb +1 -1
  11. data/lib/action_view/deprecator.rb +7 -0
  12. data/lib/action_view/digestor.rb +1 -1
  13. data/lib/action_view/gem_version.rb +4 -4
  14. data/lib/action_view/helpers/active_model_helper.rb +1 -1
  15. data/lib/action_view/helpers/asset_tag_helper.rb +130 -46
  16. data/lib/action_view/helpers/asset_url_helper.rb +6 -5
  17. data/lib/action_view/helpers/atom_feed_helper.rb +5 -5
  18. data/lib/action_view/helpers/cache_helper.rb +3 -9
  19. data/lib/action_view/helpers/capture_helper.rb +24 -10
  20. data/lib/action_view/helpers/content_exfiltration_prevention_helper.rb +70 -0
  21. data/lib/action_view/helpers/controller_helper.rb +6 -0
  22. data/lib/action_view/helpers/csp_helper.rb +2 -2
  23. data/lib/action_view/helpers/csrf_helper.rb +2 -2
  24. data/lib/action_view/helpers/date_helper.rb +17 -19
  25. data/lib/action_view/helpers/debug_helper.rb +3 -3
  26. data/lib/action_view/helpers/form_helper.rb +43 -18
  27. data/lib/action_view/helpers/form_options_helper.rb +2 -1
  28. data/lib/action_view/helpers/form_tag_helper.rb +43 -9
  29. data/lib/action_view/helpers/javascript_helper.rb +1 -0
  30. data/lib/action_view/helpers/number_helper.rb +2 -1
  31. data/lib/action_view/helpers/output_safety_helper.rb +2 -2
  32. data/lib/action_view/helpers/rendering_helper.rb +1 -1
  33. data/lib/action_view/helpers/sanitize_helper.rb +33 -14
  34. data/lib/action_view/helpers/tag_helper.rb +5 -27
  35. data/lib/action_view/helpers/tags/base.rb +11 -52
  36. data/lib/action_view/helpers/tags/collection_check_boxes.rb +1 -0
  37. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +1 -0
  38. data/lib/action_view/helpers/tags/collection_select.rb +3 -0
  39. data/lib/action_view/helpers/tags/date_field.rb +1 -1
  40. data/lib/action_view/helpers/tags/date_select.rb +2 -0
  41. data/lib/action_view/helpers/tags/datetime_field.rb +14 -6
  42. data/lib/action_view/helpers/tags/datetime_local_field.rb +11 -2
  43. data/lib/action_view/helpers/tags/grouped_collection_select.rb +3 -0
  44. data/lib/action_view/helpers/tags/month_field.rb +1 -1
  45. data/lib/action_view/helpers/tags/select.rb +3 -0
  46. data/lib/action_view/helpers/tags/select_renderer.rb +56 -0
  47. data/lib/action_view/helpers/tags/time_field.rb +1 -1
  48. data/lib/action_view/helpers/tags/time_zone_select.rb +3 -0
  49. data/lib/action_view/helpers/tags/week_field.rb +1 -1
  50. data/lib/action_view/helpers/tags/weekday_select.rb +3 -0
  51. data/lib/action_view/helpers/tags.rb +2 -0
  52. data/lib/action_view/helpers/text_helper.rb +32 -16
  53. data/lib/action_view/helpers/translation_helper.rb +3 -3
  54. data/lib/action_view/helpers/url_helper.rb +41 -14
  55. data/lib/action_view/helpers.rb +2 -0
  56. data/lib/action_view/layouts.rb +4 -2
  57. data/lib/action_view/log_subscriber.rb +49 -32
  58. data/lib/action_view/lookup_context.rb +29 -13
  59. data/lib/action_view/path_registry.rb +57 -0
  60. data/lib/action_view/path_set.rb +13 -14
  61. data/lib/action_view/railtie.rb +26 -3
  62. data/lib/action_view/record_identifier.rb +15 -8
  63. data/lib/action_view/renderer/abstract_renderer.rb +1 -1
  64. data/lib/action_view/renderer/collection_renderer.rb +9 -1
  65. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +2 -1
  66. data/lib/action_view/renderer/partial_renderer.rb +2 -1
  67. data/lib/action_view/renderer/renderer.rb +2 -0
  68. data/lib/action_view/renderer/streaming_template_renderer.rb +3 -2
  69. data/lib/action_view/renderer/template_renderer.rb +3 -2
  70. data/lib/action_view/rendering.rb +22 -4
  71. data/lib/action_view/ripper_ast_parser.rb +6 -6
  72. data/lib/action_view/template/error.rb +14 -1
  73. data/lib/action_view/template/handlers/builder.rb +4 -4
  74. data/lib/action_view/template/handlers/erb/erubi.rb +23 -27
  75. data/lib/action_view/template/handlers/erb.rb +73 -1
  76. data/lib/action_view/template/handlers.rb +1 -1
  77. data/lib/action_view/template/html.rb +1 -1
  78. data/lib/action_view/template/raw_file.rb +1 -1
  79. data/lib/action_view/template/renderable.rb +1 -1
  80. data/lib/action_view/template/resolver.rb +10 -2
  81. data/lib/action_view/template/text.rb +1 -1
  82. data/lib/action_view/template/types.rb +25 -34
  83. data/lib/action_view/template.rb +179 -52
  84. data/lib/action_view/template_path.rb +2 -0
  85. data/lib/action_view/test_case.rb +8 -5
  86. data/lib/action_view/unbound_template.rb +15 -5
  87. data/lib/action_view/version.rb +1 -1
  88. data/lib/action_view/view_paths.rb +15 -24
  89. data/lib/action_view.rb +4 -1
  90. metadata +29 -29
@@ -13,14 +13,14 @@ module ActionView # :nodoc:
13
13
 
14
14
  attr_reader :paths
15
15
 
16
- delegate :[], :include?, :pop, :size, :each, to: :paths
16
+ delegate :[], :include?, :size, :each, to: :paths
17
17
 
18
18
  def initialize(paths = [])
19
- @paths = typecast paths
19
+ @paths = typecast(paths).freeze
20
20
  end
21
21
 
22
22
  def initialize_copy(other)
23
- @paths = other.paths.dup
23
+ @paths = other.paths.dup.freeze
24
24
  self
25
25
  end
26
26
 
@@ -32,18 +32,11 @@ module ActionView # :nodoc:
32
32
  PathSet.new paths.compact
33
33
  end
34
34
 
35
- def +(array)
35
+ def +(other)
36
+ array = Array === other ? other : other.paths
36
37
  PathSet.new(paths + array)
37
38
  end
38
39
 
39
- %w(<< concat push insert unshift).each do |method|
40
- class_eval <<-METHOD, __FILE__, __LINE__ + 1
41
- def #{method}(*args)
42
- paths.#{method}(*typecast(args))
43
- end
44
- METHOD
45
- end
46
-
47
40
  def find(path, prefixes, partial, details, details_key, locals)
48
41
  find_all(path, prefixes, partial, details, details_key, locals).first ||
49
42
  raise(MissingTemplate.new(self, path, prefixes, partial, details, details_key, locals))
@@ -75,9 +68,15 @@ module ActionView # :nodoc:
75
68
  paths.map do |path|
76
69
  case path
77
70
  when Pathname, String
78
- FileSystemResolver.new path.to_s
79
- else
71
+ # This path should only be reached by "direct" users of
72
+ # ActionView::Base (not using the ViewPaths or Renderer modules).
73
+ # We can't cache/de-dup the file system resolver in this case as we
74
+ # don't know which compiled_method_container we'll be rendering to.
75
+ FileSystemResolver.new(path)
76
+ when Resolver
80
77
  path
78
+ else
79
+ raise TypeError, "#{path.inspect} is not a valid path: must be a String, Pathname, or Resolver"
81
80
  end
82
81
  end
83
82
  end
@@ -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.cache_classes
93
+ ActionView::Resolver.caching = !app.config.reloading_enabled?
78
94
  end
79
95
  end
80
96
  end
@@ -91,13 +107,20 @@ 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.cache_classes
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
- app.executor.register_hook ActionView::CacheExpiry::Executor.new(watcher: app.config.file_watcher)
116
+ view_reloader = ActionView::CacheExpiry::ViewReloader.new(watcher: app.config.file_watcher)
117
+
118
+ app.reloaders << view_reloader
119
+ view_reloader.execute
120
+ app.reloader.to_run do
121
+ require_unload_lock!
122
+ view_reloader.execute
123
+ end
101
124
  end
102
125
  end
103
126
 
@@ -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)) # => "post_45"
83
- # dom_id(Post.new) # => "new_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.new, :custom) # => "custom_post"
89
- def dom_id(record, prefix = nil)
90
- if record_id = record_key_for_dom_id(record)
91
- "#{dom_class(record, prefix)}#{JOIN}#{record_id}"
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(record, prefix || NEW)
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) : key
115
+ key && key.all? ? key.join(JOIN) : nil
109
116
  end
110
117
  end
111
118
  end
@@ -31,7 +31,7 @@ module ActionView
31
31
 
32
32
  module ObjectRendering # :nodoc:
33
33
  PREFIXED_PARTIAL_NAMES = Concurrent::Map.new do |h, k|
34
- h[k] = Concurrent::Map.new
34
+ h.compute_if_absent(k) { Concurrent::Map.new }
35
35
  end
36
36
 
37
37
  def initialize(lookup_context, options)
@@ -51,6 +51,10 @@ module ActionView
51
51
  def length
52
52
  @collection.respond_to?(:length) ? @collection.length : size
53
53
  end
54
+
55
+ def preload!
56
+ # no-op
57
+ end
54
58
  end
55
59
 
56
60
  class SameCollectionIterator < CollectionIterator # :nodoc:
@@ -84,9 +88,13 @@ module ActionView
84
88
 
85
89
  def each_with_info
86
90
  return super unless block_given?
87
- @relation.preload_associations(@collection)
91
+ preload!
88
92
  super
89
93
  end
94
+
95
+ def preload!
96
+ @relation.preload_associations(@collection)
97
+ end
90
98
  end
91
99
 
92
100
  class MixedCollectionIterator < CollectionIterator # :nodoc:
@@ -59,6 +59,7 @@ module ActionView
59
59
  seed = callable_cache_key? ? @options[:cached] : ->(i) { i }
60
60
 
61
61
  digest_path = view.digest_path_from_template(template)
62
+ collection.preload! if callable_cache_key?
62
63
 
63
64
  collection.each_with_object([{}, []]) do |item, (hash, ordered_keys)|
64
65
  key = expanded_cache_key(seed.call(item), view, template, digest_path)
@@ -100,7 +101,7 @@ module ActionView
100
101
  # We want to cache buffers as raw strings. This both improve performance and
101
102
  # avoid creating forward compatibility issues with the internal representation
102
103
  # of these two types.
103
- if body.is_a?(ActiveSupport::SafeBuffer)
104
+ if body.is_a?(ActionView::OutputBuffer) || body.is_a?(ActiveSupport::SafeBuffer)
104
105
  body = body.to_str
105
106
  end
106
107
 
@@ -246,7 +246,8 @@ module ActionView
246
246
  ActiveSupport::Notifications.instrument(
247
247
  "render_partial.action_view",
248
248
  identifier: template.identifier,
249
- layout: layout && layout.virtual_path
249
+ layout: layout && layout.virtual_path,
250
+ locals: locals
250
251
  ) do |payload|
251
252
  content = template.render(view, locals, add_to_stack: !block) do |*name|
252
253
  view._layout_for(*name, &block)
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionView
4
+ # = Action View \Renderer
5
+ #
4
6
  # This is the main entry point for rendering. It basically delegates
5
7
  # to other objects like TemplateRenderer and PartialRenderer which
6
8
  # actually renders the template.
@@ -46,7 +46,7 @@ module ActionView
46
46
  return [super.body] unless layout_name && template.supports_streaming?
47
47
 
48
48
  locals ||= {}
49
- layout = layout_name && find_layout(layout_name, locals.keys, [formats.first])
49
+ layout = find_layout(layout_name, locals.keys, [formats.first])
50
50
 
51
51
  Body.new do |buffer|
52
52
  delayed_render(buffer, template, layout, view, locals)
@@ -65,7 +65,8 @@ module ActionView
65
65
  ActiveSupport::Notifications.instrument(
66
66
  "render_template.action_view",
67
67
  identifier: template.identifier,
68
- layout: layout && layout.virtual_path
68
+ layout: layout && layout.virtual_path,
69
+ locals: locals
69
70
  ) do
70
71
  outer_config = I18n.config
71
72
  fiber = Fiber.new do
@@ -49,7 +49,7 @@ module ActionView
49
49
  @lookup_context.find_template(options[:template], options[:prefixes], false, keys, @details)
50
50
  end
51
51
  else
52
- raise ArgumentError, "You invoked render but did not give any of :partial, :template, :inline, :file, :plain, :html or :body option."
52
+ raise ArgumentError, "You invoked render but did not give any of :body, :file, :html, :inline, :partial, :plain, :renderable, or :template option."
53
53
  end
54
54
  end
55
55
 
@@ -60,7 +60,8 @@ module ActionView
60
60
  ActiveSupport::Notifications.instrument(
61
61
  "render_template.action_view",
62
62
  identifier: template.identifier,
63
- layout: layout && layout.virtual_path
63
+ layout: layout && layout.virtual_path,
64
+ locals: locals
64
65
  ) do
65
66
  template.render(view, locals) { |*name| view._layout_for(*name) }
66
67
  end
@@ -10,7 +10,8 @@ module ActionView
10
10
 
11
11
  def initialize(original_config, lookup_context)
12
12
  original_config = original_config.original_config if original_config.respond_to?(:original_config)
13
- @original_config, @lookup_context = original_config, lookup_context
13
+ @original_config = original_config
14
+ @lookup_context = lookup_context
14
15
  end
15
16
 
16
17
  def locale
@@ -48,7 +49,18 @@ module ActionView
48
49
  def _helpers
49
50
  end
50
51
 
52
+ def inherit_view_context_class?
53
+ superclass.respond_to?(:view_context_class) &&
54
+ supports_path? == superclass.supports_path? &&
55
+ _routes.equal?(superclass._routes) &&
56
+ _helpers.equal?(superclass._helpers)
57
+ end
58
+
51
59
  def build_view_context_class(klass, supports_path, routes, helpers)
60
+ if inherit_view_context_class?
61
+ return superclass.view_context_class
62
+ end
63
+
52
64
  Class.new(klass) do
53
65
  if routes
54
66
  include routes.url_helpers(supports_path)
@@ -61,8 +73,14 @@ module ActionView
61
73
  end
62
74
  end
63
75
 
76
+ def eager_load!
77
+ super
78
+ view_context_class
79
+ nil
80
+ end
81
+
64
82
  def view_context_class
65
- klass = ActionView::LookupContext::DetailsKey.view_context_class(ActionView::Base)
83
+ klass = ActionView::LookupContext::DetailsKey.view_context_class
66
84
 
67
85
  @view_context_class ||= build_view_context_class(klass, supports_path?, _routes, _helpers)
68
86
 
@@ -129,8 +147,8 @@ module ActionView
129
147
  lookup_context.formats = [format.to_sym] if format.to_sym
130
148
  end
131
149
 
132
- # Normalize args by converting render "foo" to render :action => "foo" and
133
- # render "foo/bar" to render :template => "foo/bar".
150
+ # Normalize args by converting render "foo" to render action: "foo" and
151
+ # render "foo/bar" to render template: "foo/bar".
134
152
  def _normalize_args(action = nil, options = {})
135
153
  options = super(action, options)
136
154
  case action
@@ -63,7 +63,7 @@ module ActionView
63
63
  end
64
64
 
65
65
  def call_method_name
66
- self.last.first
66
+ self[2].first
67
67
  end
68
68
 
69
69
  def to_string
@@ -86,11 +86,11 @@ module ActionView
86
86
  end
87
87
 
88
88
  def hash_from_body(body)
89
- body.map do |hash_node|
89
+ body.to_h do |hash_node|
90
90
  return nil if hash_node.type != :assoc_new
91
91
 
92
92
  [hash_node[0], hash_node[1]]
93
- end.to_h
93
+ end
94
94
  end
95
95
 
96
96
  def symbol?
@@ -179,7 +179,7 @@ module ActionView
179
179
  end
180
180
 
181
181
  def on_paren(content)
182
- content.size == 1 ? content.first : content
182
+ content
183
183
  end
184
184
  end
185
185
 
@@ -189,9 +189,9 @@ module ActionView
189
189
  parser = RenderCallExtractor.new(code)
190
190
  parser.parse
191
191
 
192
- parser.render_calls.group_by(&:first).collect do |method, nodes|
192
+ parser.render_calls.group_by(&:first).to_h do |method, nodes|
193
193
  [ method.to_sym, nodes.collect { |v| v[1] } ]
194
- end.to_h
194
+ end
195
195
  end
196
196
  end
197
197
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/core_ext/enumerable"
4
+ require "active_support/syntax_error_proxy"
4
5
 
5
6
  module ActionView
6
7
  # = Action View Errors
@@ -156,13 +157,25 @@ module ActionView
156
157
  # Override to prevent #cause resetting during re-raise.
157
158
  attr_reader :cause
158
159
 
160
+ attr_reader :template
161
+
159
162
  def initialize(template)
160
163
  super($!.message)
161
- set_backtrace($!.backtrace)
162
164
  @cause = $!
165
+ if @cause.is_a?(SyntaxError)
166
+ @cause = ActiveSupport::SyntaxErrorProxy.new(@cause)
167
+ end
163
168
  @template, @sub_templates = template, nil
164
169
  end
165
170
 
171
+ def backtrace
172
+ @cause.backtrace
173
+ end
174
+
175
+ def backtrace_locations
176
+ @cause.backtrace_locations
177
+ end
178
+
166
179
  def file_name
167
180
  @template.identifier
168
181
  end
@@ -7,10 +7,10 @@ module ActionView
7
7
 
8
8
  def call(template, source)
9
9
  require_engine
10
- "xml = ::Builder::XmlMarkup.new(:indent => 2);" \
11
- "self.output_buffer = xml.target!;" +
12
- source +
13
- ";xml.target!;"
10
+ # the double assignment is to silence "assigned but unused variable" warnings
11
+ "xml = xml = ::Builder::XmlMarkup.new(indent: 2, target: output_buffer.raw);" \
12
+ "#{source};" \
13
+ "output_buffer.to_s"
14
14
  end
15
15
 
16
16
  private
@@ -16,22 +16,16 @@ module ActionView
16
16
 
17
17
  properties[:bufvar] ||= "@output_buffer"
18
18
  properties[:preamble] ||= ""
19
- properties[:postamble] ||= "#{properties[:bufvar]}.to_s"
19
+ properties[:postamble] ||= "#{properties[:bufvar]}"
20
+
21
+ # Tell Eruby that whether template will be compiled with `frozen_string_literal: true`
22
+ properties[:freeze_template_literals] = !Template.frozen_string_literal
20
23
 
21
24
  properties[:escapefunc] = ""
22
25
 
23
26
  super
24
27
  end
25
28
 
26
- def evaluate(action_view_erb_handler_context)
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)
33
- end
34
-
35
29
  private
36
30
  def add_text(text)
37
31
  return if text.empty?
@@ -39,30 +33,32 @@ module ActionView
39
33
  if text == "\n"
40
34
  @newline_pending += 1
41
35
  else
42
- src << bufvar << ".safe_append='"
43
- src << "\n" * @newline_pending if @newline_pending > 0
44
- src << text.gsub(/['\\]/, '\\\\\&')
45
- src << "'.freeze;"
46
-
36
+ with_buffer do
37
+ src << ".safe_append='"
38
+ src << "\n" * @newline_pending if @newline_pending > 0
39
+ src << text.gsub(/['\\]/, '\\\\\&') << @text_end
40
+ end
47
41
  @newline_pending = 0
48
42
  end
49
43
  end
50
44
 
51
- BLOCK_EXPR = /\s*((\s+|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/
45
+ BLOCK_EXPR = /((\s|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/
52
46
 
53
47
  def add_expression(indicator, code)
54
48
  flush_newline_if_pending(src)
55
49
 
56
- if (indicator == "==") || @escape
57
- src << bufvar << ".safe_expr_append="
58
- else
59
- src << bufvar << ".append="
60
- end
61
-
62
- if BLOCK_EXPR.match?(code)
63
- src << " " << code
64
- else
65
- src << "(" << code << ");"
50
+ with_buffer do
51
+ if (indicator == "==") || @escape
52
+ src << ".safe_expr_append="
53
+ else
54
+ src << ".append="
55
+ end
56
+
57
+ if BLOCK_EXPR.match?(code)
58
+ src << " " << code
59
+ else
60
+ src << "(" << code << ")"
61
+ end
66
62
  end
67
63
  end
68
64
 
@@ -78,7 +74,7 @@ module ActionView
78
74
 
79
75
  def flush_newline_if_pending(src)
80
76
  if @newline_pending > 0
81
- src << bufvar << ".safe_append='#{"\n" * @newline_pending}'.freeze;"
77
+ with_buffer { src << ".safe_append='#{"\n" * @newline_pending}" << @text_end }
82
78
  @newline_pending = 0
83
79
  end
84
80
  end
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "strscan"
4
+ require "active_support/core_ext/erb/util"
5
+
3
6
  module ActionView
4
7
  class Template
5
8
  module Handlers
@@ -21,6 +24,8 @@ module ActionView
21
24
 
22
25
  ENCODING_TAG = Regexp.new("\\A(<%#{ENCODING_FLAG}-?%>)[ \\t]*")
23
26
 
27
+ LocationParsingError = Class.new(StandardError) # :nodoc:
28
+
24
29
  def self.call(template, source)
25
30
  new.call(template, source)
26
31
  end
@@ -33,6 +38,26 @@ module ActionView
33
38
  true
34
39
  end
35
40
 
41
+ # Translate an error location returned by ErrorHighlight to the correct
42
+ # source location inside the template.
43
+ def translate_location(spot, backtrace_location, source)
44
+ # Tokenize the source line
45
+ tokens = ::ERB::Util.tokenize(source.lines[backtrace_location.lineno - 1])
46
+ new_first_column = find_offset(spot[:snippet], tokens, spot[:first_column])
47
+ lineno_delta = spot[:first_lineno] - backtrace_location.lineno
48
+ spot[:first_lineno] -= lineno_delta
49
+ spot[:last_lineno] -= lineno_delta
50
+
51
+ column_delta = spot[:first_column] - new_first_column
52
+ spot[:first_column] -= column_delta
53
+ spot[:last_column] -= column_delta
54
+ spot[:script_lines] = source.lines
55
+
56
+ spot
57
+ rescue NotImplementedError, LocationParsingError
58
+ nil
59
+ end
60
+
36
61
  def call(template, source)
37
62
  # First, convert to BINARY, so in case the encoding is
38
63
  # wrong, we can still find an encoding tag
@@ -58,7 +83,7 @@ module ActionView
58
83
 
59
84
  if ActionView::Base.annotate_rendered_view_with_filenames && template.format == :html
60
85
  options[:preamble] = "@output_buffer.safe_append='<!-- BEGIN #{template.short_identifier} -->';"
61
- options[:postamble] = "@output_buffer.safe_append='<!-- END #{template.short_identifier} -->';@output_buffer.to_s"
86
+ options[:postamble] = "@output_buffer.safe_append='<!-- END #{template.short_identifier} -->';@output_buffer"
62
87
  end
63
88
 
64
89
  self.class.erb_implementation.new(erb, options).src
@@ -79,6 +104,53 @@ module ActionView
79
104
  # Otherwise, raise an exception
80
105
  raise WrongEncodingError.new(string, string.encoding)
81
106
  end
107
+
108
+ def find_offset(compiled, source_tokens, error_column)
109
+ compiled = StringScanner.new(compiled)
110
+
111
+ passed_tokens = []
112
+
113
+ while tok = source_tokens.shift
114
+ tok_name, str = *tok
115
+ case tok_name
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
126
+
127
+ if compiled.pos + str.bytesize >= error_column
128
+ offset = error_column - compiled.pos
129
+ return passed_tokens.map(&:last).join.bytesize + offset
130
+ else
131
+ unless compiled.scan(str)
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
140
+ 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
+ end
150
+
151
+ passed_tokens << tok
152
+ end
153
+ end
82
154
  end
83
155
  end
84
156
  end
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionView # :nodoc:
4
- # = Action View Template Handlers
5
4
  class Template # :nodoc:
5
+ # = Action View Template Handlers
6
6
  module Handlers # :nodoc:
7
7
  autoload :Raw, "action_view/template/handlers/raw"
8
8
  autoload :ERB, "action_view/template/handlers/erb"
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionView # :nodoc:
4
- # = Action View HTML Template
5
4
  class Template # :nodoc:
5
+ # = Action View HTML Template
6
6
  class HTML # :nodoc:
7
7
  attr_reader :type
8
8
 
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionView # :nodoc:
4
- # = Action View RawFile Template
5
4
  class Template # :nodoc:
5
+ # = Action View RawFile Template
6
6
  class RawFile # :nodoc:
7
7
  attr_accessor :type, :format
8
8