actionview 6.1.7.2 → 7.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +299 -277
  3. data/MIT-LICENSE +2 -1
  4. data/README.rdoc +3 -3
  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 +37 -19
  8. data/lib/action_view/buffers.rb +107 -9
  9. data/lib/action_view/cache_expiry.rb +48 -37
  10. data/lib/action_view/context.rb +1 -1
  11. data/lib/action_view/dependency_tracker/erb_tracker.rb +154 -0
  12. data/lib/action_view/dependency_tracker/ripper_tracker.rb +59 -0
  13. data/lib/action_view/dependency_tracker.rb +6 -147
  14. data/lib/action_view/deprecator.rb +7 -0
  15. data/lib/action_view/digestor.rb +8 -5
  16. data/lib/action_view/flows.rb +4 -4
  17. data/lib/action_view/gem_version.rb +4 -4
  18. data/lib/action_view/helpers/active_model_helper.rb +3 -3
  19. data/lib/action_view/helpers/asset_tag_helper.rb +200 -60
  20. data/lib/action_view/helpers/asset_url_helper.rb +22 -21
  21. data/lib/action_view/helpers/atom_feed_helper.rb +8 -9
  22. data/lib/action_view/helpers/cache_helper.rb +55 -12
  23. data/lib/action_view/helpers/capture_helper.rb +34 -14
  24. data/lib/action_view/helpers/content_exfiltration_prevention_helper.rb +70 -0
  25. data/lib/action_view/helpers/controller_helper.rb +8 -2
  26. data/lib/action_view/helpers/csp_helper.rb +3 -3
  27. data/lib/action_view/helpers/csrf_helper.rb +4 -4
  28. data/lib/action_view/helpers/date_helper.rb +123 -57
  29. data/lib/action_view/helpers/debug_helper.rb +6 -4
  30. data/lib/action_view/helpers/form_helper.rb +253 -97
  31. data/lib/action_view/helpers/form_options_helper.rb +72 -34
  32. data/lib/action_view/helpers/form_tag_helper.rb +189 -58
  33. data/lib/action_view/helpers/javascript_helper.rb +4 -5
  34. data/lib/action_view/helpers/number_helper.rb +43 -335
  35. data/lib/action_view/helpers/output_safety_helper.rb +6 -6
  36. data/lib/action_view/helpers/rendering_helper.rb +6 -7
  37. data/lib/action_view/helpers/sanitize_helper.rb +54 -24
  38. data/lib/action_view/helpers/tag_helper.rb +42 -35
  39. data/lib/action_view/helpers/tags/base.rb +16 -77
  40. data/lib/action_view/helpers/tags/check_box.rb +1 -1
  41. data/lib/action_view/helpers/tags/collection_check_boxes.rb +1 -0
  42. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +1 -0
  43. data/lib/action_view/helpers/tags/collection_select.rb +4 -1
  44. data/lib/action_view/helpers/tags/date_field.rb +1 -1
  45. data/lib/action_view/helpers/tags/date_select.rb +2 -0
  46. data/lib/action_view/helpers/tags/datetime_field.rb +14 -6
  47. data/lib/action_view/helpers/tags/datetime_local_field.rb +11 -2
  48. data/lib/action_view/helpers/tags/file_field.rb +16 -0
  49. data/lib/action_view/helpers/tags/grouped_collection_select.rb +3 -0
  50. data/lib/action_view/helpers/tags/month_field.rb +1 -1
  51. data/lib/action_view/helpers/tags/select.rb +4 -1
  52. data/lib/action_view/helpers/tags/select_renderer.rb +56 -0
  53. data/lib/action_view/helpers/tags/time_field.rb +11 -2
  54. data/lib/action_view/helpers/tags/time_zone_select.rb +3 -0
  55. data/lib/action_view/helpers/tags/week_field.rb +1 -1
  56. data/lib/action_view/helpers/tags/weekday_select.rb +31 -0
  57. data/lib/action_view/helpers/tags.rb +5 -2
  58. data/lib/action_view/helpers/text_helper.rb +180 -97
  59. data/lib/action_view/helpers/translation_helper.rb +14 -45
  60. data/lib/action_view/helpers/url_helper.rb +230 -132
  61. data/lib/action_view/helpers.rb +27 -25
  62. data/lib/action_view/layouts.rb +15 -10
  63. data/lib/action_view/log_subscriber.rb +49 -32
  64. data/lib/action_view/lookup_context.rb +58 -61
  65. data/lib/action_view/model_naming.rb +2 -2
  66. data/lib/action_view/path_registry.rb +57 -0
  67. data/lib/action_view/path_set.rb +28 -35
  68. data/lib/action_view/railtie.rb +44 -9
  69. data/lib/action_view/record_identifier.rb +16 -9
  70. data/lib/action_view/render_parser.rb +188 -0
  71. data/lib/action_view/renderer/abstract_renderer.rb +3 -3
  72. data/lib/action_view/renderer/collection_renderer.rb +10 -2
  73. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +21 -3
  74. data/lib/action_view/renderer/partial_renderer.rb +3 -36
  75. data/lib/action_view/renderer/renderer.rb +6 -4
  76. data/lib/action_view/renderer/streaming_template_renderer.rb +6 -5
  77. data/lib/action_view/renderer/template_renderer.rb +9 -4
  78. data/lib/action_view/rendering.rb +25 -7
  79. data/lib/action_view/ripper_ast_parser.rb +198 -0
  80. data/lib/action_view/routing_url_for.rb +8 -5
  81. data/lib/action_view/template/error.rb +122 -14
  82. data/lib/action_view/template/handlers/builder.rb +4 -4
  83. data/lib/action_view/template/handlers/erb/erubi.rb +23 -27
  84. data/lib/action_view/template/handlers/erb.rb +79 -1
  85. data/lib/action_view/template/handlers.rb +4 -4
  86. data/lib/action_view/template/html.rb +4 -4
  87. data/lib/action_view/template/inline.rb +3 -3
  88. data/lib/action_view/template/raw_file.rb +4 -4
  89. data/lib/action_view/template/renderable.rb +1 -1
  90. data/lib/action_view/template/resolver.rb +96 -313
  91. data/lib/action_view/template/text.rb +4 -4
  92. data/lib/action_view/template/types.rb +25 -32
  93. data/lib/action_view/template.rb +245 -41
  94. data/lib/action_view/template_details.rb +66 -0
  95. data/lib/action_view/template_path.rb +66 -0
  96. data/lib/action_view/test_case.rb +182 -23
  97. data/lib/action_view/testing/resolvers.rb +11 -12
  98. data/lib/action_view/unbound_template.rb +43 -7
  99. data/lib/action_view/version.rb +1 -1
  100. data/lib/action_view/view_paths.rb +19 -28
  101. data/lib/action_view.rb +6 -4
  102. data/lib/assets/compiled/rails-ujs.js +36 -5
  103. metadata +32 -25
