actionview 7.0.8.7 → 7.2.2.1

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 (95) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +59 -454
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  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 +52 -14
  8. data/lib/action_view/buffers.rb +106 -8
  9. data/lib/action_view/cache_expiry.rb +44 -41
  10. data/lib/action_view/context.rb +1 -1
  11. data/lib/action_view/dependency_tracker/{ripper_tracker.rb → ruby_tracker.rb} +4 -3
  12. data/lib/action_view/dependency_tracker.rb +1 -1
  13. data/lib/action_view/deprecator.rb +7 -0
  14. data/lib/action_view/digestor.rb +1 -1
  15. data/lib/action_view/gem_version.rb +4 -4
  16. data/lib/action_view/helpers/active_model_helper.rb +1 -1
  17. data/lib/action_view/helpers/asset_tag_helper.rb +151 -55
  18. data/lib/action_view/helpers/asset_url_helper.rb +6 -5
  19. data/lib/action_view/helpers/atom_feed_helper.rb +5 -5
  20. data/lib/action_view/helpers/cache_helper.rb +7 -13
  21. data/lib/action_view/helpers/capture_helper.rb +30 -10
  22. data/lib/action_view/helpers/content_exfiltration_prevention_helper.rb +70 -0
  23. data/lib/action_view/helpers/controller_helper.rb +6 -0
  24. data/lib/action_view/helpers/csp_helper.rb +2 -2
  25. data/lib/action_view/helpers/csrf_helper.rb +3 -3
  26. data/lib/action_view/helpers/date_helper.rb +17 -19
  27. data/lib/action_view/helpers/debug_helper.rb +3 -3
  28. data/lib/action_view/helpers/form_helper.rb +248 -214
  29. data/lib/action_view/helpers/form_options_helper.rb +2 -1
  30. data/lib/action_view/helpers/form_tag_helper.rb +125 -58
  31. data/lib/action_view/helpers/javascript_helper.rb +1 -0
  32. data/lib/action_view/helpers/number_helper.rb +37 -330
  33. data/lib/action_view/helpers/output_safety_helper.rb +6 -6
  34. data/lib/action_view/helpers/rendering_helper.rb +1 -1
  35. data/lib/action_view/helpers/sanitize_helper.rb +51 -21
  36. data/lib/action_view/helpers/tag_helper.rb +210 -42
  37. data/lib/action_view/helpers/tags/base.rb +11 -52
  38. data/lib/action_view/helpers/tags/collection_check_boxes.rb +1 -0
  39. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +1 -0
  40. data/lib/action_view/helpers/tags/collection_select.rb +3 -0
  41. data/lib/action_view/helpers/tags/date_field.rb +1 -1
  42. data/lib/action_view/helpers/tags/date_select.rb +2 -0
  43. data/lib/action_view/helpers/tags/datetime_field.rb +14 -6
  44. data/lib/action_view/helpers/tags/datetime_local_field.rb +11 -2
  45. data/lib/action_view/helpers/tags/grouped_collection_select.rb +3 -0
  46. data/lib/action_view/helpers/tags/month_field.rb +1 -1
  47. data/lib/action_view/helpers/tags/select.rb +3 -0
  48. data/lib/action_view/helpers/tags/select_renderer.rb +56 -0
  49. data/lib/action_view/helpers/tags/time_field.rb +1 -1
  50. data/lib/action_view/helpers/tags/time_zone_select.rb +3 -0
  51. data/lib/action_view/helpers/tags/week_field.rb +1 -1
  52. data/lib/action_view/helpers/tags/weekday_select.rb +3 -0
  53. data/lib/action_view/helpers/tags.rb +2 -0
  54. data/lib/action_view/helpers/text_helper.rb +157 -85
  55. data/lib/action_view/helpers/translation_helper.rb +3 -3
  56. data/lib/action_view/helpers/url_helper.rb +35 -80
  57. data/lib/action_view/helpers.rb +2 -0
  58. data/lib/action_view/layouts.rb +8 -8
  59. data/lib/action_view/log_subscriber.rb +57 -36
  60. data/lib/action_view/lookup_context.rb +29 -13
  61. data/lib/action_view/path_registry.rb +57 -0
  62. data/lib/action_view/path_set.rb +13 -14
  63. data/lib/action_view/railtie.rb +25 -3
  64. data/lib/action_view/record_identifier.rb +15 -8
  65. data/lib/action_view/render_parser/prism_render_parser.rb +127 -0
  66. data/lib/action_view/{ripper_ast_parser.rb → render_parser/ripper_render_parser.rb} +156 -13
  67. data/lib/action_view/render_parser.rb +21 -169
  68. data/lib/action_view/renderer/abstract_renderer.rb +2 -2
  69. data/lib/action_view/renderer/collection_renderer.rb +10 -2
  70. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +2 -1
  71. data/lib/action_view/renderer/partial_renderer.rb +2 -1
  72. data/lib/action_view/renderer/renderer.rb +34 -38
  73. data/lib/action_view/renderer/streaming_template_renderer.rb +3 -2
  74. data/lib/action_view/renderer/template_renderer.rb +3 -2
  75. data/lib/action_view/rendering.rb +26 -8
  76. data/lib/action_view/template/error.rb +14 -1
  77. data/lib/action_view/template/handlers/builder.rb +4 -4
  78. data/lib/action_view/template/handlers/erb/erubi.rb +23 -27
  79. data/lib/action_view/template/handlers/erb.rb +73 -1
  80. data/lib/action_view/template/handlers.rb +1 -1
  81. data/lib/action_view/template/html.rb +1 -1
  82. data/lib/action_view/template/raw_file.rb +1 -1
  83. data/lib/action_view/template/renderable.rb +8 -2
  84. data/lib/action_view/template/resolver.rb +9 -3
  85. data/lib/action_view/template/text.rb +1 -1
  86. data/lib/action_view/template/types.rb +25 -34
  87. data/lib/action_view/template.rb +278 -55
  88. data/lib/action_view/template_path.rb +2 -0
  89. data/lib/action_view/test_case.rb +181 -28
  90. data/lib/action_view/unbound_template.rb +17 -7
  91. data/lib/action_view/version.rb +1 -1
  92. data/lib/action_view/view_paths.rb +15 -24
  93. data/lib/action_view.rb +4 -1
  94. metadata +26 -26
  95. data/lib/assets/compiled/rails-ujs.js +0 -777
