actionview 6.1.7.2 → 7.0.6

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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +268 -254
  3. data/MIT-LICENSE +1 -0
  4. data/README.rdoc +2 -2
  5. data/lib/action_view/base.rb +4 -7
  6. data/lib/action_view/buffers.rb +2 -2
  7. data/lib/action_view/cache_expiry.rb +46 -32
  8. data/lib/action_view/dependency_tracker/erb_tracker.rb +154 -0
  9. data/lib/action_view/dependency_tracker/ripper_tracker.rb +59 -0
  10. data/lib/action_view/dependency_tracker.rb +6 -147
  11. data/lib/action_view/digestor.rb +7 -4
  12. data/lib/action_view/flows.rb +4 -4
  13. data/lib/action_view/gem_version.rb +5 -5
  14. data/lib/action_view/helpers/active_model_helper.rb +2 -2
  15. data/lib/action_view/helpers/asset_tag_helper.rb +95 -39
  16. data/lib/action_view/helpers/asset_url_helper.rb +16 -16
  17. data/lib/action_view/helpers/atom_feed_helper.rb +3 -4
  18. data/lib/action_view/helpers/cache_helper.rb +52 -3
  19. data/lib/action_view/helpers/capture_helper.rb +4 -4
  20. data/lib/action_view/helpers/controller_helper.rb +2 -2
  21. data/lib/action_view/helpers/csp_helper.rb +1 -1
  22. data/lib/action_view/helpers/csrf_helper.rb +2 -2
  23. data/lib/action_view/helpers/date_helper.rb +111 -43
  24. data/lib/action_view/helpers/debug_helper.rb +3 -1
  25. data/lib/action_view/helpers/form_helper.rb +211 -85
  26. data/lib/action_view/helpers/form_options_helper.rb +70 -33
  27. data/lib/action_view/helpers/form_tag_helper.rb +150 -53
  28. data/lib/action_view/helpers/javascript_helper.rb +3 -5
  29. data/lib/action_view/helpers/number_helper.rb +17 -16
  30. data/lib/action_view/helpers/output_safety_helper.rb +4 -4
  31. data/lib/action_view/helpers/rendering_helper.rb +5 -6
  32. data/lib/action_view/helpers/sanitize_helper.rb +3 -3
  33. data/lib/action_view/helpers/tag_helper.rb +37 -8
  34. data/lib/action_view/helpers/tags/base.rb +5 -25
  35. data/lib/action_view/helpers/tags/check_box.rb +1 -1
  36. data/lib/action_view/helpers/tags/collection_select.rb +1 -1
  37. data/lib/action_view/helpers/tags/file_field.rb +16 -0
  38. data/lib/action_view/helpers/tags/select.rb +1 -1
  39. data/lib/action_view/helpers/tags/time_field.rb +10 -1
  40. data/lib/action_view/helpers/tags/weekday_select.rb +28 -0
  41. data/lib/action_view/helpers/tags.rb +3 -2
  42. data/lib/action_view/helpers/text_helper.rb +25 -14
  43. data/lib/action_view/helpers/translation_helper.rb +12 -43
  44. data/lib/action_view/helpers/url_helper.rb +194 -123
  45. data/lib/action_view/helpers.rb +25 -25
  46. data/lib/action_view/layouts.rb +7 -4
  47. data/lib/action_view/lookup_context.rb +33 -52
  48. data/lib/action_view/model_naming.rb +2 -2
  49. data/lib/action_view/path_set.rb +16 -22
  50. data/lib/action_view/railtie.rb +19 -7
  51. data/lib/action_view/record_identifier.rb +1 -1
  52. data/lib/action_view/render_parser.rb +188 -0
  53. data/lib/action_view/renderer/abstract_renderer.rb +2 -2
  54. data/lib/action_view/renderer/partial_renderer.rb +1 -35
  55. data/lib/action_view/renderer/renderer.rb +4 -4
  56. data/lib/action_view/renderer/streaming_template_renderer.rb +3 -3
  57. data/lib/action_view/renderer/template_renderer.rb +6 -2
  58. data/lib/action_view/rendering.rb +3 -3
  59. data/lib/action_view/ripper_ast_parser.rb +198 -0
  60. data/lib/action_view/routing_url_for.rb +8 -5
  61. data/lib/action_view/template/error.rb +108 -13
  62. data/lib/action_view/template/handlers/erb.rb +6 -0
  63. data/lib/action_view/template/handlers.rb +3 -3
  64. data/lib/action_view/template/html.rb +3 -3
  65. data/lib/action_view/template/inline.rb +3 -3
  66. data/lib/action_view/template/raw_file.rb +3 -3
  67. data/lib/action_view/template/resolver.rb +89 -314
  68. data/lib/action_view/template/text.rb +3 -3
  69. data/lib/action_view/template/types.rb +14 -12
  70. data/lib/action_view/template.rb +18 -2
  71. data/lib/action_view/template_details.rb +66 -0
  72. data/lib/action_view/template_path.rb +64 -0
  73. data/lib/action_view/test_case.rb +7 -3
  74. data/lib/action_view/testing/resolvers.rb +11 -12
  75. data/lib/action_view/unbound_template.rb +33 -7
  76. data/lib/action_view/version.rb +1 -1
  77. data/lib/action_view/view_paths.rb +4 -4
  78. data/lib/action_view.rb +2 -3
  79. data/lib/assets/compiled/rails-ujs.js +36 -5
  80. metadata +23 -16