@@ -9,8 +9,8 @@ require "action_view/context"
9
9
  require "action_view/template"
10
10
  require "action_view/lookup_context"
11
11
 
12
- module ActionView #:nodoc:
13
- # = Action View Base
12
+ module ActionView # :nodoc:
13
+ # = Action View \Base
14
14
  #
15
15
  # Action View templates can be written in several ways.
16
16
  # If the template file has a <tt>.erb</tt> extension, then it uses the erubi[https://rubygems.org/gems/erubi]
@@ -44,9 +44,9 @@ module ActionView #:nodoc:
44
44
  # Using sub templates allows you to sidestep tedious replication and extract common display structures in shared templates. The
45
45
  # classic example is the use of a header and footer (even though the Action Pack-way would be to use Layouts):
46
46
  #
47
- # <%= render "shared/header" %>
47
+ # <%= render "application/header" %>
48
48
  # Something really specific and terrific
49
- # <%= render "shared/footer" %>
49
+ # <%= render "application/footer" %>
50
50
  #
51
51
  # As you see, we use the output embeddings for the render methods. The render call itself will just return a string holding the
52
52
  # result of the rendering. The output embedding writes it to the current template.
@@ -55,7 +55,7 @@ module ActionView #:nodoc:
55
55
  # variables defined using the regular embedding tags. Like this:
56
56
  #
57
57
  # <% @page_title = "A Wonderful Hello" %>
58
- # <%= render "shared/header" %>
58
+ # <%= render "application/header" %>
59
59
  #