@@ -13,6 +13,7 @@ module ActionView
13
13
  config.action_view.image_loading = nil
14
14
  config.action_view.image_decoding = nil
15
15
  config.action_view.apply_stylesheet_media_default = true
16
+ config.action_view.prepend_content_exfiltration_prevention = false
16
17
 
17
18
  config.eager_load_namespaces << ActionView
18
19
 
@@ -40,6 +41,17 @@ module ActionView
40
41
  end
41
42
  end
42
43
 
44
+ config.after_initialize do |app|
45
+ prepend_content_exfiltration_prevention = app.config.action_view.delete(:prepend_content_exfiltration_prevention)
46
+ ActionView::Helpers::ContentExfiltrationPreventionHelper.prepend_content_exfiltration_prevention = prepend_content_exfiltration_prevention
47
+ end
48
+
49
+ config.after_initialize do |app|
50
+ if klass = app.config.action_view.delete(:sanitizer_vendor)
51
+ ActionView::Helpers::SanitizeHelper.sanitizer_vendor = klass
52
+ end
53
+ end
54
+
43
55
  config.after_initialize do |app|
44
56
  button_to_generates_button_tag = app.config.action_view.delete(:button_to_generates_button_tag)
45
57
  unless button_to_generates_button_tag.nil?
@@ -67,6 +79,10 @@ module ActionView
67
79
  end
68
80
  end
69
81
 
82
+ initializer "action_view.deprecator", before: :load_environment_config do |app|
83
+ app.deprecators[:action_view] = ActionView.deprecator
84
+ end
85
+
70
86
  initializer "action_view.logger" do
71
87
  ActiveSupport.on_load(:action_view) { self.logger ||= Rails.logger }
72
88
  end
@@ -74,7 +90,7 @@ module ActionView
74
90
  initializer "action_view.caching" do |app|
75
91
  ActiveSupport.on_load(:action_view) do
76
92
  if app.config.action_view.cache_template_loading.nil?
77
- ActionView::Resolver.caching = app.config.cache_classes
93
+ ActionView::Resolver.caching = !app.config.reloading_enabled?
78
94
  end
79
95
  end
80
96
  end
@@ -91,13 +107,19 @@ module ActionView
91
107
 
