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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +69 -78
  3. data/README.rdoc +1 -1
  4. data/lib/action_view/base.rb +6 -9
  5. data/lib/action_view/dependency_tracker/erb_tracker.rb +36 -27
  6. data/lib/action_view/dependency_tracker/ruby_tracker.rb +2 -19
  7. data/lib/action_view/dependency_tracker/wildcard_resolver.rb +32 -0
  8. data/lib/action_view/dependency_tracker.rb +1 -0
  9. data/lib/action_view/digestor.rb +6 -2
  10. data/lib/action_view/gem_version.rb +4 -4
  11. data/lib/action_view/helpers/asset_tag_helper.rb +2 -2
  12. data/lib/action_view/helpers/atom_feed_helper.rb +1 -3
  13. data/lib/action_view/helpers/cache_helper.rb +10 -2
  14. data/lib/action_view/helpers/date_helper.rb +11 -4
  15. data/lib/action_view/helpers/form_helper.rb +104 -103
  16. data/lib/action_view/helpers/form_options_helper.rb +31 -25
  17. data/lib/action_view/helpers/form_tag_helper.rb +20 -17
  18. data/lib/action_view/helpers/output_safety_helper.rb +1 -2
  19. data/lib/action_view/helpers/rendering_helper.rb +160 -50
  20. data/lib/action_view/helpers/sanitize_helper.rb +6 -0
  21. data/lib/action_view/helpers/tag_helper.rb +26 -39
  22. data/lib/action_view/helpers/tags/base.rb +9 -9
  23. data/lib/action_view/helpers/tags/check_box.rb +2 -2
  24. data/lib/action_view/helpers/tags/collection_check_boxes.rb +4 -3
  25. data/lib/action_view/helpers/tags/collection_helpers.rb +2 -1
  26. data/lib/action_view/helpers/tags/file_field.rb +1 -1
  27. data/lib/action_view/helpers/tags/label.rb +3 -10
  28. data/lib/action_view/helpers/tags/radio_button.rb +1 -1
  29. data/lib/action_view/helpers/tags/select_renderer.rb +1 -1
  30. data/lib/action_view/helpers/tags/text_area.rb +1 -1
  31. data/lib/action_view/helpers/tags/text_field.rb +1 -1
  32. data/lib/action_view/helpers/text_helper.rb +10 -3
  33. data/lib/action_view/helpers/url_helper.rb +2 -4
  34. data/lib/action_view/layouts.rb +7 -7
  35. data/lib/action_view/record_identifier.rb +1 -1
  36. data/lib/action_view/render_parser/prism_render_parser.rb +13 -1
  37. data/lib/action_view/render_parser/ripper_render_parser.rb +10 -1
  38. data/lib/action_view/renderer/partial_renderer.rb +2 -2
  39. data/lib/action_view/renderer/streaming_template_renderer.rb +8 -2
  40. data/lib/action_view/renderer/template_renderer.rb +3 -3
  41. data/lib/action_view/rendering.rb +2 -3
  42. data/lib/action_view/template/error.rb +11 -0
  43. data/lib/action_view/template/handlers/erb/erubi.rb +1 -1
  44. data/lib/action_view/template/handlers/erb.rb +45 -37
  45. data/lib/action_view/template/raw_file.rb +4 -0
  46. data/lib/action_view/template/resolver.rb +0 -1
  47. data/lib/action_view/template.rb +1 -2
  48. data/lib/action_view/test_case.rb +0 -1
  49. data/lib/action_view.rb +1 -0
  50. metadata +12 -11
@@ -80,27 +80,27 @@ module ActionView
80
80
  end
81
81
  end
82
82
 
83
- def add_default_name_and_id_for_value(tag_value, options)
83
+ def add_default_name_and_field_for_value(tag_value, options, field = "id")
84
84
  if tag_value.nil?
85
- add_default_name_and_id(options)
85
+ add_default_name_and_field(options, field)
86
86
  else