60
60
  # Now the header can pick up on the <tt>@page_title</tt> variable and use it for outputting a title tag:
61
61
  #
@@ -65,9 +65,9 @@ module ActionView #:nodoc:
65
65
  #
66
66
  # You can pass local variables to sub templates by using a hash with the variable names as keys and the objects as values:
67
67
  #
68
- # <%= render "shared/header", { headline: "Welcome", person: person } %>
68
+ # <%= render "application/header", { headline: "Welcome", person: person } %>
69
69
  #
70
- # These can now be accessed in <tt>shared/header</tt> with:
70
+ # These can now be accessed in <tt>application/header</tt> with:
71
71
  #
72
72
  # Headline: <%= headline %>
73
73
  # First name: <%= person.first_name %>
@@ -82,8 +82,8 @@ module ActionView #:nodoc:
82
82
  #
83
83
  # === Template caching
84
84
  #
85
- # By default, Rails will compile each template to a method in order to render it. When you alter a template,
86
- # Rails will check the file's modification time and recompile it in development mode.
85
+ # By default, \Rails will compile each template to a method in order to render it. When you alter a template,
86
+ # \Rails will check the file's modification time and recompile it in development mode.
87
87
  #
88
88
  # == Builder
89
89
  #
@@ -142,7 +142,7 @@ module ActionView #:nodoc:
142
142
  include Helpers, ::ERB::Util, Context
143
143
 
144
144
  # Specify the proc used to decorate input tags that refer to attributes with errors.
145
- cattr_accessor :field_error_proc, default: Proc.new { |html_tag, instance| "<div class=\"field_with_errors\">#{html_tag}</div>".html_safe }
145
+ cattr_accessor :field_error_proc, default: Proc.new { |html_tag, instance| content_tag :div, html_tag, class: "field_with_errors" }
146
146
 
147
147
  # How to complete the streaming when an exception occurs.
148
148
  # This is our best guess: first try to close the attribute, then the tag.
@@ -156,9 +156,6 @@ module ActionView #:nodoc:
156
156
  # Specify default_formats that can be rendered.
157
157
  cattr_accessor :default_formats
158
158
 
159
- # Specify whether an error should be raised for missing translations
160
- cattr_accessor :raise_on_missing_translations, default: false
161
-
162
159
  # Specify whether submit_tag should automatically disable on click
163
160
  cattr_accessor :automatically_disable_submit_tag, default: true
164
161
 
@@ -179,7 +176,7 @@ module ActionView #:nodoc:
179
176
  ActionView::Resolver.caching = value
180
177
  end
181
178
 
182
- def xss_safe? #:nodoc:
179
+ def xss_safe? # :nodoc:
183
180
  true
184
181
  end
185
182
 
@@ -208,7 +205,8 @@ module ActionView #:nodoc:
208
205
  delegate :formats, :formats=, :locale, :locale=, :view_paths, :view_paths=, to: :lookup_context
209
206
 
210
207
  def assign(new_assigns) # :nodoc:
211
- @_assigns = new_assigns.each { |key, value| instance_variable_set("@#{key}", value) }
208
+ @_assigns = new_assigns
209
+ new_assigns.each { |key, value| instance_variable_set("@#{key}", value) }
212
210
  end
213
211
 
214
212
  # :stopdoc:
@@ -227,7 +225,7 @@ module ActionView #:nodoc:
227
225
 
228
226
  # :startdoc:
229
227
 
230
- def initialize(lookup_context, assigns, controller) #:nodoc:
228
+ def initialize(lookup_context, assigns, controller) # :nodoc:
231
229
  @_config = ActiveSupport::InheritableOptions.new
232
230
 
233
231
  @lookup_context = lookup_context
@@ -235,16 +233,36 @@ module ActionView #:nodoc:
235
233
  @view_renderer = ActionView::Renderer.new @lookup_context
236
234
  @current_template = nil
237
235
 
238
- assign(assigns)
239
236
  assign_controller(controller)
240
237
  _prepare_context
238
+
239
+ super()
240
+
241
+ # Assigns must be called last to minimize the number of shapes
242
+ assign(assigns)
241
243
  end
242
244
 