92
108
  config.after_initialize do |app|
93
109
  enable_caching = if app.config.action_view.cache_template_loading.nil?
94
- app.config.cache_classes
110
+ !app.config.reloading_enabled?
95
111
  else
96
112
  app.config.action_view.cache_template_loading
97
113
  end
98
114
 
99
115
  unless enable_caching
100
- app.executor.register_hook ActionView::CacheExpiry::Executor.new(watcher: app.config.file_watcher)
116
+ view_reloader = ActionView::CacheExpiry::ViewReloader.new(watcher: app.config.file_watcher)
117
+
118
+ app.reloaders << view_reloader
119
+ app.reloader.to_run do
120
+ require_unload_lock!
121
+ view_reloader.execute
122
+ end
101
123
  end
102
124
  end
103
125
 
@@ -4,6 +4,8 @@ require "active_support/core_ext/module"
4
4
  require "action_view/model_naming"
5
5
 
6
6
  module ActionView
7
+ # = Action View \Record \Identifier
8
+ #
7
9
  # RecordIdentifier encapsulates methods used by various ActionView helpers
8
10
  # to associate records with DOM elements.
9
11
  #
@@ -31,6 +33,8 @@ module ActionView
31
33
  # automatically generated, following naming conventions encapsulated by the
32
34
  # RecordIdentifier methods #dom_id and #dom_class:
33
35
  #
36
+ # dom_id(Post) # => "new_post"
37
+ # dom_class(Post) # => "post"
34
38
  # dom_id(Post.new) # => "new_post"
35
39
  # dom_class(Post.new) # => "post"
36
40
  # dom_id(Post.find 42) # => "post_42"
@@ -79,18 +83,21 @@ module ActionView
79
83
  # The DOM id convention is to use the singular form of an object or class with the id following an underscore.
80
84
  # If no id is found, prefix with "new_" instead.
81
85
  #
82
- # dom_id(Post.find(45)) # => "post_45"
83
- # dom_id(Post.new) # => "new_post"
86
+ # dom_id(Post.find(45)) # => "post_45"
87
+ # dom_id(Post) # => "new_post"
84
88
  #
85
89
  # If you need to address multiple instances of the same class in the same view, you can prefix the dom_id:
86
90
  #
87
91
  # dom_id(Post.find(45), :edit) # => "edit_post_45"
88
- # dom_id(Post.new, :custom) # => "custom_post"
89
- def dom_id(record, prefix = nil)
90
- if record_id = record_key_for_dom_id(record)
91
- "#{dom_class(record, prefix)}#{JOIN}#{record_id}"
92
+ # dom_id(Post, :custom) # => "custom_post"
93
+ def dom_id(record_or_class, prefix = nil)
94
+ raise ArgumentError, "dom_id must be passed a record_or_class as the first argument, you passed #{record_or_class.inspect}" unless record_or_class
95
+
96
+ record_id = record_key_for_dom_id(record_or_class) unless record_or_class.is_a?(Class)
97
+ if record_id
98
+ "#{dom_class(record_or_class, prefix)}#{JOIN}#{record_id}"
92
99
  else
93
- dom_class(record, prefix || NEW)
100
+ dom_class(record_or_class, prefix || NEW)
94
101
  end
95
102
  end
96
103
 
@@ -105,7 +112,7 @@ module ActionView
105
112
  # make sure yourself that your dom ids are valid, in case you override this method.
106
113
  def record_key_for_dom_id(record) # :doc:
107
114
  key = convert_to_model(record).to_key
108
- key ? key.join(JOIN) : key
115
+ key && key.all? ? key.join(JOIN) : nil
109
116
  end
110
117
  end
