actionview 6.1.7.2 → 7.1.3

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 (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