243
- def _run(method, template, locals, buffer, add_to_stack: true, &block)
245
+ def _run(method, template, locals, buffer, add_to_stack: true, has_strict_locals: false, &block)
244
246
  _old_output_buffer, _old_virtual_path, _old_template = @output_buffer, @virtual_path, @current_template
245
247
  @current_template = template if add_to_stack
246
248
  @output_buffer = buffer
247
- public_send(method, locals, buffer, &block)
249
+
250
+ if has_strict_locals
251
+ begin
252
+ public_send(method, buffer, **locals, &block)
253
+ rescue ArgumentError => argument_error
254
+ raise(
255
+ ArgumentError,
256
+ argument_error.
257
+ message.
258
+ gsub("unknown keyword:", "unknown local:").
259
+ gsub("missing keyword:", "missing local:").
260
+ gsub("no keywords accepted", "no locals accepted")
261
+ )
262
+ end
263
+ else
264
+ public_send(method, locals, buffer, &block)
265
+ end
248
266
  ensure
249
267
  @output_buffer, @virtual_path, @current_template = _old_output_buffer, _old_virtual_path, _old_template
250
268
  end
@@ -18,27 +18,94 @@ module ActionView
18
18
  # sbuf << 5
19
19
  # puts sbuf # => "hello\u0005"
20
20
  #
21
- class OutputBuffer < ActiveSupport::SafeBuffer #:nodoc:
22
- def initialize(*)
23
- super
24
- encode!
21
+ class OutputBuffer # :nodoc:
22
+ def initialize(buffer = "")
23
+ @raw_buffer = String.new(buffer)
24
+ @raw_buffer.encode!
25
+ end
26
+
27
+ delegate :length, :empty?, :blank?, :encoding, :encode!, :force_encoding, to: :@raw_buffer
28
+
29
+ def to_s
30
+ @raw_buffer.html_safe
31
+ end
32
+ alias_method :html_safe, :to_s
33
+
34
+ def to_str
35
+ @raw_buffer.dup
36
+ end
37
+
38
+ def html_safe?
39
+ true
25
40
  end
26
41
 
27
42
  def <<(value)
28
- return self if value.nil?
29
- super(value.to_s)
43
+ unless value.nil?
44
+ value = value.to_s
45
+ @raw_buffer << if value.html_safe?
46
+ value
47
+ else
48
+ CGI.escapeHTML(value)
49
+ end
50
+ end
51
+ self
30
52
  end
53
+ alias :concat :<<
31
54
  alias :append= :<<
32
55
 
56
+ def safe_concat(value)
57
+ @raw_buffer << value
58
+ self
59
+ end
60
+ alias :safe_append= :safe_concat
61
+
33
62
  def safe_expr_append=(val)
34
63
  return self if val.nil?
35
- safe_concat val.to_s
64
+ @raw_buffer << val.to_s
65
+ self
36
66
  end
37
67
 
38
- alias :safe_append= :safe_concat
68
+ def initialize_copy(other)
69
+ @raw_buffer = other.to_str
70
+ end
71
+
72
+ def capture(*args)
73
+ new_buffer = +""
74
+ old_buffer, @raw_buffer = @raw_buffer, new_buffer
75
+ yield(*args)
76
+ new_buffer.html_safe
77
+ ensure
78
+ @raw_buffer = old_buffer
79
+ end
80
+
81
+ def ==(other)
82
+ other.class == self.class && @raw_buffer == other.to_str
83
+ end
84
+
85
+ def raw
86
+ RawOutputBuffer.new(self)
87
+ end
88
+
89
+ attr_reader :raw_buffer
90
+ end
91
+
92
+ class RawOutputBuffer # :nodoc:
93
+ def initialize(buffer)
94
+ @buffer = buffer
95
+ end
96
+
97
+ def <<(value)
98
+ unless value.nil?
99
+ @buffer.raw_buffer << value.to_s
100
+ end
101
+ end
102
+
103
+ def raw
104
+ self
105
+ end
39
106
  end
40
107
 
41
- class StreamingBuffer #:nodoc:
108
+ class StreamingBuffer # :nodoc:
42
109
  def initialize(block)
43
110
  @block = block
44
111
  end
@@ -56,6 +123,15 @@ module ActionView
56
123
  end
57
124
  alias :safe_append= :safe_concat
58
125
 
