actionview 7.1.2 → 8.0.2

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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +51 -382
  3. data/lib/action_view/base.rb +25 -11
  4. data/lib/action_view/cache_expiry.rb +9 -3
  5. data/lib/action_view/dependency_tracker/erb_tracker.rb +36 -27
  6. data/lib/action_view/dependency_tracker/ruby_tracker.rb +43 -0
  7. data/lib/action_view/dependency_tracker/wildcard_resolver.rb +32 -0
  8. data/lib/action_view/dependency_tracker.rb +2 -1
  9. data/lib/action_view/digestor.rb +6 -2
  10. data/lib/action_view/gem_version.rb +2 -2
  11. data/lib/action_view/helpers/asset_tag_helper.rb +18 -6
  12. data/lib/action_view/helpers/atom_feed_helper.rb +0 -2
  13. data/lib/action_view/helpers/cache_helper.rb +14 -6
  14. data/lib/action_view/helpers/csrf_helper.rb +1 -1
  15. data/lib/action_view/helpers/date_helper.rb +3 -3
  16. data/lib/action_view/helpers/form_helper.rb +282 -273
  17. data/lib/action_view/helpers/form_options_helper.rb +23 -21
  18. data/lib/action_view/helpers/form_tag_helper.rb +104 -69
  19. data/lib/action_view/helpers/number_helper.rb +35 -329
  20. data/lib/action_view/helpers/output_safety_helper.rb +5 -6
  21. data/lib/action_view/helpers/rendering_helper.rb +160 -50
  22. data/lib/action_view/helpers/sanitize_helper.rb +31 -14
  23. data/lib/action_view/helpers/tag_helper.rb +196 -19
  24. data/lib/action_view/helpers/tags/collection_check_boxes.rb +4 -3
  25. data/lib/action_view/helpers/tags/collection_helpers.rb +2 -1
  26. data/lib/action_view/helpers/text_helper.rb +125 -69
  27. data/lib/action_view/helpers/url_helper.rb +6 -80
  28. data/lib/action_view/layouts.rb +11 -13
  29. data/lib/action_view/log_subscriber.rb +8 -4
  30. data/lib/action_view/railtie.rb +0 -1
  31. data/lib/action_view/record_identifier.rb +1 -1
  32. data/lib/action_view/render_parser/prism_render_parser.rb +139 -0
  33. data/lib/action_view/{ripper_ast_parser.rb → render_parser/ripper_render_parser.rb} +162 -10
  34. data/lib/action_view/render_parser.rb +21 -169
  35. data/lib/action_view/renderer/abstract_renderer.rb +1 -1
  36. data/lib/action_view/renderer/partial_renderer.rb +2 -2
  37. data/lib/action_view/renderer/renderer.rb +32 -38
  38. data/lib/action_view/renderer/streaming_template_renderer.rb +0 -1
  39. data/lib/action_view/renderer/template_renderer.rb +3 -3
  40. data/lib/action_view/rendering.rb +6 -7
  41. data/lib/action_view/template/error.rb +11 -0
  42. data/lib/action_view/template/handlers/erb.rb +45 -37
  43. data/lib/action_view/template/renderable.rb +7 -1
  44. data/lib/action_view/template/resolver.rb +0 -3
  45. data/lib/action_view/template.rb +46 -12
  46. data/lib/action_view/test_case.rb +14 -16
  47. data/lib/action_view/unbound_template.rb +4 -4
  48. data/lib/action_view.rb +1 -1
  49. metadata +17 -19
  50. data/lib/action_view/dependency_tracker/ripper_tracker.rb +0 -59
  51. data/lib/assets/compiled/rails-ujs.js +0 -777
@@ -74,7 +74,7 @@ module ActionView
74
74
  end
75
75
 
76
76
  def dependencies
77
- render_dependencies + explicit_dependencies
77
+ WildcardResolver.new(@view_paths, render_dependencies + explicit_dependencies).resolve
78
78
  end
79
79
 
80
80
  attr_reader :name, :template
@@ -90,15 +90,15 @@ module ActionView
90
90
  end
91
91
 
92
92
  def render_dependencies
93
- render_dependencies = []
93
+ dependencies = []
94
94
  render_calls = source.split(/\brender\b/).drop(1)
95
95
 
96
96
  render_calls.each do |arguments|
