actionview 6.1.7.2 → 7.1.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 (103) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +299 -277
  3. data/MIT-LICENSE +2 -1
  4. data/README.rdoc +3 -3
  5. data/app/assets/javascripts/rails-ujs.esm.js +686 -0
  6. data/app/assets/javascripts/rails-ujs.js +630 -0
  7. data/lib/action_view/base.rb +37 -19
  8. data/lib/action_view/buffers.rb +107 -9
  9. data/lib/action_view/cache_expiry.rb +48 -37
  10. data/lib/action_view/context.rb +1 -1
  11. data/lib/action_view/dependency_tracker/erb_tracker.rb +154 -0
  12. data/lib/action_view/dependency_tracker/ripper_tracker.rb +59 -0
  13. data/lib/action_view/dependency_tracker.rb +6 -147
  14. data/lib/action_view/deprecator.rb +7 -0
  15. data/lib/action_view/digestor.rb +8 -5
  16. data/lib/action_view/flows.rb +4 -4
  17. data/lib/action_view/gem_version.rb +4 -4
  18. data/lib/action_view/helpers/active_model_helper.rb +3 -3
  19. data/lib/action_view/helpers/asset_tag_helper.rb +200 -60
  20. data/lib/action_view/helpers/asset_url_helper.rb +22 -21
  21. data/lib/action_view/helpers/atom_feed_helper.rb +8 -9
  22. data/lib/action_view/helpers/cache_helper.rb +55 -12
  23. data/lib/action_view/helpers/capture_helper.rb +34 -14
  24. data/lib/action_view/helpers/content_exfiltration_prevention_helper.rb +70 -0
  25. data/lib/action_view/helpers/controller_helper.rb +8 -2
  26. data/lib/action_view/helpers/csp_helper.rb +3 -3
  27. data/lib/action_view/helpers/csrf_helper.rb +4 -4
  28. data/lib/action_view/helpers/date_helper.rb +123 -57
  29. data/lib/action_view/helpers/debug_helper.rb +6 -4
  30. data/lib/action_view/helpers/form_helper.rb +253 -97
  31. data/lib/action_view/helpers/form_options_helper.rb +72 -34
  32. data/lib/action_view/helpers/form_tag_helper.rb +189 -58
  33. data/lib/action_view/helpers/javascript_helper.rb +4 -5
  34. data/lib/action_view/helpers/number_helper.rb +43 -335
  35. data/lib/action_view/helpers/output_safety_helper.rb +6 -6
  36. data/lib/action_view/helpers/rendering_helper.rb +6 -7
  37. data/lib/action_view/helpers/sanitize_helper.rb +54 -24
  38. data/lib/action_view/helpers/tag_helper.rb +42 -35
  39. data/lib/action_view/helpers/tags/base.rb +16 -77
  40. data/lib/action_view/helpers/tags/check_box.rb +1 -1
  41. data/lib/action_view/helpers/tags/collection_check_boxes.rb +1 -0
  42. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +1 -0
  43. data/lib/action_view/helpers/tags/collection_select.rb +4 -1
  44. data/lib/action_view/helpers/tags/date_field.rb +1 -1
  45. data/lib/action_view/helpers/tags/date_select.rb +2 -0
  46. data/lib/action_view/helpers/tags/datetime_field.rb +14 -6
  47. data/lib/action_view/helpers/tags/datetime_local_field.rb +11 -2
  48. data/lib/action_view/helpers/tags/file_field.rb +16 -0
  49. data/lib/action_view/helpers/tags/grouped_collection_select.rb +3 -0
  50. data/lib/action_view/helpers/tags/month_field.rb +1 -1
  51. data/lib/action_view/helpers/tags/select.rb +4 -1
  52. data/lib/action_view/helpers/tags/select_renderer.rb +56 -0
  53. data/lib/action_view/helpers/tags/time_field.rb +11 -2
  54. data/lib/action_view/helpers/tags/time_zone_select.rb +3 -0
  55. data/lib/action_view/helpers/tags/week_field.rb +1 -1
  56. data/lib/action_view/helpers/tags/weekday_select.rb +31 -0
  57. data/lib/action_view/helpers/tags.rb +5 -2
  58. data/lib/action_view/helpers/text_helper.rb +180 -97
  59. data/lib/action_view/helpers/translation_helper.rb +14 -45
  60. data/lib/action_view/helpers/url_helper.rb +230 -132
  61. data/lib/action_view/helpers.rb +27 -25
  62. data/lib/action_view/layouts.rb +15 -10
  63. data/lib/action_view/log_subscriber.rb +49 -32
  64. data/lib/action_view/lookup_context.rb +58 -61
  65. data/lib/action_view/model_naming.rb +2 -2
  66. data/lib/action_view/path_registry.rb +57 -0
  67. data/lib/action_view/path_set.rb +28 -35
  68. data/lib/action_view/railtie.rb +44 -9
  69. data/lib/action_view/record_identifier.rb +16 -9
  70. data/lib/action_view/render_parser.rb +188 -0
  71. data/lib/action_view/renderer/abstract_renderer.rb +3 -3
  72. data/lib/action_view/renderer/collection_renderer.rb +10 -2
  73. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +21 -3
  74. data/lib/action_view/renderer/partial_renderer.rb +3 -36
  75. data/lib/action_view/renderer/renderer.rb +6 -4
  76. data/lib/action_view/renderer/streaming_template_renderer.rb +6 -5
  77. data/lib/action_view/renderer/template_renderer.rb +9 -4
  78. data/lib/action_view/rendering.rb +25 -7
  79. data/lib/action_view/ripper_ast_parser.rb +198 -0
  80. data/lib/action_view/routing_url_for.rb +8 -5
  81. data/lib/action_view/template/error.rb +122 -14
  82. data/lib/action_view/template/handlers/builder.rb +4 -4
  83. data/lib/action_view/template/handlers/erb/erubi.rb +23 -27
  84. data/lib/action_view/template/handlers/erb.rb +79 -1
  85. data/lib/action_view/template/handlers.rb +4 -4
  86. data/lib/action_view/template/html.rb +4 -4
  87. data/lib/action_view/template/inline.rb +3 -3
  88. data/lib/action_view/template/raw_file.rb +4 -4
  89. data/lib/action_view/template/renderable.rb +1 -1
  90. data/lib/action_view/template/resolver.rb +96 -313
  91. data/lib/action_view/template/text.rb +4 -4
  92. data/lib/action_view/template/types.rb +25 -32
  93. data/lib/action_view/template.rb +245 -41
  94. data/lib/action_view/template_details.rb +66 -0
  95. data/lib/action_view/template_path.rb +66 -0
  96. data/lib/action_view/test_case.rb +182 -23
  97. data/lib/action_view/testing/resolvers.rb +11 -12
  98. data/lib/action_view/unbound_template.rb +43 -7
  99. data/lib/action_view/version.rb +1 -1
  100. data/lib/action_view/view_paths.rb +19 -28
  101. data/lib/action_view.rb +6 -4
  102. data/lib/assets/compiled/rails-ujs.js +36 -5
  103. metadata +32 -25
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module ActionView #:nodoc:
4
- # = Action View Text Template
5
- class Template #:nodoc:
6
- class Text #:nodoc:
3
+ module ActionView # :nodoc:
4
+ class Template # :nodoc:
5
+ # = Action View Text Template
6
+ class Text # :nodoc:
7
7
  attr_accessor :type