126
+ def capture
127
+ buffer = +""
128
+ old_block, @block = @block, ->(value) { buffer << value }
129
+ yield
130
+ buffer.html_safe
131
+ ensure
132
+ @block = old_block
133
+ end
134
+
59
135
  def html_safe?
60
136
  true
61
137
  end
@@ -63,5 +139,27 @@ module ActionView
63
139
  def html_safe
64
140
  self
65
141
  end
142
+
143
+ def raw
144
+ RawStreamingBuffer.new(self)
145
+ end
146
+
147
+ attr_reader :block
148
+ end
149
+
150
+ class RawStreamingBuffer # :nodoc:
151
+ def initialize(buffer)
152
+ @buffer = buffer
153
+ end
154
+
155
+ def <<(value)
156
+ unless value.nil?
157
+ @buffer.block.call(value.to_s)
158
+ end
159
+ end
160
+
161
+ def raw
162
+ self
163
+ end
66
164
  end
67
165
  end
@@ -1,52 +1,63 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionView
4
- class CacheExpiry
5
- class Executor
6
- def initialize(watcher:)
7
- @cache_expiry = CacheExpiry.new(watcher: watcher)
4
+ module CacheExpiry # :nodoc: all
5
+ class ViewReloader
6
+ def initialize(watcher:, &block)
7
+ @mutex = Mutex.new
8
+ @watcher_class = watcher
9
+ @watched_dirs = nil
10
+ @watcher = nil
11
+ @previous_change = false
12
+
13
+ rebuild_watcher
14
+
15
+ ActionView::PathRegistry.file_system_resolver_hooks << method(:rebuild_watcher)
8
16
  end
9
17
 
10
- def before(target)
11
- @cache_expiry.clear_cache_if_necessary
18
+ def updated?
19
+ @previous_change || @watcher.updated?
12
20
  end
13
- end
14
21
 
15
- def initialize(watcher:)
16
- @watched_dirs = nil
17
- @watcher_class = watcher
18
- @watcher = nil
19
- @mutex = Mutex.new
20
- end
22
+ def execute
23
+ watcher = nil
24
+ @mutex.synchronize do
25
+ @previous_change = false
26
+ watcher = @watcher
27
+ end
28
+ watcher.execute
29
+ end
21
30
 
22
- def clear_cache_if_necessary
23
- @mutex.synchronize do
24
- watched_dirs = dirs_to_watch
25
- return if watched_dirs.empty?
31
+ private
32
+ def reload!
33
+ ActionView::LookupContext::DetailsKey.clear
34
+ end
26
35
 
27
- if watched_dirs != @watched_dirs
28
- @watched_dirs = watched_dirs
29
- @watcher = @watcher_class.new([], watched_dirs) do
30
- clear_cache
36
+ def rebuild_watcher
37
+ @mutex.synchronize do
38
+ old_watcher = @watcher
39
+
40
+ if @watched_dirs != dirs_to_watch
41
+ @watched_dirs = dirs_to_watch
42
+ new_watcher = @watcher_class.new([], @watched_dirs) do
43
+ reload!
44
+ end
45
+ @watcher = new_watcher
46
+
47
+ # We must check the old watcher after initializing the new one to
48
+ # ensure we don't miss any events
49
+ @previous_change ||= old_watcher&.updated?
50
+ end
31
51
  end
32
- @watcher.execute
33
- else
34
- @watcher.execute_if_updated
35
52
  end
36
- end
37
- end
38
53
 
39
- def clear_cache
40
- ActionView::LookupContext::DetailsKey.clear
41
- end
42
-
43
- private
44
- def dirs_to_watch
45
- all_view_paths.grep(FileSystemResolver).map!(&:path).tap(&:uniq!).sort!
46
- end
54
+ def dirs_to_watch
55
+ all_view_paths.uniq.sort
56
+ end
47
57
 
48
- def all_view_paths
49
- ActionView::ViewPaths.all_view_paths.flat_map(&:paths)
50
- end
58
+ def all_view_paths
59
+ ActionView::PathRegistry.all_file_system_resolvers.map(&:path)
60
+ end
61
+ end
51
62
  end
52
63
  end
@@ -17,7 +17,7 @@ module ActionView
17
17
  # Prepares the context by setting the appropriate instance variables.
