actionview 6.1.7.2 → 7.1.3

Sign up to get free protection for your applications and to get access to all the features.
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