111
118
  end
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ module RenderParser
5
+ class PrismRenderParser < Base # :nodoc:
6
+ def render_calls
7
+ queue = [Prism.parse(@code).value]
8
+ templates = []
9
+
10
+ while (node = queue.shift)
11
+ queue.concat(node.compact_child_nodes)
12
+ next unless node.is_a?(Prism::CallNode)
13
+
14
+ options = render_call_options(node)
15
+ next unless options
16
+
17
+ render_type = (options.keys & RENDER_TYPE_KEYS)[0]
18
+ template, object_template = render_call_template(options[render_type])
19
+ next unless template
20
+
21
+ if options.key?(:object) || options.key?(:collection) || object_template
22
+ next if options.key?(:object) && options.key?(:collection)
23
+ next unless options.key?(:partial)
24
+ end
25
+
26
+ if options[:spacer_template].is_a?(Prism::StringNode)
27
+ templates << partial_to_virtual_path(:partial, options[:spacer_template].unescaped)
28
+ end
29
+
30
+ templates << partial_to_virtual_path(render_type, template)
31
+
32
+ if render_type != :layout && options[:layout].is_a?(Prism::StringNode)
33
+ templates << partial_to_virtual_path(:layout, options[:layout].unescaped)
34
+ end
35
+ end
36
+
37
+ templates
38
+ end
39
+
40
+ private
41
+ # Accept a call node and return a hash of options for the render call.
42
+ # If it doesn't match the expected format, return nil.
43
+ def render_call_options(node)
44
+ # We are only looking for calls to render or render_to_string.
45
+ name = node.name.to_sym
46
+ return if name != :render && name != :render_to_string
47
+
48
+ # We are only looking for calls with arguments.
49
+ arguments = node.arguments
50
+ return unless arguments
51
+
52
+ arguments = arguments.arguments
53
+ length = arguments.length
54
+
55
+ # Get rid of any parentheses to get directly to the contents.
56
+ arguments.map! do |argument|
57
+ current = argument
58
+
59
+ while current.is_a?(Prism::ParenthesesNode) &&
60
+ current.body.is_a?(Prism::StatementsNode) &&
61
+ current.body.body.length == 1
62
+ current = current.body.body.first
63
+ end
64
+
65
+ current
66
+ end
67
+
68
+ # We are only looking for arguments that are either a string with an
69
+ # array of locals or a keyword hash with symbol keys.
70
+ options =
71
+ if (length == 1 || length == 2) && !arguments[0].is_a?(Prism::KeywordHashNode)
72
+ { partial: arguments[0], locals: arguments[1] }
73
+ elsif length == 1 &&
74
+ arguments[0].is_a?(Prism::KeywordHashNode) &&
75
+ arguments[0].elements.all? do |element|
76
+ element.is_a?(Prism::AssocNode) && element.key.is_a?(Prism::SymbolNode)
77
+ end
78
+ arguments[0].elements.to_h do |element|
79
+ [element.key.unescaped.to_sym, element.value]
80
+ end
81
+ end
82
+
83
+ return unless options
84
+
85
+ # Here we validate that the options have the keys we expect.
86
+ keys = options.keys
87
+ return if !keys.intersect?(RENDER_TYPE_KEYS)
88
+ return if (keys - ALL_KNOWN_KEYS).any?
89
+
90
+ # Finally, we can return a valid set of options.
91
+ options
92
+ end
93
+
94
+ # Accept the node that is being passed in the position of the template
95
+ # and return the template name and whether or not it is an object
96
+ # template.
97
+ def render_call_template(node)
98
+ object_template = false
99
+ template =
100
+ if node.is_a?(Prism::StringNode)
101
+ path = node.unescaped
102
+ path.include?("/") ? path : "#{directory}/#{path}"
103
+ else
104
+ dependency =
105
+ case node.type
106
+ when :class_variable_read_node
107
+ node.slice[2..]
108
+ when :instance_variable_read_node
109
+ node.slice[1..]
110
+ when :global_variable_read_node
111
+ node.slice[1..]
112
+ when :local_variable_read_node
113
+ node.slice
114
+ when :call_node
115
+ node.name.to_s
116
+ else
117
+ return
118
+ end
119
+
120
+ "#{dependency.pluralize}/#{dependency.singularize}"
121
+ end
122
+
123
+ [template, object_template]
124
+ end
125
+ end
126
+ end
127
+ end
@@ -1,10 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "ripper"
4
-
5
3
  module ActionView
6
- class RenderParser
7
- module RipperASTParser # :nodoc:
4
+ module RenderParser
5
+ class RipperRenderParser < Base # :nodoc:
8
6
  class Node < ::Array # :nodoc:
9
7
  attr_reader :type
10
8
 
@@ -63,7 +61,7 @@ module ActionView
63
61
  end
64
62
 
65
63
  def call_method_name
66
- self.last.first
64
+ self[2].first
67
65
  end
68
66
 
69
67
  def to_string
