omg-actionview 8.0.0.alpha1

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 (130) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +25 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +40 -0
  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 +316 -0
  8. data/lib/action_view/buffers.rb +165 -0
  9. data/lib/action_view/cache_expiry.rb +69 -0
  10. data/lib/action_view/context.rb +32 -0
  11. data/lib/action_view/dependency_tracker/erb_tracker.rb +159 -0
  12. data/lib/action_view/dependency_tracker/ruby_tracker.rb +43 -0
  13. data/lib/action_view/dependency_tracker/wildcard_resolver.rb +32 -0
  14. data/lib/action_view/dependency_tracker.rb +41 -0
  15. data/lib/action_view/deprecator.rb +7 -0
  16. data/lib/action_view/digestor.rb +130 -0
  17. data/lib/action_view/flows.rb +75 -0
  18. data/lib/action_view/gem_version.rb +17 -0
  19. data/lib/action_view/helpers/active_model_helper.rb +54 -0
  20. data/lib/action_view/helpers/asset_tag_helper.rb +680 -0
  21. data/lib/action_view/helpers/asset_url_helper.rb +473 -0
  22. data/lib/action_view/helpers/atom_feed_helper.rb +205 -0
  23. data/lib/action_view/helpers/cache_helper.rb +315 -0
  24. data/lib/action_view/helpers/capture_helper.rb +236 -0
  25. data/lib/action_view/helpers/content_exfiltration_prevention_helper.rb +70 -0
  26. data/lib/action_view/helpers/controller_helper.rb +42 -0
  27. data/lib/action_view/helpers/csp_helper.rb +26 -0
  28. data/lib/action_view/helpers/csrf_helper.rb +35 -0
  29. data/lib/action_view/helpers/date_helper.rb +1266 -0
  30. data/lib/action_view/helpers/debug_helper.rb +38 -0
  31. data/lib/action_view/helpers/form_helper.rb +2765 -0
  32. data/lib/action_view/helpers/form_options_helper.rb +927 -0
  33. data/lib/action_view/helpers/form_tag_helper.rb +1088 -0
  34. data/lib/action_view/helpers/javascript_helper.rb +96 -0
  35. data/lib/action_view/helpers/number_helper.rb +165 -0
  36. data/lib/action_view/helpers/output_safety_helper.rb +70 -0
  37. data/lib/action_view/helpers/rendering_helper.rb +218 -0
  38. data/lib/action_view/helpers/sanitize_helper.rb +201 -0
  39. data/lib/action_view/helpers/tag_helper.rb +621 -0
  40. data/lib/action_view/helpers/tags/base.rb +138 -0
  41. data/lib/action_view/helpers/tags/check_box.rb +65 -0
  42. data/lib/action_view/helpers/tags/checkable.rb +18 -0
  43. data/lib/action_view/helpers/tags/collection_check_boxes.rb +37 -0
  44. data/lib/action_view/helpers/tags/collection_helpers.rb +118 -0
  45. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +31 -0
  46. data/lib/action_view/helpers/tags/collection_select.rb +33 -0
  47. data/lib/action_view/helpers/tags/color_field.rb +26 -0
  48. data/lib/action_view/helpers/tags/date_field.rb +14 -0
  49. data/lib/action_view/helpers/tags/date_select.rb +75 -0
  50. data/lib/action_view/helpers/tags/datetime_field.rb +39 -0
  51. data/lib/action_view/helpers/tags/datetime_local_field.rb +29 -0
  52. data/lib/action_view/helpers/tags/datetime_select.rb +10 -0
  53. data/lib/action_view/helpers/tags/email_field.rb +10 -0
  54. data/lib/action_view/helpers/tags/file_field.rb +26 -0
  55. data/lib/action_view/helpers/tags/grouped_collection_select.rb +34 -0
  56. data/lib/action_view/helpers/tags/hidden_field.rb +14 -0
  57. data/lib/action_view/helpers/tags/label.rb +84 -0
  58. data/lib/action_view/helpers/tags/month_field.rb +14 -0
  59. data/lib/action_view/helpers/tags/number_field.rb +20 -0
  60. data/lib/action_view/helpers/tags/password_field.rb +14 -0
  61. data/lib/action_view/helpers/tags/placeholderable.rb +24 -0
  62. data/lib/action_view/helpers/tags/radio_button.rb +32 -0
  63. data/lib/action_view/helpers/tags/range_field.rb +10 -0
  64. data/lib/action_view/helpers/tags/search_field.rb +27 -0
  65. data/lib/action_view/helpers/tags/select.rb +45 -0
  66. data/lib/action_view/helpers/tags/select_renderer.rb +56 -0
  67. data/lib/action_view/helpers/tags/tel_field.rb +10 -0
  68. data/lib/action_view/helpers/tags/text_area.rb +24 -0
  69. data/lib/action_view/helpers/tags/text_field.rb +33 -0
  70. data/lib/action_view/helpers/tags/time_field.rb +23 -0
  71. data/lib/action_view/helpers/tags/time_select.rb +10 -0
  72. data/lib/action_view/helpers/tags/time_zone_select.rb +25 -0
  73. data/lib/action_view/helpers/tags/translator.rb +39 -0
  74. data/lib/action_view/helpers/tags/url_field.rb +10 -0
  75. data/lib/action_view/helpers/tags/week_field.rb +14 -0
  76. data/lib/action_view/helpers/tags/weekday_select.rb +31 -0
  77. data/lib/action_view/helpers/tags.rb +47 -0
  78. data/lib/action_view/helpers/text_helper.rb +568 -0
  79. data/lib/action_view/helpers/translation_helper.rb +161 -0
  80. data/lib/action_view/helpers/url_helper.rb +812 -0
  81. data/lib/action_view/helpers.rb +68 -0
  82. data/lib/action_view/layouts.rb +434 -0
  83. data/lib/action_view/locale/en.yml +56 -0
  84. data/lib/action_view/log_subscriber.rb +132 -0
  85. data/lib/action_view/lookup_context.rb +299 -0
  86. data/lib/action_view/model_naming.rb +14 -0
  87. data/lib/action_view/path_registry.rb +57 -0
  88. data/lib/action_view/path_set.rb +84 -0
  89. data/lib/action_view/railtie.rb +132 -0
  90. data/lib/action_view/record_identifier.rb +118 -0
  91. data/lib/action_view/render_parser/prism_render_parser.rb +139 -0
  92. data/lib/action_view/render_parser/ripper_render_parser.rb +350 -0
  93. data/lib/action_view/render_parser.rb +40 -0
  94. data/lib/action_view/renderer/abstract_renderer.rb +186 -0
  95. data/lib/action_view/renderer/collection_renderer.rb +204 -0
  96. data/lib/action_view/renderer/object_renderer.rb +34 -0
  97. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +120 -0
  98. data/lib/action_view/renderer/partial_renderer.rb +267 -0
  99. data/lib/action_view/renderer/renderer.rb +107 -0
  100. data/lib/action_view/renderer/streaming_template_renderer.rb +107 -0
  101. data/lib/action_view/renderer/template_renderer.rb +115 -0
  102. data/lib/action_view/rendering.rb +190 -0
  103. data/lib/action_view/routing_url_for.rb +149 -0
  104. data/lib/action_view/tasks/cache_digests.rake +25 -0
  105. data/lib/action_view/template/error.rb +264 -0
  106. data/lib/action_view/template/handlers/builder.rb +25 -0
  107. data/lib/action_view/template/handlers/erb/erubi.rb +85 -0
  108. data/lib/action_view/template/handlers/erb.rb +157 -0
  109. data/lib/action_view/template/handlers/html.rb +11 -0
  110. data/lib/action_view/template/handlers/raw.rb +11 -0
  111. data/lib/action_view/template/handlers.rb +66 -0
  112. data/lib/action_view/template/html.rb +33 -0
  113. data/lib/action_view/template/inline.rb +22 -0
  114. data/lib/action_view/template/raw_file.rb +25 -0
  115. data/lib/action_view/template/renderable.rb +30 -0
  116. data/lib/action_view/template/resolver.rb +212 -0
  117. data/lib/action_view/template/sources/file.rb +17 -0
  118. data/lib/action_view/template/sources.rb +13 -0
  119. data/lib/action_view/template/text.rb +32 -0
  120. data/lib/action_view/template/types.rb +50 -0
  121. data/lib/action_view/template.rb +580 -0
  122. data/lib/action_view/template_details.rb +66 -0
  123. data/lib/action_view/template_path.rb +66 -0
  124. data/lib/action_view/test_case.rb +449 -0
  125. data/lib/action_view/testing/resolvers.rb +44 -0
  126. data/lib/action_view/unbound_template.rb +67 -0
  127. data/lib/action_view/version.rb +10 -0
  128. data/lib/action_view/view_paths.rb +117 -0
  129. data/lib/action_view.rb +104 -0
  130. metadata +275 -0