@@ -10,36 +10,18 @@ require "concurrent/map"
10
10
  module ActionView
11
11
  # = Action View Resolver
12
12
  class Resolver
13
- # Keeps all information about view path and builds virtual path.
14
- class Path
15
- attr_reader :name, :prefix, :partial, :virtual
16
- alias_method :partial?, :partial
17
-
18
- def self.build(name, prefix, partial)
19
- virtual = +""
20
- virtual << "#{prefix}/" unless prefix.empty?
21
- virtual << (partial ? "_#{name}" : name)
22
- new name, prefix, partial, virtual
23
- end
24
-
25
- def initialize(name, prefix, partial, virtual)
26
- @name = name
27
- @prefix = prefix
28
- @partial = partial
29
- @virtual = virtual
30
- end
31
-
32
- def to_str
33
- @virtual
34
- end
35
- alias :to_s :to_str
36
- end
13
+ Path = ActionView::TemplatePath
14
+ deprecate_constant :Path
37
15
 
38
16
  class PathParser # :nodoc:
17
+ ParsedPath = Struct.new(:path, :details)
18
+
39
19
  def build_path_regex
40
- handlers = Template::Handlers.extensions.map { |x| Regexp.escape(x) }.join("|")
41
- formats = Template::Types.symbols.map { |x| Regexp.escape(x) }.join("|")
42
- locales = "[a-z]{2}(?:-[A-Z]{2})?"
20
+ handlers = Regexp.union(Template::Handlers.extensions.map(&:to_s))
21
+ formats = Regexp.union(Template::Types.symbols.map(&:to_s))
22
+ available_locales = I18n.available_locales.map(&:to_s)
23
+ regular_locales = [/[a-z]{2}(?:[-_][A-Z]{2})?/]
24
+ locales = Regexp.union(available_locales + regular_locales)
43
25
  variants = "[^.]*"
44
26
 