@@ -86,11 +84,11 @@ module ActionView
86
84
  end
87
85
 
88
86
  def hash_from_body(body)
89
- body.map do |hash_node|
87
+ body.to_h do |hash_node|
90
88
  return nil if hash_node.type != :assoc_new
91
89
 
92
90
  [hash_node[0], hash_node[1]]
93
- end.to_h
91
+ end
94
92
  end
95
93
 
96
94
  def symbol?
@@ -183,16 +181,161 @@ module ActionView
183
181
  end
184
182
  end
185
183
 
186
- extend self
187
-
188
- def parse_render_nodes(code)
189
- parser = RenderCallExtractor.new(code)
184
+ def render_calls
185
+ parser = RenderCallExtractor.new(@code)
190
186
  parser.parse
191
187
 
192
- parser.render_calls.group_by(&:first).collect do |method, nodes|
188
+ parser.render_calls.group_by(&:first).to_h do |method, nodes|
193
189
  [ method.to_sym, nodes.collect { |v| v[1] } ]
194
- end.to_h
190
+ end.map do |method, nodes|
191
+ nodes.map { |n| parse_render(n) }
192
+ end.flatten.compact
195
193
  end
194
+
195
+ private
196
+ def resolve_path_directory(path)
197
+ if path.include?("/")
198
+ path
199
+ else
200
+ "#{directory}/#{path}"
201
+ end
202
+ end
203
+
204
+ # Convert
205
+ # render("foo", ...)
206
+ # into either
207
+ # render(template: "foo", ...)
208
+ # or
209
+ # render(partial: "foo", ...)
210
+ def normalize_args(string, options_hash)
211
+ if options_hash
212
+ { partial: string, locals: options_hash }
213
+ else
214
+ { partial: string }
215
+ end
216
+ end
217
+
218
+ def parse_render(node)
219
+ node = node.argument_nodes
220
+
221
+ if (node.length == 1 || node.length == 2) && !node[0].hash?
222
+ if node.length == 1
223
+ options = normalize_args(node[0], nil)
224
+ elsif node.length == 2
225
+ options = normalize_args(node[0], node[1])
226
+ end
227
+
228
+ return nil unless options
229
+
230
+ parse_render_from_options(options)
231
+ elsif node.length == 1 && node[0].hash?
232
+ options = parse_hash_to_symbols(node[0])
233
+
234
+ return nil unless options
235
+
236
+ parse_render_from_options(options)
237
+ else
238
+ nil
239
+ end
240
+ end
241
+
242
+ def parse_hash(node)
243
+ node.hash? && node.to_hash
244
+ end
245
+
246
+ def parse_hash_to_symbols(node)
247
+ hash = parse_hash(node)
248
+
249
+ return unless hash
250
+
251
+ hash.transform_keys do |key_node|
252
+ key = parse_sym(key_node)
253
+
254
+ return unless key
255
+
256
+ key
257
+ end
258
+ end
259
+
260
+ def parse_render_from_options(options_hash)
261
+ renders = []
262
+ keys = options_hash.keys
263
+
264
+ if (keys & RENDER_TYPE_KEYS).size < 1
265
+ # Must have at least one of render keys
266
+ return nil
267
+ end
268
+
269
+ if (keys - ALL_KNOWN_KEYS).any?
270
+ # de-opt in case of unknown option
271
+ return nil
272
+ end
273
+
274
+ render_type = (keys & RENDER_TYPE_KEYS)[0]
275
+
276
+ node = options_hash[render_type]
277
+
278
+ if node.string?
279
+ template = resolve_path_directory(node.to_string)
280
+ else
281
+ if node.variable_reference?
282
+ dependency = node.variable_name.sub(/\A(?:\$|@{1,2})/, "")
283
+ elsif node.vcall?
284
+ dependency = node.variable_name
285
+ elsif node.call?
286
+ dependency = node.call_method_name
287
+ else
288
+ return
289
+ end
290
+
291
+ object_template = true
292
+ template = "#{dependency.pluralize}/#{dependency.singularize}"
293
+ end
294
+
295
+ return unless template
296
+
297
+ if spacer_template = render_template_with_spacer?(options_hash)
298
+ virtual_path = partial_to_virtual_path(:partial, spacer_template)
299
+ renders << virtual_path
300
+ end
301
+
302
+ if options_hash.key?(:object) || options_hash.key?(:collection) || object_template
303
+ return nil if options_hash.key?(:object) && options_hash.key?(:collection)
304
+ return nil unless options_hash.key?(:partial)
305
+ end
306
+
307
+ virtual_path = partial_to_virtual_path(render_type, template)
308
+ renders << virtual_path
309
+
310
+ # Support for rendering multiple templates (i.e. a partial with a layout)
311
+ if layout_template = render_template_with_layout?(render_type, options_hash)
312
+ virtual_path = partial_to_virtual_path(:layout, layout_template)
313
+
314
+ renders << virtual_path
315
+ end
316
+
317
+ renders
318
+ end
319
+
320
+ def parse_str(node)
321
+ node.string? && node.to_string
322
+ end
323
+
324
+ def parse_sym(node)
325
+ node.symbol? && node.to_symbol
326
+ end
327
+
328
+ def render_template_with_layout?(render_type, options_hash)
329
+ if render_type != :layout && options_hash.key?(:layout)
330
+ parse_str(options_hash[:layout])
331
+ end
332
+ end
333
+
334
+ def render_template_with_spacer?(options_hash)
335
+ if options_hash.key?(:spacer_template)
336
+ parse_str(options_hash[:spacer_template])
337
+ end
338
+ end
196
339
  end