@@ -0,0 +1,580 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "delegate"
4
+
5
+ module ActionView
6
+ # = Action View \Template
7
+ class Template
8
+ extend ActiveSupport::Autoload
9
+
10
+ STRICT_LOCALS_REGEX = /\#\s+locals:\s+\((.*)\)/
11
+
12
+ # === Encodings in ActionView::Template
13
+ #
14
+ # ActionView::Template is one of a few sources of potential
15
+ # encoding issues in \Rails. This is because the source for
16
+ # templates are usually read from disk, and Ruby (like most
17
+ # encoding-aware programming languages) assumes that the
18
+ # String retrieved through File IO is encoded in the
19
+ # <tt>default_external</tt> encoding. In \Rails, the default
20
+ # <tt>default_external</tt> encoding is UTF-8.
21
+ #
22
+ # As a result, if a user saves their template as ISO-8859-1
23
+ # (for instance, using a non-Unicode-aware text editor),
24
+ # and uses characters outside of the ASCII range, their
25
+ # users will see diamonds with question marks in them in
26
+ # the browser.
27
+ #
28
+ # For the rest of this documentation, when we say "UTF-8",
29
+ # we mean "UTF-8 or whatever the default_internal encoding
30
+ # is set to". By default, it will be UTF-8.
31
+ #
32
+ # To mitigate this problem, we use a few strategies:
33
+ # 1. If the source is not valid UTF-8, we raise an exception
34
+ # when the template is compiled to alert the user
35
+ # to the problem.
36
+ # 2. The user can specify the encoding using Ruby-style
37
+ # encoding comments in any template engine. If such
38
+ # a comment is supplied, \Rails will apply that encoding
39
+ # to the resulting compiled source returned by the
40
+ # template handler.
41
+ # 3. In all cases, we transcode the resulting String to
42
+ # the UTF-8.
43
+ #
44
+ # This means that other parts of \Rails can always assume
45
+ # that templates are encoded in UTF-8, even if the original
46
+ # source of the template was not UTF-8.
47
+ #
48
+ # From a user's perspective, the easiest thing to do is
49
+ # to save your templates as UTF-8. If you do this, you
50
+ # do not need to do anything else for things to "just work".
51
+ #
52
+ # === Instructions for template handlers
53
+ #
54
+ # The easiest thing for you to do is to simply ignore
55
+ # encodings. \Rails will hand you the template source
56
+ # as the default_internal (generally UTF-8), raising
57
+ # an exception for the user before sending the template
58
+ # to you if it could not determine the original encoding.
59
+ #
60
+ # For the greatest simplicity, you can support only
61
+ # UTF-8 as the <tt>default_internal</tt>. This means
62
+ # that from the perspective of your handler, the
63
+ # entire pipeline is just UTF-8.
64
+ #
65
+ # === Advanced: Handlers with alternate metadata sources
66
+ #
67
+ # If you want to provide an alternate mechanism for
68
+ # specifying encodings (like ERB does via <%# encoding: ... %>),
69
+ # you may indicate that you will handle encodings yourself
70
+ # by implementing <tt>handles_encoding?</tt> on your handler.
71
+ #
72
+ # If you do, \Rails will not try to encode the String
73
+ # into the default_internal, passing you the unaltered
74
+ # bytes tagged with the assumed encoding (from
75
+ # default_external).
76
+ #
77
+ # In this case, make sure you return a String from
78
+ # your handler encoded in the default_internal. Since
79
+ # you are handling out-of-band metadata, you are
80
+ # also responsible for alerting the user to any
81
+ # problems with converting the user's data to
82
+ # the <tt>default_internal</tt>.
83
+ #
84
+ # To do so, simply raise +WrongEncodingError+ as follows:
85
+ #
86
+ # raise WrongEncodingError.new(
87
+ # problematic_string,
88
+ # expected_encoding
89
+ # )
90
+
91
+ ##
92
+ # :method: local_assigns
93
+ #
94
+ # Returns a hash with the defined local variables.
95
+ #
96
+ # Given this sub template rendering:
97
+ #
98
+ # <%= render "application/header", { headline: "Welcome", person: person } %>
99
+ #
100
+ # You can use +local_assigns+ in the sub templates to access the local variables:
101
+ #
102
+ # local_assigns[:headline] # => "Welcome"
103
+ #
104
+ # Each key in +local_assigns+ is available as a partial-local variable:
105
+ #
106
+ # local_assigns[:headline] # => "Welcome"
107
+ # headline # => "Welcome"
108
+ #
109
+ # Since +local_assigns+ is a +Hash+, it's compatible with Ruby 3.1's pattern
110
+ # matching assignment operator:
111
+ #
112
+ # local_assigns => { headline:, **options }
113
+ # headline # => "Welcome"
114
+ # options # => {}
115
+ #
116
+ # Pattern matching assignment also supports variable renaming:
117
+ #
118
+ # local_assigns => { headline: title }
119
+ # title # => "Welcome"
120
+ #
121
+ # If a template refers to a variable that isn't passed into the view as part
122
+ # of the <tt>locals: { ... }</tt> Hash, the template will raise an
123
+ # +ActionView::Template::Error+:
124
+ #
125
+ # <%# => raises ActionView::Template::Error %>
126
+ # <% alerts.each do |alert| %>
127
+ # <p><%= alert %></p>
128
+ # <% end %>
129
+ #
130
+ # Since +local_assigns+ returns a +Hash+ instance, you can conditionally
131
+ # read a variable, then fall back to a default value when
132
+ # the key isn't part of the <tt>locals: { ... }</tt> options:
133
+ #
134
+ # <% local_assigns.fetch(:alerts, []).each do |alert| %>
135
+ # <p><%= alert %></p>
136
+ # <% end %>
137
+ #
138
+ # Combining Ruby 3.1's pattern matching assignment with calls to
139
+ # +Hash#with_defaults+ enables compact partial-local variable
140
+ # assignments:
141
+ #
142
+ # <% local_assigns.with_defaults(alerts: []) => { headline:, alerts: } %>
143
+ #
144
+ # <h1><%= headline %></h1>
145
+ #
146
+ # <% alerts.each do |alert| %>
147
+ # <p><%= alert %></p>
148
+ # <% end %>
149
+ #
150
+ # By default, templates will accept any <tt>locals</tt> as keyword arguments
151
+ # and make them available to <tt>local_assigns</tt>. To restrict what
152
+ # <tt>local_assigns</tt> a template will accept, add a <tt>locals:</tt> magic comment:
153
+ #
154
+ # <%# locals: (headline:, alerts: []) %>
155
+ #
156
+ # <h1><%= headline %></h1>
157
+ #
158
+ # <% alerts.each do |alert| %>
159
+ # <p><%= alert %></p>
160
+ # <% end %>
161
+ #
162
+ # Read more about strict locals in {Action View Overview}[https://guides.rubyonrails.org/action_view_overview.html#strict-locals]
163
+ # in the guides.
164
+
165
+ eager_autoload do
166
+ autoload :Error
167
+ autoload :RawFile
168
+ autoload :Renderable
169
+ autoload :Handlers
170
+ autoload :HTML
171
+ autoload :Inline
172
+ autoload :Types
173
+ autoload :Sources
174
+ autoload :Text
175
+ autoload :Types
176
+ end
177
+
178
+ extend Template::Handlers
179
+
180
+ singleton_class.attr_accessor :frozen_string_literal
181
+ @frozen_string_literal = false
182
+
183
+ class << self # :nodoc:
184
+ def mime_types_implementation=(implementation)
185
+ # This method isn't thread-safe, but it's not supposed
186
+ # to be called after initialization
187
+ if self::Types != implementation
188
+ remove_const(:Types)
189
+ const_set(:Types, implementation)
190
+ end
191
+ end
192
+ end
193
+
194
+ attr_reader :identifier, :handler
195
+ attr_reader :variable, :format, :variant, :virtual_path
196
+
197
+ NONE = Object.new
198
+
199
+ def initialize(source, identifier, handler, locals:, format: nil, variant: nil, virtual_path: nil)
200
+ @source = source.dup
201
+ @identifier = identifier
202
+ @handler = handler
203
+ @compiled = false
204
+ @locals = locals
205
+ @virtual_path = virtual_path
206
+
207
+ @variable = if @virtual_path
208
+ base = @virtual_path.end_with?("/") ? "" : ::File.basename(@virtual_path)
209
+ base =~ /\A_?(.*?)(?:\.\w+)*\z/
210
+ $1.to_sym
211
+ end
212
+
213
+ @format = format
214
+ @variant = variant
215
+ @compile_mutex = Mutex.new
216
+ @strict_locals = NONE
217
+ @strict_local_keys = nil
218
+ @type = nil
219
+ end
220
+
221
+ # The locals this template has been or will be compiled for, or nil if this
222
+ # is a strict locals template.
223
+ def locals
224
+ if strict_locals?
225
+ nil
226
+ else
227
+ @locals
228
+ end
229
+ end
230
+
231
+ def spot(location) # :nodoc:
232
+ ast = RubyVM::AbstractSyntaxTree.parse(compiled_source, keep_script_lines: true)
233
+ node_id = RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location(location)
234
+ node = find_node_by_id(ast, node_id)
235
+
236
+ ErrorHighlight.spot(node)
237
+ end
238
+
239
+ # Translate an error location returned by ErrorHighlight to the correct
240
+ # source location inside the template.
241
+ def translate_location(backtrace_location, spot)
242
+ if handler.respond_to?(:translate_location)
243
+ handler.translate_location(spot, backtrace_location, encode!) || spot
244
+ else
245
+ spot
246
+ end
247
+ end
248
+
249
+ # Returns whether the underlying handler supports streaming. If so,
250
+ # a streaming buffer *may* be passed when it starts rendering.
251
+ def supports_streaming?
252
+ handler.respond_to?(:supports_streaming?) && handler.supports_streaming?
253
+ end
254
+
255
+ # Render a template. If the template was not compiled yet, it is done
256
+ # exactly before rendering.
257
+ #
258
+ # This method is instrumented as "!render_template.action_view". Notice that
259
+ # we use a bang in this instrumentation because you don't want to
260
+ # consume this in production. This is only slow if it's being listened to.
261
+ def render(view, locals, buffer = nil, implicit_locals: [], add_to_stack: true, &block)
262
+ instrument_render_template do
263
+ compile!(view)
264
+
265
+ if strict_locals? && @strict_local_keys && !implicit_locals.empty?
266
+ locals_to_ignore = implicit_locals - @strict_local_keys
267
+ locals.except!(*locals_to_ignore)
268
+ end
269
+
270
+ if buffer
271
+ view._run(method_name, self, locals, buffer, add_to_stack: add_to_stack, has_strict_locals: strict_locals?, &block)
272
+ nil
273
+ else
274
+ result = view._run(method_name, self, locals, OutputBuffer.new, add_to_stack: add_to_stack, has_strict_locals: strict_locals?, &block)
275
+ result.is_a?(OutputBuffer) ? result.to_s : result
276
+ end
277
+ end
278
+ rescue => e
279
+ handle_render_error(view, e)
280
+ end
281
+
282
+ def type
283
+ @type ||= Types[format]
284
+ end
285
+
286
+ def short_identifier
287
+ @short_identifier ||= defined?(Rails.root) ? identifier.delete_prefix("#{Rails.root}/") : identifier
288
+ end
289
+
290
+ def inspect
291
+ "#<#{self.class.name} #{short_identifier} locals=#{locals.inspect}>"
292
+ end
293
+
294
+ def source
295
+ @source.to_s
296
+ end
297
+
298
+ LEADING_ENCODING_REGEXP = /\A#{ENCODING_FLAG}/
299
+ private_constant :LEADING_ENCODING_REGEXP
300
+
301
+ # This method is responsible for properly setting the encoding of the
302
+ # source. Until this point, we assume that the source is BINARY data.
303
+ # If no additional information is supplied, we assume the encoding is
304
+ # the same as <tt>Encoding.default_external</tt>.
305
+ #
306
+ # The user can also specify the encoding via a comment on the first
307
+ # line of the template (<tt># encoding: NAME-OF-ENCODING</tt>). This will work
308
+ # with any template engine, as we process out the encoding comment
309
+ # before passing the source on to the template engine, leaving a
310
+ # blank line in its stead.
311
+ def encode!
312
+ source = self.source
313
+
314
+ return source unless source.encoding == Encoding::BINARY
315
+
316
+ # Look for # encoding: *. If we find one, we'll encode the
317
+ # String in that encoding, otherwise, we'll use the
318
+ # default external encoding.
319
+ if source.sub!(LEADING_ENCODING_REGEXP, "")
320
+ encoding = magic_encoding = $1
321
+ else
322
+ encoding = Encoding.default_external
323
+ end
324
+
325
+ # Tag the source with the default external encoding
326
+ # or the encoding specified in the file
327
+ source.force_encoding(encoding)
328
+
329
+ # If the user didn't specify an encoding, and the handler
330
+ # handles encodings, we simply pass the String as is to
331
+ # the handler (with the default_external tag)
332
+ if !magic_encoding && @handler.respond_to?(:handles_encoding?) && @handler.handles_encoding?
333
+ source
334
+ # Otherwise, if the String is valid in the encoding,
335
+ # encode immediately to default_internal. This means
336
+ # that if a handler doesn't handle encodings, it will
337
+ # always get Strings in the default_internal
338
+ elsif source.valid_encoding?
339
+ source.encode!
340
+ # Otherwise, since the String is invalid in the encoding
341
+ # specified, raise an exception
342
+ else
343
+ raise WrongEncodingError.new(source, encoding)
344
+ end
345
+ end
346
+
347
+ # This method is responsible for marking a template as having strict locals
348
+ # which means the template can only accept the locals defined in a magic
349
+ # comment. For example, if your template acceps the locals +title+ and
350
+ # +comment_count+, add the following to your template file:
351
+ #
352
+ # <%# locals: (title: "Default title", comment_count: 0) %>
353
+ #
354
+ # Strict locals are useful for validating template arguments and for
355
+ # specifying defaults.
356
+ def strict_locals!
357
+ if @strict_locals == NONE
358
+ self.source.sub!(STRICT_LOCALS_REGEX, "")
359
+ @strict_locals = $1
360
+
361
+ return if @strict_locals.nil? # Magic comment not found
362
+
363
+ @strict_locals = "**nil" if @strict_locals.blank?
364
+ end
365
+
366
+ @strict_locals
367
+ end
368
+
369
+ # Returns whether a template is using strict locals.
370
+ def strict_locals?
371
+ strict_locals!
372
+ end
373
+
374
+ # Exceptions are marshalled when using the parallel test runner with DRb, so we need
375
+ # to ensure that references to the template object can be marshalled as well. This means forgoing
376
+ # the marshalling of the compiler mutex and instantiating that again on unmarshalling.
377
+ def marshal_dump # :nodoc:
378
+ [ @source, @identifier, @handler, @compiled, @locals, @virtual_path, @format, @variant ]
379
+ end
380
+
381
+ def marshal_load(array) # :nodoc:
382
+ @source, @identifier, @handler, @compiled, @locals, @virtual_path, @format, @variant = *array
383
+ @compile_mutex = Mutex.new
384
+ end
385
+
386
+ def method_name # :nodoc:
387
+ @method_name ||= begin
388
+ m = +"_#{identifier_method_name}__#{@identifier.hash}_#{__id__}"
389
+ m.tr!("-", "_")
390
+ m
391
+ end
392
+ end
393
+
394
+ private
395
+ def find_node_by_id(node, node_id)
396
+ return node if node.node_id == node_id
397
+
398
+ node.children.grep(node.class).each do |child|
399
+ found = find_node_by_id(child, node_id)
400
+ return found if found
401
+ end
402
+
403
+ false
404
+ end
405
+
406
+ # Compile a template. This method ensures a template is compiled
407
+ # just once and removes the source after it is compiled.
408
+ def compile!(view)
409
+ return if @compiled
410
+
411
+ # Templates can be used concurrently in threaded environments
412
+ # so compilation and any instance variable modification must
413
+ # be synchronized
414
+ @compile_mutex.synchronize do
415
+ # Any thread holding this lock will be compiling the template needed
416
+ # by the threads waiting. So re-check the @compiled flag to avoid
417
+ # re-compilation
418
+ return if @compiled
419
+
420
+ mod = view.compiled_method_container
421
+
422
+ instrument("!compile_template") do
423
+ compile(mod)
424
+ end
425
+
426
+ @compiled = true
427
+ end
428
+ end
429
+
430
+ # This method compiles the source of the template. The compilation of templates
431
+ # involves setting strict_locals! if applicable, encoding the template, and setting
432
+ # frozen string literal.
433
+ def compiled_source
434
+ set_strict_locals = strict_locals!
435
+ source = encode!
436
+ code = @handler.call(self, source)
437
+
438
+ method_arguments =
439
+ if set_strict_locals
440
+ if set_strict_locals.include?("&")
441
+ "local_assigns, output_buffer, #{set_strict_locals}"
442
+ else
443
+ "local_assigns, output_buffer, #{set_strict_locals}, &_"
444
+ end
445
+ else
446
+ "local_assigns, output_buffer, &_"
447
+ end
448
+
449
+ # Make sure that the resulting String to be eval'd is in the
450
+ # encoding of the code
451
+ source = +<<-end_src
452
+ def #{method_name}(#{method_arguments})
453
+ @virtual_path = #{@virtual_path.inspect};#{locals_code};#{code}
454
+ end
455
+ end_src
456
+
457
+ # Make sure the source is in the encoding of the returned code
458
+ source.force_encoding(code.encoding)
459
+
460
+ # In case we get back a String from a handler that is not in
461
+ # BINARY or the default_internal, encode it to the default_internal
462
+ source.encode!
463
+
464
+ # Now, validate that the source we got back from the template
465
+ # handler is valid in the default_internal. This is for handlers
466
+ # that handle encoding but screw up
467
+ unless source.valid_encoding?
468
+ raise WrongEncodingError.new(source, Encoding.default_internal)
469
+ end
470
+
471
+ if Template.frozen_string_literal
472
+ "# frozen_string_literal: true\n#{source}"
473
+ else
474
+ source
475
+ end
476
+ end
477
+
478
+ # Among other things, this method is responsible for properly setting
479
+ # the encoding of the compiled template.
480
+ #
481
+ # If the template engine handles encodings, we send the encoded
482
+ # String to the engine without further processing. This allows
483
+ # the template engine to support additional mechanisms for
484
+ # specifying the encoding. For instance, ERB supports <%# encoding: %>
485
+ #
486
+ # Otherwise, after we figure out the correct encoding, we then
487
+ # encode the source into <tt>Encoding.default_internal</tt>.
488
+ # In general, this means that templates will be UTF-8 inside of Rails,
489
+ # regardless of the original source encoding.
490
+ def compile(mod)
491
+ begin
492
+ mod.module_eval(compiled_source, identifier, offset)
493
+ rescue SyntaxError
494
+ # Account for when code in the template is not syntactically valid; e.g. if we're using
495
+ # ERB and the user writes <%= foo( %>, attempting to call a helper `foo` and interpolate
496
+ # the result into the template, but missing an end parenthesis.
497
+ raise SyntaxErrorInTemplate.new(self, encode!)
498
+ end
499
+
500
+ return unless strict_locals?
501
+
502
+ parameters = mod.instance_method(method_name).parameters
503
+ parameters -= [[:req, :local_assigns], [:req, :output_buffer]]
504
+
505
+ # Check compiled method parameters to ensure that only kwargs
506
+ # were provided as strict locals, preventing `locals: (foo, *foo)` etc
507
+ # and allowing `locals: (foo:)`.
508
+ non_kwarg_parameters = parameters.select do |parameter|
509
+ ![:keyreq, :key, :keyrest, :nokey].include?(parameter[0])
510
+ end
511
+
512
+ non_kwarg_parameters.pop if non_kwarg_parameters.last == %i(block _)
513
+
514
+ unless non_kwarg_parameters.empty?
515
+ mod.undef_method(method_name)
516
+
517
+ raise ArgumentError.new(
518
+ "#{non_kwarg_parameters.map { |_, name| "`#{name}`" }.to_sentence} set as non-keyword " \
519
+ "#{'argument'.pluralize(non_kwarg_parameters.length)} for #{short_identifier}. " \
520
+ "Locals can only be set as keyword arguments."
521
+ )
522
+ end
523
+
524
+ unless parameters.any? { |type, _| type == :keyrest }
525
+ parameters.map!(&:last)
526
+ parameters.sort!
527
+ @strict_local_keys = parameters.freeze
528
+ end
529
+ end
530
+
531
+ def offset
532
+ if Template.frozen_string_literal
533
+ -1
534
+ else
535
+ 0
536
+ end
537
+ end
538
+
539
+ def handle_render_error(view, e)
540
+ if e.is_a?(Template::Error)
541
+ e.sub_template_of(self)
542
+ raise e
543
+ else
544
+ raise Template::Error.new(self)
545
+ end
546
+ end
547
+
548
+ RUBY_RESERVED_KEYWORDS = ::ActiveSupport::Delegation::RUBY_RESERVED_KEYWORDS
549
+ private_constant :RUBY_RESERVED_KEYWORDS
550
+
551
+ def locals_code
552
+ return "" if strict_locals?
553
+
554
+ # Only locals with valid variable names get set directly. Others will
555
+ # still be available in local_assigns.
556
+ locals = @locals - RUBY_RESERVED_KEYWORDS
557
+
558
+ locals = locals.grep(/\A(?![A-Z0-9])(?:[[:alnum:]_]|[^\0-\177])+\z/)
559
+
560
+ # Assign for the same variable is to suppress unused variable warning
561
+ locals.each_with_object(+"") { |key, code| code << "#{key} = local_assigns[:#{key}]; #{key} = #{key};" }
562
+ end
563
+
564
+ def identifier_method_name
565
+ short_identifier.tr("^a-z_", "_")
566
+ end
567
+
568
+ def instrument(action, &block) # :doc:
569
+ ActiveSupport::Notifications.instrument("#{action}.action_view", instrument_payload, &block)
570
+ end
571
+
572
+ def instrument_render_template(&block)
573
+ ActiveSupport::Notifications.instrument("!render_template.action_view", instrument_payload, &block)
574
+ end
575
+
576
+ def instrument_payload
577
+ { virtual_path: @virtual_path, identifier: @identifier }
578
+ end
579
+ end
580
+ 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