18
18
  def _prepare_context
19
19
  @view_flow = OutputFlow.new
20
- @output_buffer = nil
20
+ @output_buffer = ActionView::OutputBuffer.new
21
21
  @virtual_path = nil
22
22
  end
23
23
 
@@ -0,0 +1,154 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ class DependencyTracker # :nodoc:
5
+ class ERBTracker # :nodoc:
6
+ EXPLICIT_DEPENDENCY = /# Template Dependency: (\S+)/
7
+
8
+ # A valid ruby identifier - suitable for class, method and specially variable names
9
+ IDENTIFIER = /
10
+ [[:alpha:]_] # at least one uppercase letter, lowercase letter or underscore
11
+ [[:word:]]* # followed by optional letters, numbers or underscores
12
+ /x
13
+
14
+ # Any kind of variable name. e.g. @instance, @@class, $global or local.
15
+ # Possibly following a method call chain
16
+ VARIABLE_OR_METHOD_CHAIN = /
17
+ (?:\$|@{1,2})? # optional global, instance or class variable indicator
18
+ (?:#{IDENTIFIER}\.)* # followed by an optional chain of zero-argument method calls
19
+ (?<dynamic>#{IDENTIFIER}) # and a final valid identifier, captured as DYNAMIC
20
+ /x
21
+
22
+ # A simple string literal. e.g. "School's out!"
23
+ STRING = /
24
+ (?<quote>['"]) # an opening quote
25
+ (?<static>.*?) # with anything inside, captured as STATIC
26
+ \k<quote> # and a matching closing quote
27
+ /x
28
+
29
+ # Part of any hash containing the :partial key
30
+ PARTIAL_HASH_KEY = /
31
+ (?:\bpartial:|:partial\s*=>) # partial key in either old or new style hash syntax
32
+ \s* # followed by optional spaces
33
+ /x
34
+
35
+ # Part of any hash containing the :layout key
36
+ LAYOUT_HASH_KEY = /
37
+ (?:\blayout:|:layout\s*=>) # layout key in either old or new style hash syntax
38
+ \s* # followed by optional spaces
39
+ /x
40
+
41
+ # Matches:
42
+ # partial: "comments/comment", collection: @all_comments => "comments/comment"
43
+ # (object: @single_comment, partial: "comments/comment") => "comments/comment"
44
+ #
45
+ # "comments/comments"
46
+ # 'comments/comments'
47
+ # ('comments/comments')
48
+ #
49
+ # (@topic) => "topics/topic"
50
+ # topics => "topics/topic"
51
+ # (message.topics) => "topics/topic"
52
+ RENDER_ARGUMENTS = /\A
53
+ (?:\s*\(?\s*) # optional opening paren surrounded by spaces
54
+ (?:.*?#{PARTIAL_HASH_KEY}|#{LAYOUT_HASH_KEY})? # optional hash, up to the partial or layout key declaration
55
+ (?:#{STRING}|#{VARIABLE_OR_METHOD_CHAIN}) # finally, the dependency name of interest
56
+ /xm
57
+
58
+ LAYOUT_DEPENDENCY = /\A
59
+ (?:\s*\(?\s*) # optional opening paren surrounded by spaces
60
+ (?:.*?#{LAYOUT_HASH_KEY}) # check if the line has layout key declaration
61
+ (?:#{STRING}|#{VARIABLE_OR_METHOD_CHAIN}) # finally, the dependency name of interest
62
+ /xm
63
+
64
+ def self.supports_view_paths? # :nodoc:
65
+ true
66
+ end
67
+
68
+ def self.call(name, template, view_paths = nil)
69
+ new(name, template, view_paths).dependencies
70
+ end
71
+
72
+ def initialize(name, template, view_paths = nil)
73
+ @name, @template, @view_paths = name, template, view_paths
74
+ end
75
+
76
+ def dependencies
77
+ render_dependencies + explicit_dependencies
78
+ end
79
+
80
+ attr_reader :name, :template
81
+ private :name, :template
82
+
83
+ private
84
+ def source
85
+ template.source
86
+ end
87
+
88
+ def directory
89
+ name.split("/")[0..-2].join("/")
90
+ end
91
+
92
+ def render_dependencies
93
+ render_dependencies = []
94
+ render_calls = source.split(/\brender\b/).drop(1)
95
+
96
+ render_calls.each do |arguments|
97
+ add_dependencies(render_dependencies, arguments, LAYOUT_DEPENDENCY)
98
+ add_dependencies(render_dependencies, arguments, RENDER_ARGUMENTS)
99
+ end
100
+
101
+ render_dependencies.uniq
102
+ end
103
+
104
+ def add_dependencies(render_dependencies, arguments, pattern)
105
+ arguments.scan(pattern) do
106
+ match = Regexp.last_match
107
+ add_dynamic_dependency(render_dependencies, match[:dynamic])
108
+ add_static_dependency(render_dependencies, match[:static], match[:quote])
109
+ end
110
+ end
111
+
112
+ def add_dynamic_dependency(dependencies, dependency)
113
+ if dependency
114
+ dependencies << "#{dependency.pluralize}/#{dependency.singularize}"
115
+ end
116
+ end
117
+
118
+ def add_static_dependency(dependencies, dependency, quote_type)
119
+ if quote_type == '"'
120
+ # Ignore if there is interpolation
121
+ return if dependency.include?('#{')
122
+ end
123
+
124
+ if dependency
125
+ if dependency.include?("/")
126
+ dependencies << dependency
127
+ else
128
+ dependencies << "#{directory}/#{dependency}"
129
+ end
130
+ end
131
+ end
132
+
133
+ def resolve_directories(wildcard_dependencies)
134
+ return [] unless @view_paths
135
+ return [] if wildcard_dependencies.empty?
136
+
137
+ # Remove trailing "/*"
138
+ prefixes = wildcard_dependencies.map { |query| query[0..-3] }
139
+
140
+ @view_paths.flat_map(&:all_template_paths).uniq.filter_map { |path|
141
+ path.to_s if prefixes.include?(path.prefix)
142
+ }.sort
143
+ end
144
+
145
+ def explicit_dependencies
146
+ dependencies = source.scan(EXPLICIT_DEPENDENCY).flatten.uniq
147
+
148
+ wildcards, explicits = dependencies.partition { |dependency| dependency.end_with?("/*") }
149
+
150
+ (explicits + resolve_directories(wildcards)).uniq
151
+ end
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ class DependencyTracker # :nodoc:
5
+ class RipperTracker # :nodoc:
6
+ EXPLICIT_DEPENDENCY = /# Template Dependency: (\S+)/
7
+
8
+ def self.call(name, template, view_paths = nil)
9
+ new(name, template, view_paths).dependencies
10
+ end
11
+
12
+ def dependencies
13
+ render_dependencies + explicit_dependencies
14
+ end
15
+
16
+ def self.supports_view_paths? # :nodoc:
17
+ true
18
+ end
19
+
20
+ def initialize(name, template, view_paths = nil)
21
+ @name, @template, @view_paths = name, template, view_paths
22
+ end
23
+
24
+ private
25
+ attr_reader :template, :name, :view_paths
26
+
27
+ def render_dependencies
28
+ return [] unless template.source.include?("render")
29
+
30
+ compiled_source = template.handler.call(template, template.source)
31
+
32
+ RenderParser.new(@name, compiled_source).render_calls.filter_map do |render_call|
33
+ next if render_call.end_with?("/_")
34
+ render_call.gsub(%r|/_|, "/")
35
+ end
36
+ end
37
+
38
+ def explicit_dependencies
39
+ dependencies = template.source.scan(EXPLICIT_DEPENDENCY).flatten.uniq
40
+
41
+ wildcards, explicits = dependencies.partition { |dependency| dependency.end_with?("/*") }
42
+
43
+ (explicits + resolve_directories(wildcards)).uniq
44
+ end
45
+
46
+ def resolve_directories(wildcard_dependencies)
47
+ return [] unless view_paths
48
+ return [] if wildcard_dependencies.empty?
49
+
50
+ # Remove trailing "/*"
51
+ prefixes = wildcard_dependencies.map { |query| query[0..-3] }
52
+
53
+ view_paths.flat_map(&:all_template_paths).uniq.filter_map { |path|
54
+ path.to_s if prefixes.include?(path.prefix)
55
+ }.sort
56
+ end
57
+ end
58
+ end
59
+ end