8
8
 
9
9
  def initialize(string)
@@ -3,12 +3,15 @@
3
3
  require "active_support/core_ext/module/attribute_accessors"
4
4
 
5
5
  module ActionView
6
- class Template #:nodoc:
7
- class Types
8
- class Type
9
- SET = Struct.new(:symbols).new([ :html, :text, :js, :css, :xml, :json ])
10
-
11
- def self.[](type)
6
+ class Template # :nodoc:
7
+ # SimpleType is mostly just a stub implementation for when Action View
8
+ # is used without Action Dispatch.
9
+ class SimpleType # :nodoc:
10
+ @symbols = [ :html, :text, :js, :css, :xml, :json ]
11
+ class << self
12
+ attr_reader :symbols
13
+
14
+ def [](type)
12
15
  if type.is_a?(self)
13
16
  type
14
17
  else
@@ -16,42 +19,32 @@ module ActionView
16
19
  end
17
20
  end
18
21
 
19
- attr_reader :symbol
20
-
21
- def initialize(symbol)
22
- @symbol = symbol.to_sym
23
- end
24
-
25
- def to_s
26
- @symbol.to_s
27
- end
28
- alias to_str to_s
29
-
30
- def ref
31
- @symbol
32
- end
33
- alias to_sym ref
34
-
35
- def ==(type)
36
- @symbol == type.to_sym unless type.blank?
22
+ def valid_symbols?(symbols) # :nodoc
23
+ symbols.all? { |s| @symbols.include?(s) }
37
24
  end