97
- add_dependencies(render_dependencies, arguments, LAYOUT_DEPENDENCY)
98
- add_dependencies(render_dependencies, arguments, RENDER_ARGUMENTS)
97
+ add_dependencies(dependencies, arguments, LAYOUT_DEPENDENCY)
98
+ add_dependencies(dependencies, arguments, RENDER_ARGUMENTS)
99
99
  end
100
100
 
101
- render_dependencies.uniq
101
+ dependencies
102
102
  end
103
103
 
104
104
  def add_dependencies(render_dependencies, arguments, pattern)
@@ -116,12 +116,37 @@ module ActionView
116
116
  end
117
117
 
118
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
119
+ if quote_type == '"' && dependency.include?('#{')
120
+ scanner = StringScanner.new(dependency)
121
+
122
+ wildcard_dependency = +""
123
+
124
+ while !scanner.eos?
125
+ if scanner.scan_until(/\#{/)
126
+ unmatched_brackets = 1
127
+ wildcard_dependency << scanner.pre_match
128
+
129
+ while unmatched_brackets > 0 && !scanner.eos?
130
+ found = scanner.scan_until(/[{}]/)
131
+ return unless found
132
+
133
+ case scanner.matched
134
+ when "{"
135
+ unmatched_brackets += 1
136
+ when "}"
137
+ unmatched_brackets -= 1
138
+ end
139
+ end
140
+
141
+ wildcard_dependency << "*"
142
+ else
143
+ wildcard_dependency << scanner.rest
144
+ scanner.terminate
145
+ end
146
+ end
123
147
 
124
- if dependency
148
+ dependencies << wildcard_dependency
149
+ elsif dependency
125
150
  if dependency.include?("/")
126
151
  dependencies << dependency
127
152
  else
@@ -130,24 +155,8 @@ module ActionView
130
155
  end
131
156
  end
132
157
 
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
158
  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
159
+ source.scan(EXPLICIT_DEPENDENCY).flatten.uniq
151
160
  end
152
161
  end
153
162
  end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ class DependencyTracker # :nodoc:
5
+ class RubyTracker # :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
+ WildcardResolver.new(view_paths, render_dependencies + explicit_dependencies).resolve
14
+ end
15
+
16
+ def self.supports_view_paths? # :nodoc:
17
+ true
18
+ end
19
+
20
+ def initialize(name, template, view_paths = nil, parser_class: RenderParser::Default)
21
+ @name, @template, @view_paths = name, template, view_paths
22
+ @parser_class = parser_class
23
+ end
24
+
25
+ private
26
+ attr_reader :template, :name, :view_paths
27
+
28
+ def render_dependencies
29
+ return [] unless template.source.include?("render")
30
+
31
+ compiled_source = template.handler.call(template, template.source)
32
+
33
+ @parser_class.new(@name, compiled_source).render_calls.filter_map do |render_call|
34
+ render_call.gsub(%r|/_|, "/")
35
+ end
36
+ end
37
+
38
+ def explicit_dependencies
39
+ template.source.scan(EXPLICIT_DEPENDENCY).flatten.uniq
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ class DependencyTracker # :nodoc:
5
+ class WildcardResolver # :nodoc:
6
+ def initialize(view_paths, dependencies)
7
+ @view_paths = view_paths
8
+
9
+ @wildcard_dependencies, @explicit_dependencies =
10
+ dependencies.partition { |dependency| dependency.end_with?("/*") }
11
+ end
12
+
13
+ def resolve
14
+ return explicit_dependencies.uniq if !view_paths || wildcard_dependencies.empty?
15
+
16
+ (explicit_dependencies + resolved_wildcard_dependencies).uniq
17
+ end
18
+
19
+ private
20
+ attr_reader :explicit_dependencies, :wildcard_dependencies, :view_paths
21
+
22
+ def resolved_wildcard_dependencies
23
+ # Remove trailing "/*"
24
+ prefixes = wildcard_dependencies.map { |query| query[0..-3] }
25
+
26
+ view_paths.flat_map(&:all_template_paths).uniq.filter_map { |path|
27
+ path.to_s if prefixes.include?(path.prefix)
28
+ }.sort
29
+ end
30
+ end
31
+ end
32
+ end
@@ -9,7 +9,8 @@ module ActionView
9
9
  extend ActiveSupport::Autoload
10
10
 
11
11
  autoload :ERBTracker
12
- autoload :RipperTracker
12
+ autoload :RubyTracker
13
+ autoload :WildcardResolver
13
14
 
14
15
  @trackers = Concurrent::Map.new
15
16
 
@@ -107,8 +107,12 @@ module ActionView
107
107
  end.join("-")
108
108
  end
109
109
 
110
- def to_dep_map
111
- children.any? ? { name => children.map(&:to_dep_map) } : name
110
+ def to_dep_map(seen = Set.new.compare_by_identity)
111
+ if seen.add?(self)
112
+ children.any? ? { name => children.map { |c| c.to_dep_map(seen) } } : name
113
+ else # the tree has a cycle
114
+ name
115
+ end
112
116
  end
113
117
  end
114
118
 
@@ -7,8 +7,8 @@ module ActionView
7
7
  end
8
8
 
9
9
  module VERSION
10
- MAJOR = 7
11
- MINOR = 1
10
+ MAJOR = 8
11
+ MINOR = 0
12
12
  TINY = 2
13
13
  PRE = nil
14
14
 
@@ -68,6 +68,8 @@ module ActionView
68
68
  # attribute, which indicates to the browser that the script is meant to
69
69
  # be executed after the document has been parsed. Additionally, prevents
70
70
  # sending the Preload Links header.
71
+ # * <tt>:nopush</tt> - Specify if the use of server push is not desired
72
+ # for the script. Defaults to +true+.
71
73
  #
72
74
  # Any other specified options will be treated as HTML attributes for the
73
75
  # +script+ tag.
@@ -166,6 +168,10 @@ module ActionView
166
168
  # that path.
167
169
  # * <tt>:skip_pipeline</tt> - This option is used to bypass the asset pipeline
168
170
  # when it is set to true.
171
+ # * <tt>:nonce</tt> - When set to true, adds an automatic nonce value if
172
+ # you have Content Security Policy enabled.
173
+ # * <tt>:nopush</tt> - Specify if the use of server push is not desired
174
+ # for the stylesheet. Defaults to +true+.
169
175
  #
170
176
  # ==== Examples
171
177
  #
@@ -190,6 +196,9 @@ module ActionView
190
196
  # stylesheet_link_tag "random.styles", "/css/stylish"
191
197
  # # => <link href="/assets/random.styles" rel="stylesheet" />
192
198
  # # <link href="/css/stylish.css" rel="stylesheet" />
199
+ #
200
+ # stylesheet_link_tag "style", nonce: true
201
+ # # => <link href="/assets/style.css" rel="stylesheet" nonce="..." />
193
202
  def stylesheet_link_tag(*sources)
194
203
  options = sources.extract_options!.stringify_keys
195
204
  path_options = options.extract!("protocol", "extname", "host", "skip_pipeline").symbolize_keys
@@ -214,6 +223,9 @@ module ActionView
214
223
  "crossorigin" => crossorigin,
215
224
  "href" => href
216
225
  }.merge!(options)