45
27
  %r{
@@ -58,79 +40,15 @@ module ActionView
58
40
  def parse(path)
59
41
  @regex ||= build_path_regex
60
42
  match = @regex.match(path)
61
- {
62
- prefix: match[:prefix] || "",
63
- action: match[:action],
64
- partial: !!match[:partial],
65
- locale: match[:locale]&.to_sym,
66
- handler: match[:handler]&.to_sym,
67
- format: match[:format]&.to_sym,
68
- variant: match[:variant]
69
- }
70
- end
71
- end
72
-
73
- # Threadsafe template cache
74
- class Cache #:nodoc:
75
- class SmallCache < Concurrent::Map
76
- def initialize(options = {})
77
- super(options.merge(initial_capacity: 2))
78
- end
79
- end
80
-
81
- # Preallocate all the default blocks for performance/memory consumption reasons
82
- PARTIAL_BLOCK = lambda { |cache, partial| cache[partial] = SmallCache.new }
83
- PREFIX_BLOCK = lambda { |cache, prefix| cache[prefix] = SmallCache.new(&PARTIAL_BLOCK) }
84
- NAME_BLOCK = lambda { |cache, name| cache[name] = SmallCache.new(&PREFIX_BLOCK) }
85
- KEY_BLOCK = lambda { |cache, key| cache[key] = SmallCache.new(&NAME_BLOCK) }
86
-
87
- # Usually a majority of template look ups return nothing, use this canonical preallocated array to save memory
88
- NO_TEMPLATES = [].freeze
89
-
90
- def initialize
91
- @data = SmallCache.new(&KEY_BLOCK)
92
- @query_cache = SmallCache.new
93
- end
94
-
95
- def inspect
96
- "#{to_s[0..-2]} keys=#{@data.size} queries=#{@query_cache.size}>"
97
- end
98
-
99
- # Cache the templates returned by the block
100
- def cache(key, name, prefix, partial, locals)
101
- @data[key][name][prefix][partial][locals] ||= canonical_no_templates(yield)
102
- end
103
-
104
- def cache_query(query) # :nodoc:
105
- @query_cache[query] ||= canonical_no_templates(yield)
106
- end
107
-
108
- def clear
109
- @data.clear
110
- @query_cache.clear
111
- end
112
-
113
- # Get the cache size. Do not call this
114
- # method. This method is not guaranteed to be here ever.
115
- def size # :nodoc:
116
- size = 0
117
- @data.each_value do |v1|
118
- v1.each_value do |v2|
119
- v2.each_value do |v3|
120
- v3.each_value do |v4|
121
- size += v4.size
122
- end
123
- end
124
- end
125
- end
126
-
127
- size + @query_cache.size
43
+ path = TemplatePath.build(match[:action], match[:prefix] || "", !!match[:partial])
44
+ details = TemplateDetails.new(
45
+ match[:locale]&.to_sym,
46
+ match[:handler]&.to_sym,
47
+ match[:format]&.to_sym,
48
+ match[:variant]&.to_sym
49
+ )
50
+ ParsedPath.new(path, details)
128
51
  end
129
-
130
- private
131
- def canonical_no_templates(templates)
132
- templates.empty? ? NO_TEMPLATES : templates
133
- end
134
52
  end
135
53
 
136
54
  cattr_accessor :caching, default: true
@@ -139,25 +57,17 @@ module ActionView
139
57
  alias :caching? :caching
140
58
  end
141
59
 
142
- def initialize
143
- @cache = Cache.new
144
- end
145
-
146
60
  def clear_cache
147
- @cache.clear
148
61
  end
149
62
 
150
63
  # Normalizes the arguments and passes it on to find_templates.
151
64
  def find_all(name, prefix = nil, partial = false, details = {}, key = nil, locals = [])
152
- locals = locals.map(&:to_s).sort!.freeze
153
-
154
- cached(key, [name, prefix, partial], details, locals) do
155
- _find_all(name, prefix, partial, details, key, locals)
156
- end
65
+ _find_all(name, prefix, partial, details, key, locals)
157
66
  end
158
67
 
159
- def find_all_with_query(query) # :nodoc:
160
- @cache.cache_query(query) { find_template_paths(File.join(@path, query)) }
68
+ def all_template_paths # :nodoc:
69
+ # Not implemented by default
70
+ []
161
71
  end
162
72
 
163
73
  private
@@ -173,34 +83,18 @@ module ActionView
173
83
  def find_templates(name, prefix, partial, details, locals = [])
174
84
  raise NotImplementedError, "Subclasses must implement a find_templates(name, prefix, partial, details, locals = []) method"
175
85
  end
176
-
177
- # Handles templates caching. If a key is given and caching is on
178
- # always check the cache before hitting the resolver. Otherwise,
179
- # it always hits the resolver but if the key is present, check if the
180
- # resolver is fresher before returning it.
181
- def cached(key, path_info, details, locals)
182
- name, prefix, partial = path_info
183
-
184
- if key
185
- @cache.cache(key, name, prefix, partial, locals) do
186
- yield
187
- end
188
- else
189
- yield
190
- end
191
- end
192
86
  end
193
87
 
194
- # An abstract class that implements a Resolver with path semantics.
195
- class PathResolver < Resolver #:nodoc:
196
- EXTENSIONS = { locale: ".", formats: ".", variants: "+", handlers: "." }
197
- DEFAULT_PATTERN = ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}"
88
+ # A resolver that loads files from the filesystem.
89
+ class FileSystemResolver < Resolver
90
+ attr_reader :path
198
91
 
199
- def initialize
200
- @pattern = DEFAULT_PATTERN
92
+ def initialize(path)
93
+ raise ArgumentError, "path already is a Resolver class" if path.is_a?(Resolver)
201
94
  @unbound_templates = Concurrent::Map.new
202
95
  @path_parser = PathParser.new
203
- super
96
+ @path = File.expand_path(path)
97
+ super()
204
98
  end
