actionview 7.0.1 → 7.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +281 -202
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +3 -3
  5. data/app/assets/javascripts/rails-ujs.esm.js +693 -0
  6. data/app/assets/javascripts/rails-ujs.js +630 -0
  7. data/lib/action_view/base.rb +33 -12
  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 +2 -2
  14. data/lib/action_view/helpers/active_model_helper.rb +1 -1
  15. data/lib/action_view/helpers/asset_tag_helper.rb +133 -48
  16. data/lib/action_view/helpers/asset_url_helper.rb +13 -12
  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 +26 -12
  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 +3 -3
  24. data/lib/action_view/helpers/date_helper.rb +76 -64
  25. data/lib/action_view/helpers/debug_helper.rb +3 -3
  26. data/lib/action_view/helpers/form_helper.rb +62 -31
  27. data/lib/action_view/helpers/form_options_helper.rb +6 -3
  28. data/lib/action_view/helpers/form_tag_helper.rb +88 -44
  29. data/lib/action_view/helpers/javascript_helper.rb +1 -0
  30. data/lib/action_view/helpers/number_helper.rb +15 -13
  31. data/lib/action_view/helpers/output_safety_helper.rb +4 -4
  32. data/lib/action_view/helpers/rendering_helper.rb +5 -6
  33. data/lib/action_view/helpers/sanitize_helper.rb +34 -15
  34. data/lib/action_view/helpers/tag_helper.rb +27 -16
  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 +4 -1
  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 +33 -17
  53. data/lib/action_view/helpers/translation_helper.rb +6 -6
  54. data/lib/action_view/helpers/url_helper.rb +90 -65
  55. data/lib/action_view/helpers.rb +2 -0
  56. data/lib/action_view/layouts.rb +13 -8
  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 +16 -9
  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 +21 -3
  66. data/lib/action_view/renderer/partial_renderer.rb +3 -2
  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 +24 -6
  71. data/lib/action_view/ripper_ast_parser.rb +6 -6
  72. data/lib/action_view/routing_url_for.rb +7 -4
  73. data/lib/action_view/template/error.rb +14 -1
  74. data/lib/action_view/template/handlers/builder.rb +4 -4
  75. data/lib/action_view/template/handlers/erb/erubi.rb +23 -27
  76. data/lib/action_view/template/handlers/erb.rb +73 -1
  77. data/lib/action_view/template/handlers.rb +1 -1
  78. data/lib/action_view/template/html.rb +1 -1
  79. data/lib/action_view/template/raw_file.rb +1 -1
  80. data/lib/action_view/template/renderable.rb +1 -1
  81. data/lib/action_view/template/resolver.rb +15 -5
  82. data/lib/action_view/template/text.rb +1 -1
  83. data/lib/action_view/template/types.rb +25 -34
  84. data/lib/action_view/template.rb +227 -53
  85. data/lib/action_view/template_path.rb +2 -0
  86. data/lib/action_view/test_case.rb +174 -21
  87. data/lib/action_view/unbound_template.rb +15 -5
  88. data/lib/action_view/version.rb +1 -1
  89. data/lib/action_view/view_paths.rb +19 -28
  90. data/lib/action_view.rb +4 -1
  91. data/lib/assets/compiled/rails-ujs.js +36 -5
  92. metadata +27 -27
@@ -4,18 +4,20 @@ require "thread"
4
4
  require "delegate"
5
5
 
6
6
  module ActionView
7
- # = Action View Template
7
+ # = Action View \Template
8
8
  class Template
9
9
  extend ActiveSupport::Autoload
10
10
 
11
+ STRICT_LOCALS_REGEX = /\#\s+locals:\s+\((.*)\)/
12
+
11
13
  # === Encodings in ActionView::Template
12
14
  #
13
15
  # ActionView::Template is one of a few sources of potential
14
- # encoding issues in Rails. This is because the source for
16
+ # encoding issues in \Rails. This is because the source for
15
17
  # templates are usually read from disk, and Ruby (like most
16
18
  # encoding-aware programming languages) assumes that the
17
19
  # String retrieved through File IO is encoded in the
18
- # <tt>default_external</tt> encoding. In Rails, the default
20
+ # <tt>default_external</tt> encoding. In \Rails, the default
19
21
  # <tt>default_external</tt> encoding is UTF-8.
20
22
  #
21
23
  # As a result, if a user saves their template as ISO-8859-1