226
+ if tag_options["nonce"] == true
227
+ tag_options["nonce"] = content_security_policy_nonce
228
+ end
217
229
 
218
230
  if apply_stylesheet_media_default && tag_options["media"].blank?
219
231
  tag_options["media"] = "screen"
@@ -351,13 +363,13 @@ module ActionView
351
363
  nopush = options.delete(:nopush) || false
352
364
  rel = mime_type == "module" ? "modulepreload" : "preload"
353
365
 
354
- link_tag = tag.link(**{
366
+ link_tag = tag.link(
355
367
  rel: rel,
356
368
  href: href,
357
369
  as: as_type,
358
370
  type: mime_type,
359
- crossorigin: crossorigin
360
- }.merge!(options.symbolize_keys))
371
+ crossorigin: crossorigin,
372
+ **options.symbolize_keys)
361
373
 
362
374
  preload_link = "<#{href}>; rel=#{rel}; as=#{as_type}"
363
375
  preload_link += "; type=#{mime_type}" if mime_type
@@ -645,11 +657,11 @@ module ActionView
645
657
  return if response_present && response.sending?
646
658
 
647
659
  if respond_to?(:request) && request
648
- request.send_early_hints("Link" => preload_links.join("\n"))
660
+ request.send_early_hints("link" => preload_links.join(","))
649
661
  end
650
662
 
651
663
  if response_present
652
- header = +response.headers["Link"].to_s
664
+ header = +response.headers["link"].to_s
653
665
  preload_links.each do |link|
654
666
  break if header.bytesize + link.bytesize > max_header_size
655
667
 
@@ -660,7 +672,7 @@ module ActionView
660
672
  end
661
673
  end
662
674
 