205
99
 
206
100
  def clear_cache
@@ -209,26 +103,37 @@ module ActionView
209
103
  super
210
104
  end
211
105
 
212
- private
213
- def _find_all(name, prefix, partial, details, key, locals)
214
- path = Path.build(name, prefix, partial)
215
- query(path, details, details[:formats], locals, cache: !!key)
106
+ def to_s
107
+ @path.to_s
108
+ end
109
+ alias :to_path :to_s
110
+
111
+ def eql?(resolver)
112
+ self.class.equal?(resolver.class) && to_path == resolver.to_path
113
+ end
114
+ alias :== :eql?
115
+
116
+ def all_template_paths # :nodoc:
117
+ paths = template_glob("**/*")
118
+ paths.map do |filename|
119
+ filename.from(@path.size + 1).remove(/\.[^\/]*\z/)
120
+ end.uniq.map do |filename|
121
+ TemplatePath.parse(filename)
216
122
  end
123
+ end
217
124
 
218
- def query(path, details, formats, locals, cache:)
219
- template_paths = find_template_paths_from_details(path, details)
220
- template_paths = reject_files_external_to_app(template_paths)
125
+ private
126
+ def _find_all(name, prefix, partial, details, key, locals)
127
+ requested_details = key || TemplateDetails::Requested.new(**details)
128
+ cache = key ? @unbound_templates : Concurrent::Map.new
221
129
 
222
- template_paths.map do |template|
223
- unbound_template =
224
- if cache
225
- @unbound_templates.compute_if_absent([template, path.virtual]) do
226
- build_unbound_template(template, path.virtual)
227
- end
228
- else
229
- build_unbound_template(template, path.virtual)
230
- end
130
+ unbound_templates =
131
+ cache.compute_if_absent(TemplatePath.virtual(name, prefix, partial)) do
132
+ path = TemplatePath.build(name, prefix, partial)
133
+ unbound_templates_from_path(path)
134
+ end
231
135
 
136
+ filter_and_sort_by_details(unbound_templates, requested_details).map do |unbound_template|
232
137
  unbound_template.bind_locals(locals)
233
138
  end
234
139
  end
@@ -237,196 +142,66 @@ module ActionView
237
142
  Template::Sources::File.new(template)
238
143
  end
239
144
 
240
- def build_unbound_template(template, virtual_path)
241
- handler, format, variant = extract_handler_and_format_and_variant(template)
145
+ def build_unbound_template(template)
146
+ parsed = @path_parser.parse(template.from(@path.size + 1))
147
+ details = parsed.details
242
148
  source = source_for_template(template)
243
149
 
244
150
  UnboundTemplate.new(
245
151
  source,
246
152
  template,
247
- handler,
248
- virtual_path: virtual_path,
249
- format: format,
250
- variant: variant,
153
+ details: details,
154
+ virtual_path: parsed.path.virtual,
251
155
  )
252
156
  end
253
157
 
254
- def reject_files_external_to_app(files)
255
- files.reject { |filename| !inside_path?(@path, filename) }
256
- end
257
-
258
- def find_template_paths_from_details(path, details)
158
+ def unbound_templates_from_path(path)
259
159
  if path.name.include?(".")