87
- specified_id = options["id"]
88
- add_default_name_and_id(options)
87
+ specified_field = options[field]
88
+ add_default_name_and_field(options, field)
89
89
 
90
- if specified_id.blank? && options["id"].present?
91
- options["id"] += "_#{sanitized_value(tag_value)}"
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 add_default_name_and_id(options)
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["id"] = options.fetch("id") { tag_id(index, options.delete("namespace")) }
101
+ options[field] = options.fetch(field) { tag_id(index, options.delete("namespace")) }
102
102
  if namespace = options.delete("namespace")
103
- options["id"] = options["id"] ? "#{namespace}_#{options['id']}" : namespace
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
- add_default_name_and_id_for_value(@checked_value, options)
24
+ add_default_name_and_field_for_value(@checked_value, options)
25
25
  options.delete("multiple")
26
26
  else
27
- add_default_name_and_id(options)
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 check_box(extra_html_options = {})
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.check_box(@object_name, @method_name, html_options, @value, nil)
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.check_box + builder.label
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
- @template_object.hidden_field_tag(hidden_name, "", id: nil)
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
- add_default_name_and_id(options)
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
- if name_and_id["for"]
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(name_and_id["id"], content, options)
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
- add_default_name_and_id_for_value(@tag_value, options)
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
- add_default_name_and_id(html_options)
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
- add_default_name_and_id(options)
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
- add_default_name_and_id(options)
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 = [first_part, separator, phrase, separator, second_part].join.strip
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}"
@@ -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, [], { formats: formats }).first || super"
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 _normalize_options(options) # :nodoc:
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
@@ -11,7 +11,7 @@ module ActionView
11
11
  #
12
12
  # Consider for example the following code that form of post:
13
13
  #
14
- # <%= form_for(post) do |f| %>
14
+ # <%= form_with(model: post) do |f| %>
15
15
  # <%= f.text_field :body %>
16
16
  # <% end %>
17
17
  #
@@ -97,9 +97,21 @@ module ActionView
97
97
  def render_call_template(node)
98
98
  object_template = false
99
99
  template =
100
- if node.is_a?(Prism::StringNode)
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
- self[0][0][0]
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 +to_partial_path+,
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 +to_partial_path+,
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 layout_name && template.supports_streaming?
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, [], details)
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, [], **all_details)
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 _normalize_options(options)
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 Eruby that whether template will be compiled with `frozen_string_literal: true`
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
- tokens = ::ERB::Util.tokenize(source.lines[backtrace_location.lineno - 1])
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] = source.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
- passed_tokens = []
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
- 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
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
- if compiled.pos + str.bytesize >= error_column
128
- offset = error_column - compiled.pos
129
- return passed_tokens.map(&:last).join.bytesize + offset
143
+ compiled.pos += str.bytesize
130
144
  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
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
- passed_tokens << tok
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
@@ -20,6 +20,10 @@ module ActionView # :nodoc:
20
20
  def render(*args)
21
21
  ::File.read(@filename)
22
22
  end
23
+
24
+ def supports_streaming?
25
+ false
26
+ end
23
27
  end
24
28
  end
25
29
  end
@@ -4,7 +4,6 @@ require "pathname"
4
4
  require "active_support/core_ext/class"
5
5
  require "active_support/core_ext/module/attribute_accessors"
6
6
  require "action_view/template"
7
- require "thread"
8
7
  require "concurrent/map"
9
8
 
10
9
  module ActionView
@@ -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 acceps the locals +title+ and
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] = [] }
data/lib/action_view.rb CHANGED
@@ -81,6 +81,7 @@ module ActionView
81
81
  autoload :MissingTemplate
82
82
  autoload :ActionViewError
83
83
  autoload :EncodingError
84
+ autoload :StrictLocalsError
84
85
  autoload :TemplateError
85
86
  autoload :SyntaxErrorInTemplate
86
87
  autoload :WrongEncodingError