663
- response.headers["Link"] = header
675
+ response.headers["link"] = header
664
676
  end
665
677
  end
666
678
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "set"
4
-
5
3
  module ActionView
6
4
  module Helpers # :nodoc:
7
5
  # = Action View Atom Feed \Helpers
@@ -76,11 +76,11 @@ module ActionView
76
76
  # render 'comments/comments'
77
77
  # render('comments/comments')
78
78
  #
79
- # render "header" translates to render("comments/header")
79
+ # render "header" # translates to render("comments/header")
80
80
  #
81
- # render(@topic) translates to render("topics/topic")
82
- # render(topics) translates to render("topics/topic")
83
- # render(message.topics) translates to render("topics/topic")
81
+ # render(@topic) # translates to render("topics/topic")
82
+ # render(topics) # translates to render("topics/topic")
83
+ # render(message.topics) # translates to render("topics/topic")
84
84
  #
85
85
  # It's not possible to derive all render calls like that, though.
86
86
  # Here are a few examples of things that can't be derived:
@@ -93,6 +93,14 @@ module ActionView
93
93
  # render partial: 'attachments/attachment', collection: group_of_attachments
94
94
  # render partial: 'documents/document', collection: @project.documents.where(published: true).order('created_at')
95
95
  #
96
+ # One last type of dependency can be determined implicitly:
97
+ #
98
+ # render "maintenance_tasks/runs/info/#{run.status}"
99
+ #
100
+ # Because the value passed to render ends in interpolation, Action View
101
+ # will mark all partials within the "maintenance_tasks/runs/info" folder as
102
+ # dependencies.
103
+ #
96
104
  # === Explicit dependencies
97
105
  #
98
106
  # Sometimes you'll have template dependencies that can't be derived at all. This is typically
@@ -189,7 +197,7 @@ module ActionView
189
197
  CachingRegistry.caching?
190
198
  end
191
199
 
192
- # Raises +UncacheableFragmentError+ when called from within a +cache+ block.
200
+ # Raises UncacheableFragmentError when called from within a +cache+ block.
193
201
  #
194
202
  # Useful to denote helper methods that can't participate in fragment caching:
195
203
  #
@@ -198,7 +206,7 @@ module ActionView
198
206
  # "#{project.name} - #{Time.now}"
199
207
  # end
200
208
  #
201
- # # Which will then raise if used within a +cache+ block:
209
+ # # Which will then raise if used within a `cache` block:
202
210
  # <% cache project do %>
203
211
  # <%= project_name_with_time(project) %>
204
212
  # <% end %>
@@ -17,7 +17,7 @@ module ActionView
17
17
  # You don't need to use these tags for regular forms as they generate their own hidden fields.
18
18
  #
19
19
  # For Ajax requests other than GETs, extract the "csrf-token" from the meta-tag and send as the
20
- # +X-CSRF-Token+ HTTP header. If you are using rails-ujs, this happens automatically.
20
+ # +X-CSRF-Token+ HTTP header.
21
21
  #
22
22
  def csrf_meta_tags
23
23
  if defined?(protect_against_forgery?) && protect_against_forgery?
@@ -1228,7 +1228,7 @@ module ActionView
1228
1228
  class FormBuilder
1229
1229
  # Wraps ActionView::Helpers::DateHelper#date_select for form builders:
1230
1230
  #
1231
- # <%= form_for @person do |f| %>
1231
+ # <%= form_with model: @person do |f| %>
1232
1232
  # <%= f.date_select :birth_date %>
1233
1233
  # <%= f.submit %>
1234
1234
  # <% end %>
@@ -1240,7 +1240,7 @@ module ActionView
1240
1240
 
1241
1241
  # Wraps ActionView::Helpers::DateHelper#time_select for form builders:
1242
1242
  #
1243
- # <%= form_for @race do |f| %>
1243
+ # <%= form_with model: @race do |f| %>
1244
1244
  # <%= f.time_select :average_lap %>
1245
1245
  # <%= f.submit %>
1246
1246
  # <% end %>
@@ -1252,7 +1252,7 @@ module ActionView
1252
1252
 
1253
1253
  # Wraps ActionView::Helpers::DateHelper#datetime_select for form builders:
1254
1254
  #
1255
- # <%= form_for @person do |f| %>
1255
+ # <%= form_with model: @person do |f| %>
1256
1256
  # <%= f.datetime_select :last_request_at %>
1257
1257
  # <%= f.submit %>
1258
1258
  # <% end %>