@@ -34,13 +36,13 @@ module ActionView
34
36
  # to the problem.
35
37
  # 2. The user can specify the encoding using Ruby-style
36
38
  # encoding comments in any template engine. If such
37
- # a comment is supplied, Rails will apply that encoding
39
+ # a comment is supplied, \Rails will apply that encoding
38
40
  # to the resulting compiled source returned by the
39
41
  # template handler.
40
42
  # 3. In all cases, we transcode the resulting String to
41
43
  # the UTF-8.
42
44
  #
43
- # This means that other parts of Rails can always assume
45
+ # This means that other parts of \Rails can always assume
44
46
  # that templates are encoded in UTF-8, even if the original
45
47
  # source of the template was not UTF-8.
46
48
  #
@@ -51,7 +53,7 @@ module ActionView
51
53
  # === Instructions for template handlers
52
54
  #
53
55
  # The easiest thing for you to do is to simply ignore
54
- # encodings. Rails will hand you the template source
56
+ # encodings. \Rails will hand you the template source
55
57
  # as the default_internal (generally UTF-8), raising
56
58
  # an exception for the user before sending the template
57
59
  # to you if it could not determine the original encoding.
@@ -68,7 +70,7 @@ module ActionView
68
70
  # you may indicate that you will handle encodings yourself
69
71
  # by implementing <tt>handles_encoding?</tt> on your handler.
70
72
  #
71
- # If you do, Rails will not try to encode the String
73
+ # If you do, \Rails will not try to encode the String
72
74
  # into the default_internal, passing you the unaltered
73
75
  # bytes tagged with the assumed encoding (from
74
76
  # default_external).
@@ -94,11 +96,58 @@ module ActionView
94
96
  #
95
97
  # Given this sub template rendering:
96
98
  #
97
- # <%= render "shared/header", { headline: "Welcome", person: person } %>
99
+ # <%= render "application/header", { headline: "Welcome", person: person } %>
98
100
  #
99
101
  # You can use +local_assigns+ in the sub templates to access the local variables:
100
102
  #
101
103
  # local_assigns[:headline] # => "Welcome"
104
+ #
105
+ # Each key in +local_assigns+ is available as a partial-local variable:
106
+ #
107
+ # local_assigns[:headline] # => "Welcome"
108
+ # headline # => "Welcome"
109
+ #
110
+ # Since +local_assigns+ is a +Hash+, it's compatible with Ruby 3.1's pattern
111
+ # matching assignment operator:
112
+ #
113
+ # local_assigns => { headline:, **options }
114
+ # headline # => "Welcome"
115
+ # options # => {}
116
+ #
117
+ # Pattern matching assignment also supports variable renaming:
118
+ #
119
+ # local_assigns => { headline: title }
120
+ # title # => "Welcome"
121
+ #
122
+ # If a template refers to a variable that isn't passed into the view as part
123
+ # of the <tt>locals: { ... }</tt> Hash, the template will raise an
124
+ # +ActionView::Template::Error+:
125
+ #
126
+ # <%# => raises ActionView::Template::Error %>
127
+ # <% alerts.each do |alert| %>
128
+ # <p><%= alert %></p>
129
+ # <% end %>
130
+ #
131
+ # Since +local_assigns+ returns a +Hash+ instance, you can conditionally
132
+ # read a variable, then fall back to a default value when
133
+ # the key isn't part of the <tt>locals: { ... }</tt> options:
134
+ #
135
+ # <% local_assigns.fetch(:alerts, []).each do |alert| %>
136
+ # <p><%= alert %></p>
137
+ # <% end %>
138
+ #
139
+ # Combining Ruby 3.1's pattern matching assignment with calls to
140
+ # +Hash#with_defaults+ enables compact partial-local variable
141
+ # assignments:
142
+ #
143
+ # <% local_assigns.with_defaults(alerts: []) => { headline:, alerts: } %>
144
+ #
145
+ # <h1><%= headline %></h1>
146
+ #
147
+ # <% alerts.each do |alert| %>
148
+ # <p><%= alert %></p>
149
+ # <% end %>
150
+ #
102
151
 
103
152
  eager_autoload do
104
153
  autoload :Error
@@ -107,6 +156,7 @@ module ActionView
107
156
  autoload :Handlers
108
157
  autoload :HTML
109
158
  autoload :Inline
159
+ autoload :Types
110
160
  autoload :Sources
111
161
  autoload :Text
112
162
  autoload :Types
