actionview 6.1.7.2 → 7.0.6

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