38
25
  end
39
26
 
40
- cattr_accessor :type_klass
27
+ attr_reader :symbol
41
28
 
42
- def self.delegate_to(klass)
43
- self.type_klass = klass
29
+ def initialize(symbol)
30
+ @symbol = symbol.to_sym
44
31
  end
45
32
 
46
- delegate_to Type
33
+ def to_s
34
+ @symbol.to_s
35
+ end
36
+ alias to_str to_s
47
37
 
48
- def self.[](type)
49
- type_klass[type]
38
+ def ref
39
+ @symbol
50
40
  end
41
+ alias to_sym ref
51
42
 
52
- def self.symbols
53
- type_klass::SET.symbols
43
+ def ==(type)
44
+ @symbol == type.to_sym unless type.blank?
54
45
  end
55
46
  end
47
+
48
+ Types = SimpleType # :nodoc:
56
49
  end
57
50
  end
@@ -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
@@ -114,11 +164,27 @@ module ActionView
114
164
 
115
165
  extend Template::Handlers
116
166
 
167
+ singleton_class.attr_accessor :frozen_string_literal
168
+ @frozen_string_literal = false
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
+
117
181
  attr_reader :identifier, :handler
118
- attr_reader :variable, :format, :variant, :locals, :virtual_path
182
+ attr_reader :variable, :format, :variant, :virtual_path
183
+
184
+ NONE = Object.new
119
185
 
120
186
  def initialize(source, identifier, handler, locals:, format: nil, variant: nil, virtual_path: nil)
121
- @source = source
187
+ @source = source.dup
122
188
  @identifier = identifier
123
189
  @handler = handler
124
190
  @compiled = false
@@ -134,6 +200,37 @@ module ActionView
134
200
  @format = format
135
201
  @variant = variant
136
202
  @compile_mutex = Mutex.new
203
+ @strict_locals = NONE
204
+ @strict_local_keys = nil
205
+ @type = nil
206
+ end
207
+
208
+ # The locals this template has been or will be compiled for, or nil if this
209
+ # is a strict locals template.
210
+ def locals
211
+ if strict_locals?
212
+ nil
213
+ else
214
+ @locals
215
+ end
216
+ end
217
+
218
+ def spot(location) # :nodoc:
219
+ ast = RubyVM::AbstractSyntaxTree.parse(compiled_source, keep_script_lines: true)
220
+ node_id = RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location(location)
221
+ node = find_node_by_id(ast, node_id)
222
+
223
+ ErrorHighlight.spot(node)
224
+ end
225
+
226
+ # Translate an error location returned by ErrorHighlight to the correct
227
+ # source location inside the template.
228
+ def translate_location(backtrace_location, spot)
229
+ if handler.respond_to?(:translate_location)
230
+ handler.translate_location(spot, backtrace_location, encode!) || spot
231
+ else
232
+ spot
233
+ end
137
234
  end