197
340
  end
198
341
  end
@@ -1,176 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "action_view/ripper_ast_parser"
4
-
5
3
  module ActionView
6
- class RenderParser # :nodoc:
7
- def initialize(name, code)
8
- @name = name
9
- @code = code
10
- @parser = RipperASTParser
11
- end
12
-
13
- def render_calls
14
- render_nodes = @parser.parse_render_nodes(@code)
15
-
16
- render_nodes.map do |method, nodes|
17
- nodes.map { |n| send(:parse_render, n) }
18
- end.flatten.compact
19
- end
4
+ module RenderParser # :nodoc:
5
+ ALL_KNOWN_KEYS = [:partial, :template, :layout, :formats, :locals, :object, :collection, :as, :status, :content_type, :location, :spacer_template]
6
+ RENDER_TYPE_KEYS = [:partial, :template, :layout]
20
7
 
21
- private
22
- def directory
23
- File.dirname(@name)
24
- end
25
-
26
- def resolve_path_directory(path)
27
- if path.include?("/")
28
- path
29
- else
30
- "#{directory}/#{path}"
31
- end
32
- end
33
-
34
- # Convert
35
- # render("foo", ...)
36
- # into either
37
- # render(template: "foo", ...)
38
- # or
39
- # render(partial: "foo", ...)
40
- def normalize_args(string, options_hash)
41
- if options_hash
42
- { partial: string, locals: options_hash }
43
- else
44
- { partial: string }
45
- end
46
- end
47
-
48
- def parse_render(node)
49
- node = node.argument_nodes
50
-
51
- if (node.length == 1 || node.length == 2) && !node[0].hash?
52
- if node.length == 1
53
- options = normalize_args(node[0], nil)
54
- elsif node.length == 2
55
- options = normalize_args(node[0], node[1])
56
- end
57
-
58
- return nil unless options
59
-
60
- parse_render_from_options(options)
61
- elsif node.length == 1 && node[0].hash?
62
- options = parse_hash_to_symbols(node[0])
63
-
64
- return nil unless options
65
-
66
- parse_render_from_options(options)
67
- else
68
- nil
69
- end
70
- end
71
-
72
- def parse_hash(node)
73
- node.hash? && node.to_hash
74
- end
75
-
76
- def parse_hash_to_symbols(node)
77
- hash = parse_hash(node)
78
-
79
- return unless hash
80
-
81
- hash.transform_keys do |key_node|
82
- key = parse_sym(key_node)
83
-
84
- return unless key
85
-
86
- key
87
- end
88
- end
89
-
90
- ALL_KNOWN_KEYS = [:partial, :template, :layout, :formats, :locals, :object, :collection, :as, :status, :content_type, :location, :spacer_template]
91
-
92
- RENDER_TYPE_KEYS =
93
- [:partial, :template, :layout]
94
-
95
- def parse_render_from_options(options_hash)
96
- renders = []
97
- keys = options_hash.keys
98
-
99
- if (keys & RENDER_TYPE_KEYS).size < 1
100
- # Must have at least one of render keys
101
- return nil
102
- end
103
-
104
- if (keys - ALL_KNOWN_KEYS).any?
105
- # de-opt in case of unknown option
106
- return nil
107
- end
108
-
109
- render_type = (keys & RENDER_TYPE_KEYS)[0]
110
-
111
- node = options_hash[render_type]
112
-
113
- if node.string?
114
- template = resolve_path_directory(node.to_string)
115
- else
116
- if node.variable_reference?
117
- dependency = node.variable_name.sub(/\A(?:\$|@{1,2})/, "")
118
- elsif node.vcall?
119
- dependency = node.variable_name
120
- elsif node.call?
121
- dependency = node.call_method_name
122
- else
123
- return
124
- end
125
-
126
- object_template = true
127
- template = "#{dependency.pluralize}/#{dependency.singularize}"
128
- end
129
-
130
- return unless template
131
-
132
- if spacer_template = render_template_with_spacer?(options_hash)
133
- virtual_path = partial_to_virtual_path(:partial, spacer_template)
134
- renders << virtual_path
135
- end
136
-
137
- if options_hash.key?(:object) || options_hash.key?(:collection) || object_template
138
- return nil if options_hash.key?(:object) && options_hash.key?(:collection)
139
- return nil unless options_hash.key?(:partial)
140
- end
141
-
142
- virtual_path = partial_to_virtual_path(render_type, template)
143
- renders << virtual_path
144
-
145
- # Support for rendering multiple templates (i.e. a partial with a layout)
146
- if layout_template = render_template_with_layout?(render_type, options_hash)
147
- virtual_path = partial_to_virtual_path(:layout, layout_template)
148
-
149
- renders << virtual_path
150
- end
151
-
152
- renders
153
- end
154
-
155
- def parse_str(node)
156
- node.string? && node.to_string
157
- end
158
-
159
- def parse_sym(node)
160
- node.symbol? && node.to_symbol
8
+ class Base # :nodoc:
9
+ def initialize(name, code)
10
+ @name = name
11
+ @code = code
161
12
  end