@@ -117,11 +167,24 @@ module ActionView
117
167
  singleton_class.attr_accessor :frozen_string_literal
118
168
  @frozen_string_literal = false
119
169
 
170
+ class << self # :nodoc:
171
+ def mime_types_implementation=(implementation)
172
+ # This method isn't thread-safe, but it's not supposed
173
+ # to be called after initialization
174
+ if self::Types != implementation
175
+ remove_const(:Types)
176
+ const_set(:Types, implementation)
177
+ end
178
+ end
179
+ end
180
+
120
181
  attr_reader :identifier, :handler
121
- attr_reader :variable, :format, :variant, :locals, :virtual_path
182
+ attr_reader :variable, :format, :variant, :virtual_path
183
+
184
+ NONE = Object.new
122
185
 
123
186
  def initialize(source, identifier, handler, locals:, format: nil, variant: nil, virtual_path: nil)
124
- @source = source
187
+ @source = source.dup
125
188
  @identifier = identifier
126
189
  @handler = handler
127
190
  @compiled = false
@@ -137,6 +200,36 @@ module ActionView
137
200
  @format = format
138
201
  @variant = variant
139
202
  @compile_mutex = Mutex.new
203
+ @strict_locals = NONE
204
+ @type = nil
205
+ end
206
+
207
+ # The locals this template has been or will be compiled for, or nil if this
208
+ # is a strict locals template.
209
+ def locals
210
+ if strict_locals?
211
+ nil
212
+ else
213
+ @locals
214
+ end
215
+ end
216
+
217
+ def spot(location) # :nodoc:
218
+ ast = RubyVM::AbstractSyntaxTree.parse(compiled_source, keep_script_lines: true)
219
+ node_id = RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location(location)
220
+ node = find_node_by_id(ast, node_id)
221
+
222
+ ErrorHighlight.spot(node)
223
+ end
224
+
225
+ # Translate an error location returned by ErrorHighlight to the correct
226
+ # source location inside the template.
227
+ def translate_location(backtrace_location, spot)
228
+ if handler.respond_to?(:translate_location)
229
+ handler.translate_location(spot, backtrace_location, encode!) || spot
230
+ else
231
+ spot
232
+ end
140
233
  end
141
234
 
142
235
  # Returns whether the underlying handler supports streaming. If so,
@@ -151,10 +244,15 @@ module ActionView
151
244
  # This method is instrumented as "!render_template.action_view". Notice that
152
245
  # we use a bang in this instrumentation because you don't want to
153
246
  # consume this in production. This is only slow if it's being listened to.
154
- def render(view, locals, buffer = ActionView::OutputBuffer.new, add_to_stack: true, &block)
247
+ def render(view, locals, buffer = nil, add_to_stack: true, &block)
155
248
  instrument_render_template do
156
249
  compile!(view)
157
- view._run(method_name, self, locals, buffer, add_to_stack: add_to_stack, &block)
250
+ if buffer
251
+ view._run(method_name, self, locals, buffer, add_to_stack: add_to_stack, has_strict_locals: strict_locals?, &block)
252
+ nil
253
+ else
254
+ view._run(method_name, self, locals, OutputBuffer.new, add_to_stack: add_to_stack, has_strict_locals: strict_locals?, &block)&.to_s
255
+ end
158
256
  end
159
257
  rescue => e
160
258
  handle_render_error(view, e)
@@ -169,13 +267,16 @@ module ActionView
169
267
  end
170
268
 
171
269
  def inspect
172
- "#<#{self.class.name} #{short_identifier} locals=#{@locals.inspect}>"
270
+ "#<#{self.class.name} #{short_identifier} locals=#{locals.inspect}>"
173
271
  end
174
272
 
175
273
  def source
176
274
  @source.to_s
177
275
  end
178
276
 
277
+ LEADING_ENCODING_REGEXP = /\A#{ENCODING_FLAG}/
278
+ private_constant :LEADING_ENCODING_REGEXP
279
+
179
280
  # This method is responsible for properly setting the encoding of the
180
281
  # source. Until this point, we assume that the source is BINARY data.
181
282
  # If no additional information is supplied, we assume the encoding is
@@ -194,7 +295,7 @@ module ActionView
194
295
  # Look for # encoding: *. If we find one, we'll encode the
195
296
  # String in that encoding, otherwise, we'll use the
196
297
  # default external encoding.