138
235
 
139
236
  # Returns whether the underlying handler supports streaming. If so,
@@ -148,10 +245,21 @@ module ActionView
148
245
  # This method is instrumented as "!render_template.action_view". Notice that
149
246
  # we use a bang in this instrumentation because you don't want to
150
247
  # consume this in production. This is only slow if it's being listened to.
151
- def render(view, locals, buffer = ActionView::OutputBuffer.new, add_to_stack: true, &block)
248
+ def render(view, locals, buffer = nil, implicit_locals: [], add_to_stack: true, &block)
152
249
  instrument_render_template do
153
250
  compile!(view)
154
- view._run(method_name, self, locals, buffer, add_to_stack: add_to_stack, &block)
251
+
252
+ if strict_locals? && @strict_local_keys && !implicit_locals.empty?
253
+ locals_to_ignore = implicit_locals - @strict_local_keys
254
+ locals.except!(*locals_to_ignore)
255
+ end
256
+
257
+ if buffer
258
+ view._run(method_name, self, locals, buffer, add_to_stack: add_to_stack, has_strict_locals: strict_locals?, &block)
259
+ nil
260
+ else
261
+ view._run(method_name, self, locals, OutputBuffer.new, add_to_stack: add_to_stack, has_strict_locals: strict_locals?, &block)&.to_s
262
+ end
155
263
  end
156
264
  rescue => e
157
265
  handle_render_error(view, e)
@@ -166,20 +274,23 @@ module ActionView
166
274
  end
167
275
 
168
276
  def inspect
169
- "#<#{self.class.name} #{short_identifier} locals=#{@locals.inspect}>"
277
+ "#<#{self.class.name} #{short_identifier} locals=#{locals.inspect}>"
170
278
  end
171
279
 
172
280
  def source
173
281
  @source.to_s
174
282
  end
175
283
 
284
+ LEADING_ENCODING_REGEXP = /\A#{ENCODING_FLAG}/
285
+ private_constant :LEADING_ENCODING_REGEXP
286
+
176
287
  # This method is responsible for properly setting the encoding of the
177
288
  # source. Until this point, we assume that the source is BINARY data.
178
289
  # If no additional information is supplied, we assume the encoding is
179
290
  # the same as <tt>Encoding.default_external</tt>.
180
291
  #
181
292
  # The user can also specify the encoding via a comment on the first
182
- # line of the template (# encoding: NAME-OF-ENCODING). This will work
293
+ # line of the template (<tt># encoding: NAME-OF-ENCODING</tt>). This will work
183
294
  # with any template engine, as we process out the encoding comment
184
295
  # before passing the source on to the template engine, leaving a
185
296
  # blank line in its stead.
@@ -191,7 +302,7 @@ module ActionView
191
302
  # Look for # encoding: *. If we find one, we'll encode the
192
303
  # String in that encoding, otherwise, we'll use the
193
304
  # default external encoding.