162
13
 
163
14
  private
164
- def render_template_with_layout?(render_type, options_hash)
165
- if render_type != :layout && options_hash.key?(:layout)
166
- parse_str(options_hash[:layout])
167
- end
168
- end
169
-
170
- def render_template_with_spacer?(options_hash)
171
- if options_hash.key?(:spacer_template)
172
- parse_str(options_hash[:spacer_template])
173
- end
15
+ def directory
16
+ File.dirname(@name)
174
17
  end
175
18
 
176
19
  def partial_to_virtual_path(render_type, partial_path)
@@ -180,9 +23,18 @@ module ActionView
180
23
  partial_path
181
24
  end
182
25
  end
26
+ end
183
27
 
184
- def layout_to_virtual_path(layout_path)
185
- "layouts/#{layout_path}"
186
- end
28
+ # Check if prism is available. If it is, use it. Otherwise, use ripper.
29
+ begin
30
+ require "prism"
31
+ rescue LoadError
32
+ require "ripper"
33
+ require_relative "render_parser/ripper_render_parser"
34
+ Default = RipperRenderParser
35
+ else
36
+ require_relative "render_parser/prism_render_parser"
37
+ Default = PrismRenderParser
38
+ end
187
39
  end
188
40
  end
@@ -31,7 +31,7 @@ module ActionView
31
31
 
32
32
  module ObjectRendering # :nodoc:
33
33
  PREFIXED_PARTIAL_NAMES = Concurrent::Map.new do |h, k|
34
- h[k] = Concurrent::Map.new
34
+ h.compute_if_absent(k) { Concurrent::Map.new }
35
35
  end
36
36
 
37
37
  def initialize(lookup_context, options)
@@ -79,7 +79,7 @@ module ActionView
79
79
  path = if object.respond_to?(:to_partial_path)
80
80
  object.to_partial_path
81
81
  else
82
- raise ArgumentError.new("'#{object.inspect}' is not an ActiveModel-compatible object. It must implement :to_partial_path.")
82
+ raise ArgumentError.new("'#{object.inspect}' is not an ActiveModel-compatible object. It must implement #to_partial_path.")
83
83
  end
84
84
 
85
85
  if view.prefix_partial_path_with_controller_namespace