actionview 7.1.5.1 → 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.
@@ -18,7 +18,7 @@ module ActionView
18
18
  info do
19
19
  message = +" Rendered #{from_rails_root(event.payload[:identifier])}"
20
20
  message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout]
21
- message << " (Duration: #{event.duration.round(1)}ms | Allocations: #{event.allocations})"
21
+ message << " (Duration: #{event.duration.round(1)}ms | GC: #{event.gc_time.round(1)}ms)"
22
22
  end
23
23
  end
24
24
  subscribe_log_level :render_template, :debug
@@ -27,7 +27,7 @@ module ActionView
27
27
  debug do
28
28
  message = +" Rendered #{from_rails_root(event.payload[:identifier])}"
29
29
  message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout]
30
- message << " (Duration: #{event.duration.round(1)}ms | Allocations: #{event.allocations})"
30
+ message << " (Duration: #{event.duration.round(1)}ms | GC: #{event.gc_time.round(1)}ms)"
31
31
  message << " #{cache_message(event.payload)}" unless event.payload[:cache_hit].nil?
32
32
  message
33
33
  end
@@ -37,7 +37,7 @@ module ActionView
37
37
  def render_layout(event)
38
38
  info do
39
39
  message = +" Rendered layout #{from_rails_root(event.payload[:identifier])}"
40
- message << " (Duration: #{event.duration.round(1)}ms | Allocations: #{event.allocations})"
40
+ message << " (Duration: #{event.duration.round(1)}ms | GC: #{event.gc_time.round(1)}ms)"
41
41
  end
42
42
  end
43
43
  subscribe_log_level :render_layout, :info
@@ -48,7 +48,7 @@ module ActionView
48
48
  debug do
49
49
  message = +" Rendered collection of #{from_rails_root(identifier)}"
50
50
  message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout]
51
- message << " #{render_count(event.payload)} (Duration: #{event.duration.round(1)}ms | Allocations: #{event.allocations})"
51
+ message << " #{render_count(event.payload)} (Duration: #{event.duration.round(1)}ms | GC: #{event.gc_time.round(1)}ms)"
52
52
  message
53
53
  end
54
54
  end
@@ -96,6 +96,10 @@ module ActionView
96
96
 
97
97
  def finish(name, id, payload)
98
98
  end
99
+
100
+ def silenced?(_)
101
+ logger.nil? || !logger.debug?
102
+ end
99
103
  end
100
104
 
101
105
  def self.attach_to(*)
@@ -116,7 +116,6 @@ module ActionView
116
116
  view_reloader = ActionView::CacheExpiry::ViewReloader.new(watcher: app.config.file_watcher)
117
117
 
118
118
  app.reloaders << view_reloader
119
- view_reloader.execute
120
119
  app.reloader.to_run do
121
120
  require_unload_lock!
122
121
  view_reloader.execute
@@ -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
 
@@ -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
188
  parser.render_calls.group_by(&:first).to_h do |method, nodes|
193
189
  [ method.to_sym, nodes.collect { |v| v[1] } ]
194
- end
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
@@ -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
@@ -45,12 +45,6 @@ module ActionView
45
45
  end
46
46
  end
47
47
 
48
- # Direct access to template rendering.
49
- def render_template(context, options) # :nodoc:
50
- render_template_to_object(context, options).body
51
- end
52
-
53
- # Direct access to partial rendering.
54
48
  def render_partial(context, options, &block) # :nodoc:
55
49
  render_partial_to_object(context, options, &block).body
56
50
  end
@@ -59,46 +53,46 @@ module ActionView
59
53
  @cache_hits ||= {}
60
54
  end
61
55
 
62
- def render_template_to_object(context, options) # :nodoc:
63
- TemplateRenderer.new(@lookup_context).render(context, options)
64
- end
56
+ private
57
+ def render_template_to_object(context, options)
58
+ TemplateRenderer.new(@lookup_context).render(context, options)
59
+ end
65
60
 
66
- def render_partial_to_object(context, options, &block) # :nodoc:
67
- partial = options[:partial]
68
- if String === partial
69
- collection = collection_from_options(options)
61
+ def render_partial_to_object(context, options, &block)
62
+ partial = options[:partial]
63
+ if String === partial
64
+ collection = collection_from_options(options)
70
65
 
71
- if collection
72
- # Collection + Partial
73
- renderer = CollectionRenderer.new(@lookup_context, options)
74
- renderer.render_collection_with_partial(collection, partial, context, block)
75
- else
76
- if options.key?(:object)
77
- # Object + Partial
78
- renderer = ObjectRenderer.new(@lookup_context, options)
79
- renderer.render_object_with_partial(options[:object], partial, context, block)
66
+ if collection
67
+ # Collection + Partial
68
+ renderer = CollectionRenderer.new(@lookup_context, options)
69
+ renderer.render_collection_with_partial(collection, partial, context, block)
80
70
  else
81
- # Partial
82
- renderer = PartialRenderer.new(@lookup_context, options)
83
- renderer.render(partial, context, block)
71
+ if options.key?(:object)
72
+ # Object + Partial
73
+ renderer = ObjectRenderer.new(@lookup_context, options)
74
+ renderer.render_object_with_partial(options[:object], partial, context, block)
75
+ else
76
+ # Partial
77
+ renderer = PartialRenderer.new(@lookup_context, options)
78
+ renderer.render(partial, context, block)
79
+ end
84
80
  end
85
- end
86
- else
87
- collection = collection_from_object(partial) || collection_from_options(options)
88
-
89
- if collection
90
- # Collection + Derived Partial
91
- renderer = CollectionRenderer.new(@lookup_context, options)
92
- renderer.render_collection_derive_partial(collection, context, block)
93
81
  else
94
- # Object + Derived Partial
95
- renderer = ObjectRenderer.new(@lookup_context, options)
96
- renderer.render_object_derive_partial(partial, context, block)
82
+ collection = collection_from_object(partial) || collection_from_options(options)
83
+
84
+ if collection
85
+ # Collection + Derived Partial
86
+ renderer = CollectionRenderer.new(@lookup_context, options)
87
+ renderer.render_collection_derive_partial(collection, context, block)
88
+ else
89
+ # Object + Derived Partial
90
+ renderer = ObjectRenderer.new(@lookup_context, options)
91
+ renderer.render_object_derive_partial(partial, context, block)
92
+ end
97
93
  end
98
94
  end
99
- end
100
95
 
101
- private
102
96
  def collection_from_options(options)
103
97
  if options.key?(:collection)
104
98
  collection = options[:collection]