194
- if source.sub!(/\A#{ENCODING_FLAG}/, "")
305
+ if source.sub!(LEADING_ENCODING_REGEXP, "")
195
306
  encoding = magic_encoding = $1
196
307
  else
197
308
  encoding = Encoding.default_external
@@ -219,6 +330,32 @@ module ActionView
219
330
  end
220
331
  end
221
332
 
333
+ # This method is responsible for marking a template as having strict locals
334
+ # which means the template can only accept the locals defined in a magic
335
+ # comment. For example, if your template acceps the locals +title+ and
336
+ # +comment_count+, add the following to your template file:
337
+ #
338
+ # <%# locals: (title: "Default title", comment_count: 0) %>
339
+ #
340
+ # Strict locals are useful for validating template arguments and for
341
+ # specifying defaults.
342
+ def strict_locals!
343
+ if @strict_locals == NONE
344
+ self.source.sub!(STRICT_LOCALS_REGEX, "")
345
+ @strict_locals = $1
346
+
347
+ return if @strict_locals.nil? # Magic comment not found
348
+
349
+ @strict_locals = "**nil" if @strict_locals.blank?
350
+ end
351
+
352
+ @strict_locals
353
+ end
354
+
355
+ # Returns whether a template is using strict locals.
356
+ def strict_locals?
357
+ strict_locals!
358
+ end
222
359
 
223
360
  # Exceptions are marshalled when using the parallel test runner with DRb, so we need
224
361
  # to ensure that references to the template object can be marshalled as well. This means forgoing
@@ -232,7 +369,26 @@ module ActionView
232
369
  @compile_mutex = Mutex.new
233
370
  end
234
371
 
372
+ def method_name # :nodoc:
373
+ @method_name ||= begin
374
+ m = +"_#{identifier_method_name}__#{@identifier.hash}_#{__id__}"
375
+ m.tr!("-", "_")
376
+ m
377
+ end
378
+ end
379
+
235
380
  private
381
+ def find_node_by_id(node, node_id)
382
+ return node if node.node_id == node_id
383
+
384
+ node.children.grep(node.class).each do |child|
385
+ found = find_node_by_id(child, node_id)
386
+ return found if found
387
+ end
388
+
389
+ false
390
+ end
391
+
236
392
  # Compile a template. This method ensures a template is compiled
237
393
  # just once and removes the source after it is compiled.
238
394
  def compile!(view)
@@ -257,27 +413,25 @@ module ActionView
257
413
  end
258
414
  end
259
415
 
260
- # Among other things, this method is responsible for properly setting
261
- # the encoding of the compiled template.
262
- #
263
- # If the template engine handles encodings, we send the encoded
264
- # String to the engine without further processing. This allows
265
- # the template engine to support additional mechanisms for
266
- # specifying the encoding. For instance, ERB supports <%# encoding: %>
267
- #
268
- # Otherwise, after we figure out the correct encoding, we then
269
- # encode the source into <tt>Encoding.default_internal</tt>.
270
- # In general, this means that templates will be UTF-8 inside of Rails,
271
- # regardless of the original source encoding.
272
- def compile(mod)
416
+ # This method compiles the source of the template. The compilation of templates
417
+ # involves setting strict_locals! if applicable, encoding the template, and setting
418
+ # frozen string literal.
419
+ def compiled_source
420
+ set_strict_locals = strict_locals!
273
421
  source = encode!
274
422
  code = @handler.call(self, source)
275
423
 
424
+ method_arguments =
425
+ if set_strict_locals
426
+ "output_buffer, #{set_strict_locals}"
427
+ else
428
+ "local_assigns, output_buffer"
429
+ end
430
+
276
431
  # Make sure that the resulting String to be eval'd is in the
277
432
  # encoding of the code
278
- original_source = source
279
433
  source = +<<-end_src
280
- def #{method_name}(local_assigns, output_buffer)
434
+ def #{method_name}(#{method_arguments})
281
435
  @virtual_path = #{@virtual_path.inspect};#{locals_code};#{code}
282
436
  end
283
437
  end_src
@@ -296,13 +450,68 @@ module ActionView
296
450
  raise WrongEncodingError.new(source, Encoding.default_internal)
297
451
  end
298
452
 
453
+ if Template.frozen_string_literal
454
+ "# frozen_string_literal: true\n#{source}"
455
+ else
456
+ source
457
+ end
458
+ end
459
+
460
+ # Among other things, this method is responsible for properly setting
461
+ # the encoding of the compiled template.
462
+ #
463
+ # If the template engine handles encodings, we send the encoded
464
+ # String to the engine without further processing. This allows
465
+ # the template engine to support additional mechanisms for
466
+ # specifying the encoding. For instance, ERB supports <%# encoding: %>
467
+ #
468
+ # Otherwise, after we figure out the correct encoding, we then
469
+ # encode the source into <tt>Encoding.default_internal</tt>.
470
+ # In general, this means that templates will be UTF-8 inside of Rails,
471
+ # regardless of the original source encoding.
472
+ def compile(mod)
299
473
  begin
300
- mod.module_eval(source, identifier, 0)
474
+ mod.module_eval(compiled_source, identifier, offset)
301
475
  rescue SyntaxError
302
476
  # Account for when code in the template is not syntactically valid; e.g. if we're using
303
477
  # ERB and the user writes <%= foo( %>, attempting to call a helper `foo` and interpolate
304
478
  # the result into the template, but missing an end parenthesis.
305
- raise SyntaxErrorInTemplate.new(self, original_source)
479
+ raise SyntaxErrorInTemplate.new(self, encode!)
480
+ end
481
+
482
+ return unless strict_locals?
483
+
484
+ parameters = mod.instance_method(method_name).parameters - [[:req, :output_buffer]]
485
+ # Check compiled method parameters to ensure that only kwargs
486
+ # were provided as strict locals, preventing `locals: (foo, *foo)` etc
487
+ # and allowing `locals: (foo:)`.
488
+
489
+ non_kwarg_parameters = parameters.select do |parameter|
490
+ ![:keyreq, :key, :keyrest, :nokey].include?(parameter[0])
491
+ end
492
+
493
+ unless non_kwarg_parameters.empty?
494
+ mod.undef_method(method_name)
495
+
496
+ raise ArgumentError.new(
497
+ "#{non_kwarg_parameters.map { |_, name| "`#{name}`" }.to_sentence} set as non-keyword " \
498
+ "#{'argument'.pluralize(non_kwarg_parameters.length)} for #{short_identifier}. " \
499
+ "Locals can only be set as keyword arguments."
500
+ )
501
+ end
502
+
503
+ unless parameters.any? { |type, _| type == :keyrest }
504
+ parameters.map!(&:last)
505
+ parameters.sort!
506
+ @strict_local_keys = parameters.freeze
507
+ end
508
+ end
509
+
510
+ def offset
511
+ if Template.frozen_string_literal
512
+ -1
513
+ else
514
+ 0
306
515
  end
307
516
  end
308
517
 
@@ -316,23 +525,18 @@ module ActionView
316
525
  end
317
526
 
318
527
  def locals_code
528
+ return "" if strict_locals?
529
+
319
530
  # Only locals with valid variable names get set directly. Others will
320
531
  # still be available in local_assigns.
321
532
  locals = @locals - Module::RUBY_RESERVED_KEYWORDS
322
- locals = locals.grep(/\A@?(?![A-Z0-9])(?:[[:alnum:]_]|[^\0-\177])+\z/)
533
+
534
+ locals = locals.grep(/\A(?![A-Z0-9])(?:[[:alnum:]_]|[^\0-\177])+\z/)
323
535
 
324
536
  # Assign for the same variable is to suppress unused variable warning
325
537
  locals.each_with_object(+"") { |key, code| code << "#{key} = local_assigns[:#{key}]; #{key} = #{key};" }
326
538
  end
327
539
 
328
- def method_name
329
- @method_name ||= begin
330
- m = +"_#{identifier_method_name}__#{@identifier.hash}_#{__id__}"
331
- m.tr!("-", "_")
332
- m
333
- end
334
- end
335
-
336
540
  def identifier_method_name
337
541
  short_identifier.tr("^a-z_", "_")
338
542
  end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ class TemplateDetails # :nodoc:
5
+ class Requested
6
+ attr_reader :locale, :handlers, :formats, :variants
7
+ attr_reader :locale_idx, :handlers_idx, :formats_idx, :variants_idx
8
+
9
+ ANY_HASH = Hash.new(1).merge(nil => 0).freeze
10
+
11
+ def initialize(locale:, handlers:, formats:, variants:)
12
+ @locale = locale
13
+ @handlers = handlers
14
+ @formats = formats
15
+ @variants = variants
16
+
17
+ @locale_idx = build_idx_hash(locale)
18
+ @handlers_idx = build_idx_hash(handlers)
19
+ @formats_idx = build_idx_hash(formats)
20
+ if variants == :any
21
+ @variants_idx = ANY_HASH
22
+ else
23
+ @variants_idx = build_idx_hash(variants)
24
+ end
25
+ end
26
+
27
+ private
28
+ def build_idx_hash(arr)
29
+ [*arr, nil].each_with_index.to_h.freeze
30
+ end
31
+ end
32
+
33
+ attr_reader :locale, :handler, :format, :variant
34
+
35
+ def initialize(locale, handler, format, variant)
36
+ @locale = locale
37
+ @handler = handler
38
+ @format = format
39
+ @variant = variant
40
+ end
41
+
42
+ def matches?(requested)
43
+ requested.formats_idx[@format] &&
44
+ requested.locale_idx[@locale] &&
45
+ requested.variants_idx[@variant] &&
46
+ requested.handlers_idx[@handler]
47
+ end
48
+
49
+ def sort_key_for(requested)
50
+ [
51
+ requested.formats_idx[@format],
52
+ requested.locale_idx[@locale],
53
+ requested.variants_idx[@variant],
54
+ requested.handlers_idx[@handler]
55
+ ]
56
+ end
57
+
58
+ def handler_class
59
+ Template.handler_for_extension(handler)
60
+ end
61
+
62
+ def format_or_default
63
+ format || handler_class.try(:default_format)
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ # = Action View \TemplatePath
5
+ #
6
+ # Represents a template path within ActionView's lookup and rendering system,
7
+ # like "users/show"
8
+ #
9
+ # TemplatePath makes it convenient to convert between separate name, prefix,
10
+ # partial arguments and the virtual path.
11
+ class TemplatePath
12
+ attr_reader :name, :prefix, :partial, :virtual
13
+ alias_method :partial?, :partial
14
+ alias_method :virtual_path, :virtual
15
+
16
+ # Convert name, prefix, and partial into a virtual path string
17
+ def self.virtual(name, prefix, partial)
18
+ if prefix.empty?
19
+ "#{partial ? "_" : ""}#{name}"
20
+ elsif partial
21
+ "#{prefix}/_#{name}"
22
+ else
23
+ "#{prefix}/#{name}"
24
+ end
25
+ end
26
+
27
+ # Build a TemplatePath form a virtual path
28
+ def self.parse(virtual)
29
+ if nameidx = virtual.rindex("/")
30
+ prefix = virtual[0, nameidx]
31
+ name = virtual.from(nameidx + 1)
32
+ prefix = prefix[1..] if prefix.start_with?("/")
33
+ else
34
+ prefix = ""
35
+ name = virtual
36
+ end
37
+ partial = name.start_with?("_")
38
+ name = name[1..] if partial
39
+ new name, prefix, partial, virtual
40
+ end
41
+
42
+ # Convert name, prefix, and partial into a TemplatePath
43
+ def self.build(name, prefix, partial)
44
+ new name, prefix, partial, virtual(name, prefix, partial)
45
+ end
46
+
47
+ def initialize(name, prefix, partial, virtual)
48
+ @name = name
49
+ @prefix = prefix
50
+ @partial = partial
51
+ @virtual = virtual
52
+ end
53
+
54
+ alias :to_str :virtual
55
+ alias :to_s :virtual
56
+
57
+ def hash # :nodoc:
58
+ @virtual.hash
59
+ end
60
+
61
+ def eql?(other) # :nodoc:
62
+ @virtual == other.virtual
63
+ end
64
+ alias :== :eql? # :nodoc:
65
+ end
66
+ end