260
- ActiveSupport::Deprecation.warn("Rendering actions with '.' in the name is deprecated: #{path}")
261
- end
262
-
263
- query = build_query(path, details)
264
- find_template_paths(query)
265
- end
266
-
267
- def find_template_paths(query)
268
- Dir[query].uniq.reject do |filename|
269
- File.directory?(filename) ||
270
- # deals with case-insensitive file systems.
271
- !File.fnmatch(query, filename, File::FNM_EXTGLOB)
272
- end
273
- end
274
-
275
- def inside_path?(path, filename)
276
- filename = File.expand_path(filename)
277
- path = File.join(path, "")
278
- filename.start_with?(path)
279
- end
280
-
281
- # Helper for building query glob string based on resolver's pattern.
282
- def build_query(path, details)
283
- query = @pattern.dup
284
-
285
- prefix = path.prefix.empty? ? "" : "#{escape_entry(path.prefix)}\\1"
286
- query.gsub!(/:prefix(\/)?/, prefix)
287
-
288
- partial = escape_entry(path.partial? ? "_#{path.name}" : path.name)
289
- query.gsub!(":action", partial)
290
-
291
- details.each do |ext, candidates|
292
- if ext == :variants && candidates == :any
293
- query.gsub!(/:#{ext}/, "*")
294
- else
295
- query.gsub!(/:#{ext}/, "{#{candidates.compact.uniq.join(',')}}")
296
- end
160
+ return []
297
161
  end
298
162
 
299
- File.expand_path(query, @path)
300
- end
301
-
302
- def escape_entry(entry)
303
- entry.gsub(/[*?{}\[\]]/, '\\\\\\&')
304
- end
305
-
306
- # Extract handler, formats and variant from path. If a format cannot be found neither
307
- # from the path, or the handler, we should return the array of formats given
308
- # to the resolver.
309
- def extract_handler_and_format_and_variant(path)
310
- details = @path_parser.parse(path)
311
-
312
- handler = Template.handler_for_extension(details[:handler])
313
- format = details[:format] || handler.try(:default_format)
314
- variant = details[:variant]
315
-
316
- # Template::Types[format] and handler.default_format can return nil
317
- [handler, format, variant]
318
- end
319
- end
320
-
321
- # A resolver that loads files from the filesystem.
322
- class FileSystemResolver < PathResolver
323
- attr_reader :path
324
-
325
- def initialize(path)
326
- raise ArgumentError, "path already is a Resolver class" if path.is_a?(Resolver)
327
- super()
328
- @path = File.expand_path(path)
329
- end
330
-
331
- def to_s
332
- @path.to_s
333
- end
334
- alias :to_path :to_s
335
-
336
- def eql?(resolver)
337
- self.class.equal?(resolver.class) && to_path == resolver.to_path
338
- end
339
- alias :== :eql?
340
- end
341
-
342
- # An Optimized resolver for Rails' most common case.
343
- class OptimizedFileSystemResolver < FileSystemResolver #:nodoc:
344
- def initialize(path)
345
- super(path)
346
- end
347
-
348
- private
349
- def find_candidate_template_paths(path)
350
163
  # Instead of checking for every possible path, as our other globs would
351
164
  # do, scan the directory for files with the right prefix.
352
- query = "#{escape_entry(File.join(@path, path))}*"
165
+ paths = template_glob("#{escape_entry(path.to_s)}*")
353
166
 
354
- Dir[query].reject do |filename|
355
- File.directory?(filename)
167
+ paths.map do |path|
168
+ build_unbound_template(path)
169
+ end.select do |template|
170
+ # Select for exact virtual path match, including case sensitivity
171
+ template.virtual_path == path.virtual
356
172
  end
357
173
  end
358
174
 
359
- def find_template_paths_from_details(path, details)
360
- if path.name.include?(".")
361
- # Fall back to the unoptimized resolver, which will warn
362
- return super
175
+ def filter_and_sort_by_details(templates, requested_details)
176
+ filtered_templates = templates.select do |template|
177
+ template.details.matches?(requested_details)
363
178
  end
364
179
 
365
- candidates = find_candidate_template_paths(path)
366
-
367
- regex = build_regex(path, details)
368
-
369
- candidates.uniq.reject do |filename|
370
- # This regex match does double duty of finding only files which match
371
- # details (instead of just matching the prefix) and also filtering for
372
- # case-insensitive file systems.
373
- !regex.match?(filename) ||
374
- File.directory?(filename)
375
- end.sort_by do |filename|
376
- # Because we scanned the directory, instead of checking for files
377
- # one-by-one, they will be returned in an arbitrary order.
378
- # We can use the matches found by the regex and sort by their index in
379
- # details.
380
- match = filename.match(regex)
381
- EXTENSIONS.keys.map do |ext|
382
- if ext == :variants && details[ext] == :any
383
- match[ext].nil? ? 0 : 1
384
- elsif match[ext].nil?
385
- # No match should be last
386
- details[ext].length
387
- else
388
- found = match[ext].to_sym
389
- details[ext].index(found)
390
- end
180
+ if filtered_templates.count > 1
181
+ filtered_templates.sort_by! do |template|
182
+ template.details.sort_key_for(requested_details)
391
183
  end
392
184
  end
393
- end
394
185
 
395
- def build_regex(path, details)
396
- query = Regexp.escape(File.join(@path, path))
397
- exts = EXTENSIONS.map do |ext, prefix|
398
- match =
399
- if ext == :variants && details[ext] == :any
400
- ".*?"
401
- else
402
- arr = details[ext].compact
403
- arr.uniq!
404
- arr.map! { |e| Regexp.escape(e) }
405
- arr.join("|")
406
- end
407
- prefix = Regexp.escape(prefix)
408
- "(#{prefix}(?<#{ext}>#{match}))?"
409
- end.join
410
-
411
- %r{\A#{query}#{exts}\z}
186
+ filtered_templates
412
187
  end
413
- end
414
188
 
415
- # The same as FileSystemResolver but does not allow templates to store
416
- # a virtual path since it is invalid for such resolvers.
417
- class FallbackFileSystemResolver < FileSystemResolver #:nodoc:
418
- private_class_method :new
189
+ # Safe glob within @path
190
+ def template_glob(glob)
191
+ query = File.join(escape_entry(@path), glob)
192
+ path_with_slash = File.join(@path, "")
419
193
 
420
- def self.instances
421
- [new(""), new("/")]
422
- end
194
+ Dir.glob(query).filter_map do |filename|
195
+ filename = File.expand_path(filename)
196
+ next if File.directory?(filename)
197
+ next unless filename.start_with?(path_with_slash)
423
198
 
424
- def build_unbound_template(template, _)
425
- super(template, nil)
426
- end
199
+ filename
200
+ end
201
+ end
427
202
 
428
- def reject_files_external_to_app(files)
429
- files
430
- end
203
+ def escape_entry(entry)
204
+ entry.gsub(/[*?{}\[\]]/, '\\\\\\&')
205
+ end
431
206
  end
432
207
  end
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module ActionView #:nodoc:
3
+ module ActionView # :nodoc:
4
4
  # = Action View Text Template
5
- class Template #:nodoc:
6
- class Text #:nodoc:
5
+ class Template # :nodoc:
6
+ class Text # :nodoc:
7
7
  attr_accessor :type
8
8
 
9
9
  def initialize(string)
@@ -3,8 +3,8 @@
3
3
  require "active_support/core_ext/module/attribute_accessors"
4
4
 
5
5
  module ActionView
6
- class Template #:nodoc:
7
- class Types
6
+ class Template # :nodoc:
7
+ module Types
8
8
  class Type
9
9
  SET = Struct.new(:symbols).new([ :html, :text, :js, :css, :xml, :json ])
10
10
 
@@ -37,21 +37,23 @@ module ActionView
37
37
  end
38
38
  end
39
39
 
40
- cattr_accessor :type_klass
40
+ class << self
41
+ attr_accessor :type_klass
41
42
 
42
- def self.delegate_to(klass)
43
- self.type_klass = klass
44
- end
43
+ def delegate_to(klass)
44
+ self.type_klass = klass
45
+ end
45
46
 
46
- delegate_to Type
47
+ def [](type)
48
+ type_klass[type]
49
+ end
47
50
 
48
- def self.[](type)
49
- type_klass[type]
51
+ def symbols
52
+ type_klass::SET.symbols
53
+ end
50
54
  end
51
55
 
52
- def self.symbols
53
- type_klass::SET.symbols
54
- end
56
+ delegate_to Type
55
57
  end
56
58
  end
57
59
  end
@@ -114,6 +114,9 @@ module ActionView
114
114
 
115
115
  extend Template::Handlers
116
116
 
117
+ singleton_class.attr_accessor :frozen_string_literal
118
+ @frozen_string_literal = false
119
+
117
120
  attr_reader :identifier, :handler
118
121
  attr_reader :variable, :format, :variant, :locals, :virtual_path
119
122
 
@@ -297,7 +300,11 @@ module ActionView
297
300
  end
298
301
 
299
302
  begin
300
- mod.module_eval(source, identifier, 0)
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
301
308
  rescue SyntaxError
302
309
  # Account for when code in the template is not syntactically valid; e.g. if we're using
303
310
  # ERB and the user writes <%= foo( %>, attempting to call a helper `foo` and interpolate
@@ -319,7 +326,16 @@ module ActionView
319
326
  # Only locals with valid variable names get set directly. Others will
320
327
  # still be available in local_assigns.
321
328
  locals = @locals - Module::RUBY_RESERVED_KEYWORDS
322
- locals = locals.grep(/\A@?(?![A-Z0-9])(?:[[:alnum:]_]|[^\0-\177])+\z/)
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
323
339
 
324
340
  # Assign for the same variable is to suppress unused variable warning
325
341
  locals.each_with_object(+"") { |key, code| code << "#{key} = local_assigns[:#{key}]; #{key} = #{key};" }
@@ -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