197
- if source.sub!(/\A#{ENCODING_FLAG}/, "")
298
+ if source.sub!(LEADING_ENCODING_REGEXP, "")
198
299
  encoding = magic_encoding = $1
199
300
  else
200
301
  encoding = Encoding.default_external
@@ -222,6 +323,32 @@ module ActionView
222
323
  end
223
324
  end
224
325
 
326
+ # This method is responsible for marking a template as having strict locals
327
+ # which means the template can only accept the locals defined in a magic
328
+ # comment. For example, if your template acceps the locals +title+ and
329
+ # +comment_count+, add the following to your template file:
330
+ #
331
+ # <%# locals: (title: "Default title", comment_count: 0) %>
332
+ #
333
+ # Strict locals are useful for validating template arguments and for
334
+ # specifying defaults.
335
+ def strict_locals!
336
+ if @strict_locals == NONE
337
+ self.source.sub!(STRICT_LOCALS_REGEX, "")
338
+ @strict_locals = $1
339
+
340
+ return if @strict_locals.nil? # Magic comment not found
341
+
342
+ @strict_locals = "**nil" if @strict_locals.blank?
343
+ end
344
+
345
+ @strict_locals
346
+ end
347
+
348
+ # Returns whether a template is using strict locals.
349
+ def strict_locals?
350
+ strict_locals!
351
+ end
225
352
 
226
353
  # Exceptions are marshalled when using the parallel test runner with DRb, so we need
227
354
  # to ensure that references to the template object can be marshalled as well. This means forgoing
@@ -235,7 +362,26 @@ module ActionView
235
362
  @compile_mutex = Mutex.new
236
363
  end
237
364
 
365
+ def method_name # :nodoc:
366
+ @method_name ||= begin
367
+ m = +"_#{identifier_method_name}__#{@identifier.hash}_#{__id__}"
368
+ m.tr!("-", "_")
369
+ m
370
+ end
371
+ end
372
+
238
373
  private
374
+ def find_node_by_id(node, node_id)
375
+ return node if node.node_id == node_id
376
+
377
+ node.children.grep(node.class).each do |child|
378
+ found = find_node_by_id(child, node_id)
379
+ return found if found
380
+ end
381
+
382
+ false
383
+ end
384
+
239
385
  # Compile a template. This method ensures a template is compiled
240
386
  # just once and removes the source after it is compiled.
241
387
  def compile!(view)
@@ -260,27 +406,25 @@ module ActionView
260
406
  end
261
407
  end
262
408
 
263
- # Among other things, this method is responsible for properly setting
264
- # the encoding of the compiled template.
265
- #
266
- # If the template engine handles encodings, we send the encoded
267
- # String to the engine without further processing. This allows
268
- # the template engine to support additional mechanisms for
269
- # specifying the encoding. For instance, ERB supports <%# encoding: %>
270
- #
271
- # Otherwise, after we figure out the correct encoding, we then
272
- # encode the source into <tt>Encoding.default_internal</tt>.
273
- # In general, this means that templates will be UTF-8 inside of Rails,
274
- # regardless of the original source encoding.
275
- def compile(mod)
409
+ # This method compiles the source of the template. The compilation of templates
410
+ # involves setting strict_locals! if applicable, encoding the template, and setting
411
+ # frozen string literal.
412
+ def compiled_source
413
+ set_strict_locals = strict_locals!
276
414
  source = encode!
277
415
  code = @handler.call(self, source)
278
416
 
417
+ method_arguments =
418
+ if set_strict_locals
419
+ "output_buffer, #{set_strict_locals}"
420
+ else
421
+ "local_assigns, output_buffer"
422
+ end
423
+
279
424
  # Make sure that the resulting String to be eval'd is in the
280
425
  # encoding of the code
281
- original_source = source
282
426
  source = +<<-end_src
283
- def #{method_name}(local_assigns, output_buffer)
427
+ def #{method_name}(#{method_arguments})
284
428
  @virtual_path = #{@virtual_path.inspect};#{locals_code};#{code}
285
429
  end
286
430
  end_src
@@ -299,17 +443,61 @@ module ActionView
299
443
  raise WrongEncodingError.new(source, Encoding.default_internal)
300
444
  end
301
445
 
446
+ if Template.frozen_string_literal
447
+ "# frozen_string_literal: true\n#{source}"
448
+ else
449
+ source
450
+ end
451
+ end
452
+
453
+ # Among other things, this method is responsible for properly setting
454
+ # the encoding of the compiled template.
455
+ #
456
+ # If the template engine handles encodings, we send the encoded
457
+ # String to the engine without further processing. This allows
458
+ # the template engine to support additional mechanisms for
459
+ # specifying the encoding. For instance, ERB supports <%# encoding: %>
460
+ #
461
+ # Otherwise, after we figure out the correct encoding, we then
462
+ # encode the source into <tt>Encoding.default_internal</tt>.
463
+ # In general, this means that templates will be UTF-8 inside of Rails,
464
+ # regardless of the original source encoding.
465
+ def compile(mod)
302
466
  begin
303
- if Template.frozen_string_literal
304
- mod.module_eval("# frozen_string_literal: true\n#{source}", identifier, -1)
305
- else
306
- mod.module_eval(source, identifier, 0)
307
- end
467
+ mod.module_eval(compiled_source, identifier, offset)
308
468
  rescue SyntaxError
309
469
  # Account for when code in the template is not syntactically valid; e.g. if we're using
310
470
  # ERB and the user writes <%= foo( %>, attempting to call a helper `foo` and interpolate
311
471
  # the result into the template, but missing an end parenthesis.
312
- raise SyntaxErrorInTemplate.new(self, original_source)
472
+ raise SyntaxErrorInTemplate.new(self, encode!)
473
+ end
474
+
475
+ return unless strict_locals?
476
+
477
+ # Check compiled method parameters to ensure that only kwargs
478
+ # were provided as strict locals, preventing `locals: (foo, *foo)` etc
479
+ # and allowing `locals: (foo:)`.
480
+
481
+ non_kwarg_parameters =
482
+ (mod.instance_method(method_name).parameters - [[:req, :output_buffer]]).
483
+ select { |parameter| ![:keyreq, :key, :keyrest, :nokey].include?(parameter[0]) }
484
+
485
+ return unless non_kwarg_parameters.any?
486
+
487
+ mod.undef_method(method_name)
488
+
489
+ raise ArgumentError.new(
490
+ "#{non_kwarg_parameters.map { |_, name| "`#{name}`" }.to_sentence} set as non-keyword " \
491
+ "#{'argument'.pluralize(non_kwarg_parameters.length)} for #{short_identifier}. " \
492
+ "Locals can only be set as keyword arguments."
493
+ )
494
+ end
495
+
496
+ def offset
497
+ if Template.frozen_string_literal
498
+ -1
499
+ else
500
+ 0
313
501
  end
314
502
  end
315
503
 
@@ -323,32 +511,18 @@ module ActionView
323
511
  end
324
512
 
325
513
  def locals_code
514
+ return "" if strict_locals?
515
+
326
516
  # Only locals with valid variable names get set directly. Others will
327
517
  # still be available in local_assigns.
328
518
  locals = @locals - Module::RUBY_RESERVED_KEYWORDS
329
- deprecated_locals = locals.grep(/\A@+/)
330
- if deprecated_locals.any?
331
- ActiveSupport::Deprecation.warn(<<~MSG)
332
- Passing instance variables to `render` is deprecated.
333
- In Rails 7.1, #{deprecated_locals.to_sentence} will be ignored.
334
- MSG
335
- locals = locals.grep(/\A@?(?![A-Z0-9])(?:[[:alnum:]_]|[^\0-\177])+\z/)
336
- else
337
- locals = locals.grep(/\A(?![A-Z0-9])(?:[[:alnum:]_]|[^\0-\177])+\z/)
338
- end
519
+
520
+ locals = locals.grep(/\A(?![A-Z0-9])(?:[[:alnum:]_]|[^\0-\177])+\z/)
339
521
 
340
522
  # Assign for the same variable is to suppress unused variable warning
341
523
  locals.each_with_object(+"") { |key, code| code << "#{key} = local_assigns[:#{key}]; #{key} = #{key};" }
342
524
  end
343
525
 
344
- def method_name
345
- @method_name ||= begin
346
- m = +"_#{identifier_method_name}__#{@identifier.hash}_#{__id__}"
347
- m.tr!("-", "_")
348
- m
349
- end
350
- end
351
-
352
526
  def identifier_method_name
353
527
  short_identifier.tr("^a-z_", "_")
354
528
  end
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionView
4
+ # = Action View \TemplatePath
5
+ #
4
6
  # Represents a template path within ActionView's lookup and rendering system,
5
7
  # like "users/show"
6
8
  #