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
@@ -1,32 +1,140 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  module ActionView
4
6
  module Helpers # :nodoc:
5
- # = Action View \Rendering \Helpers
7
+ # # Action View Rendering Helpers
6
8
  #
7
- # Implements methods that allow rendering from a view context.
8
- # In order to use this module, all you need is to implement
9
- # view_renderer that returns an ActionView::Renderer object.
9
+ # Implements methods that allow rendering from a view context. In order to use
10
+ # this module, all you need is to implement view_renderer that returns an
11
+ # ActionView::Renderer object.
10
12
  module RenderingHelper
11
- # Returns the result of a render that's dictated by the options hash. The primary options are:
13
+ # Renders a template and returns the result.
14
+ #
15
+ # Pass the template to render as the first argument. This is shorthand
16
+ # syntax for partial rendering, so the template filename should be
17
+ # prefixed with an underscore. The partial renderer looks for the partial
18
+ # template in the directory of the calling template first.
19
+ #
20
+ # <% # app/views/posts/new.html.erb %>
21
+ # <%= render "form" %>
22
+ # # => renders app/views/posts/_form.html.erb
23
+ #
24
+ # Use the complete view path to render a partial from another directory.
25
+ #
26
+ # <% # app/views/posts/show.html.erb %>
27
+ # <%= render "comments/form" %>
28
+ # # => renders app/views/comments/_form.html.erb
29
+ #
30
+ # Without the rendering mode, the second argument can be a Hash of local
31
+ # variable assignments for the template.
32
+ #
33
+ # <% # app/views/posts/new.html.erb %>
34
+ # <%= render "form", post: Post.new %>
35
+ # # => renders app/views/posts/_form.html.erb
36
+ #
37
+ # If the first argument responds to `render_in`, the template will be rendered
38
+ # by calling `render_in` with the current view context.
39
+ #
40
+ # class Greeting
41
+ # def render_in(view_context)
42
+ # view_context.render html: "<h1>Hello, World</h1>"
43
+ # end
44
+ #
45
+ # def format
46
+ # :html
47
+ # end
48
+ # end
49
+ #
50
+ # <%= render Greeting.new %>
51
+ # # => "<h1>Hello, World</h1>"
52
+ #
53
+ # #### Rendering Mode
54
+ #
55
+ # Pass the rendering mode as first argument to override it.
56
+ #
57
+ # `:partial`
58
+ # : See ActionView::PartialRenderer for details.
59
+ #
60
+ # <%= render partial: "form", locals: { post: Post.new } %>
61
+ # # => renders app/views/posts/_form.html.erb
62
+ #
63
+ # `:file`
64
+ # : Renders the contents of a file. This option should **not** be used with
65
+ # unsanitized user input.
66
+ #
67
+ # <%= render file: "/path/to/some/file" %>
68
+ # # => renders /path/to/some/file
69
+ #
70
+ # `:inline`
71
+ # : Renders an ERB template string.
72
+ #
73
+ # <% name = "World" %>
74
+ # <%= render inline: "<h1>Hello, <%= name %>!</h1>" %>
75
+ # # => renders "<h1>Hello, World!</h1>"
76
+ #
77
+ # `:body`
78
+ # : Renders the provided text, and sets the format as `:text`.
79
+ #
80
+ # <%= render body: "Hello, World!" %>
81
+ # # => renders "Hello, World!"
82
+ #
83
+ # `:plain`
84
+ # : Renders the provided text, and sets the format as `:text`.
85
+ #
86
+ # <%= render plain: "Hello, World!" %>
87
+ # # => renders "Hello, World!"
88
+ #
89
+ # `:html`
90
+ # : Renders the provided HTML string, and sets the format as
91
+ # `:html`. If the string is not `html_safe?`, performs HTML escaping on
92
+ # the string before rendering.
93
+ #
94
+ # <%= render html: "<h1>Hello, World!</h1>".html_safe %>
95
+ # # => renders "<h1>Hello, World!</h1>"
96
+ #
97
+ # <%= render html: "<h1>Hello, World!</h1>" %>
98
+ # # => renders "&lt;h1&gt;Hello, World!&lt;/h1&gt;"
99
+ #
100
+ # `:renderable`
101
+ # : Renders the provided object by calling `render_in` with the current view
102
+ # context. The format is determined by calling `format` on the
103
+ # renderable if it responds to `format`, falling back to `:html` by
104
+ # default.
105
+ #
106
+ # <%= render renderable: Greeting.new %>
107
+ # # => renders "<h1>Hello, World</h1>"
108
+ #
109
+ #
110
+ # #### Options
111
+ #
112
+ # `:locals`
113
+ # : Hash of local variable assignments for the template.
114
+ #
115
+ # <%= render inline: "<h1>Hello, <%= name %>!</h1>", locals: { name: "World" } %>
116
+ # # => renders "<h1>Hello, World!</h1>"
117
+ #
118
+ # `:formats`
119
+ # : Override the current format to render a template for a different format.
120
+ #
121
+ # <% # app/views/posts/show.html.erb %>
122
+ # <%= render template: "posts/content", formats: [:text] %>
123
+ # # => renders app/views/posts/content.text.erb
12
124
  #
13
- # * <tt>:partial</tt> - See ActionView::PartialRenderer.
14
- # * <tt>:file</tt> - Renders an explicit template file (this used to be the old default), add +:locals+ to pass in those.
15
- # * <tt>:inline</tt> - Renders an inline template similar to how it's done in the controller.
16
- # * <tt>:plain</tt> - Renders the text passed in out. Setting the content
17
- # type as <tt>text/plain</tt>.
18
- # * <tt>:html</tt> - Renders the HTML safe string passed in out, otherwise
19
- # performs HTML escape on the string first. Setting the content type as
20
- # <tt>text/html</tt>.
21
- # * <tt>:body</tt> - Renders the text passed in, and inherits the content
22
- # type of <tt>text/plain</tt> from ActionDispatch::Response object.
125
+ # `:variants`
126
+ # : Render a template for a different variant.
23
127
  #
24
- # If no <tt>options</tt> hash is passed or if <tt>:update</tt> is specified, then:
128
+ # <% # app/views/posts/show.html.erb %>
129
+ # <%= render template: "posts/content", variants: [:tablet] %>
130
+ # # => renders app/views/posts/content.html+tablet.erb
25
131
  #
26
- # If an object responding to +render_in+ is passed, +render_in+ is called on the object,
27
- # passing in the current view context.
132
+ # `:handlers`
133
+ # : Render a template for a different handler.
28
134
  #
29
- # Otherwise, a partial is rendered using the second parameter as the locals hash.
135
+ # <% # app/views/posts/show.html.erb %>
136
+ # <%= render template: "posts/content", handlers: [:builder] %>
137
+ # # => renders app/views/posts/content.html.builder
30
138
  def render(options = {}, locals = {}, &block)
31
139
  case options
32
140
  when Hash
@@ -47,52 +155,54 @@ module ActionView
47
155
  end
48
156
 
49
157
  # Overrides _layout_for in the context object so it supports the case a block is
50
- # passed to a partial. Returns the contents that are yielded to a layout, given a
51
- # name or a block.
158
+ # passed to a partial. Returns the contents that are yielded to a layout, given
159
+ # a name or a block.
52
160
  #
53
- # You can think of a layout as a method that is called with a block. If the user calls
54
- # <tt>yield :some_name</tt>, the block, by default, returns <tt>content_for(:some_name)</tt>.
55
- # If the user calls simply +yield+, the default block returns <tt>content_for(:layout)</tt>.
161
+ # You can think of a layout as a method that is called with a block. If the user
162
+ # calls `yield :some_name`, the block, by default, returns
163
+ # `content_for(:some_name)`. If the user calls simply `yield`, the default block
164
+ # returns `content_for(:layout)`.
56
165
  #
57
166
  # The user can override this default by passing a block to the layout:
58
167
  #
59
- # # The template
60
- # <%= render layout: "my_layout" do %>
61
- # Content
62
- # <% end %>
168
+ # # The template
169
+ # <%= render layout: "my_layout" do %>
170
+ # Content
171
+ # <% end %>
63
172
  #
64
- # # The layout
65
- # <html>
66
- # <%= yield %>
67
- # </html>
173
+ # # The layout
174
+ # <html>
175
+ # <%= yield %>
176
+ # </html>
68
177
  #
69
- # In this case, instead of the default block, which would return <tt>content_for(:layout)</tt>,
70
- # this method returns the block that was passed in to <tt>render :layout</tt>, and the response
178
+ # In this case, instead of the default block, which would return `content_for(:layout)`,
179
+ # this method returns the block that was passed in to `render :layout`, and the response
71
180
  # would be
72
181
  #
73
- # <html>
74
- # Content
75
- # </html>
182
+ # <html>
183
+ # Content
184
+ # </html>
76
185
  #
77
- # Finally, the block can take block arguments, which can be passed in by +yield+:
186
+ # Finally, the block can take block arguments, which can be passed in by
187
+ # `yield`:
78
188
  #
79
- # # The template
80
- # <%= render layout: "my_layout" do |customer| %>
81
- # Hello <%= customer.name %>
82
- # <% end %>
189
+ # # The template
190
+ # <%= render layout: "my_layout" do |customer| %>
191
+ # Hello <%= customer.name %>
192
+ # <% end %>
83
193
  #
84
- # # The layout
85
- # <html>
86
- # <%= yield Struct.new(:name).new("David") %>
87
- # </html>
194
+ # # The layout
195
+ # <html>
196
+ # <%= yield Struct.new(:name).new("David") %>
197
+ # </html>
88
198
  #
89
- # In this case, the layout would receive the block passed into <tt>render :layout</tt>,
199
+ # In this case, the layout would receive the block passed into `render :layout`,
90
200
  # and the struct specified would be passed into the block as an argument. The result
91
201
  # would be
92
202
  #
93
- # <html>
94
- # Hello David
95
- # </html>
203
+ # <html>
204
+ # Hello David
205
+ # </html>
96
206
  #
97
207
  def _layout_for(*args, &block)
98
208
  name = args.first
@@ -15,38 +15,49 @@ module ActionView
15
15
 
16
16
  # Sanitizes HTML input, stripping all but known-safe tags and attributes.
17
17
  #
18
- # It also strips href/src attributes with unsafe protocols like <tt>javascript:</tt>, while
18
+ # It also strips +href+ / +src+ attributes with unsafe protocols like +javascript:+, while
19
19
  # also protecting against attempts to use Unicode, ASCII, and hex character references to work
20
20
  # around these protocol filters.
21
21
  #
22
- # The default sanitizer is Rails::HTML5::SafeListSanitizer. See {Rails HTML
22
+ # The default sanitizer is +Rails::HTML5::SafeListSanitizer+. See {Rails HTML
23
23
  # Sanitizers}[https://github.com/rails/rails-html-sanitizer] for more information.
24
24
  #
25
25
  # Custom sanitization rules can also be provided.
26
26
  #
27
+ # <b>Warning</b>: Adding disallowed tags or attributes to the allowlists may introduce
28
+ # vulnerabilities into your application. Please rely on the default allowlists whenever
29
+ # possible, because they are curated to maintain security and safety. If you think that the
30
+ # default allowlists should be expanded, please {open an issue on the rails-html-sanitizer
31
+ # project}[https://github.com/rails/rails-html-sanitizer/issues].
32
+ #
27
33
  # Please note that sanitizing user-provided text does not guarantee that the
28
34
  # resulting markup is valid or even well-formed.
29
35
  #
30
36
  # ==== Options
31
37
  #
32
- # * <tt>:tags</tt> - An array of allowed tags.
33
- # * <tt>:attributes</tt> - An array of allowed attributes.
34
- # * <tt>:scrubber</tt> - A {Rails::HTML scrubber}[https://github.com/rails/rails-html-sanitizer]
38
+ # [+:tags+]
39
+ # An array of allowed tags.
40
+ #
41
+ # [+:attributes+]
42
+ # An array of allowed attributes.
43
+ #
44
+ # [+:scrubber+]
45
+ # A {Rails::HTML scrubber}[https://github.com/rails/rails-html-sanitizer]
35
46
  # or {Loofah::Scrubber}[https://github.com/flavorjones/loofah] object that
36
47
  # defines custom sanitization rules. A custom scrubber takes precedence over
37
48
  # custom tags and attributes.
38
49
  #
39
50
  # ==== Examples
40
51
  #
41
- # Normal use:
52
+ # ===== Normal use
42
53
  #
43
54
  # <%= sanitize @comment.body %>
44
55
  #
45
- # Providing custom lists of permitted tags and attributes:
56
+ # ===== Providing custom lists of permitted tags and attributes
46
57
  #
47
58
  # <%= sanitize @comment.body, tags: %w(strong em a), attributes: %w(href) %>
48
59
  #
49
- # Providing a custom Rails::HTML scrubber:
60
+ # ===== Providing a custom +Rails::HTML+ scrubber
50
61
  #
51
62
  # class CommentScrubber < Rails::HTML::PermitScrubber
52
63
  # def initialize
@@ -60,21 +71,27 @@ module ActionView
60
71
  # end
61
72
  # end
62
73
  #
74
+ # <code></code>
75
+ #
63
76
  # <%= sanitize @comment.body, scrubber: CommentScrubber.new %>
64
77
  #
65
78
  # See {Rails HTML Sanitizer}[https://github.com/rails/rails-html-sanitizer] for
66
- # documentation about Rails::HTML scrubbers.
79
+ # documentation about +Rails::HTML+ scrubbers.
67
80
  #
68
- # Providing a custom Loofah::Scrubber:
81
+ # ===== Providing a custom +Loofah::Scrubber+
69
82
  #
70
83
  # scrubber = Loofah::Scrubber.new do |node|
71
84
  # node.remove if node.name == 'script'
72
85
  # end
73
86
  #
87
+ # <code></code>
88
+ #
74
89
  # <%= sanitize @comment.body, scrubber: scrubber %>
75
90
  #
76
91
  # See {Loofah's documentation}[https://github.com/flavorjones/loofah] for more
77
- # information about defining custom Loofah::Scrubber objects.
92
+ # information about defining custom +Loofah::Scrubber+ objects.
93
+ #
94
+ # ==== Global Configuration
78
95
  #
79
96
  # To set the default allowed tags or attributes across your application:
80
97
  #
@@ -95,13 +112,13 @@ module ActionView
95
112
  # # In config/application.rb
96
113
  # config.action_view.sanitizer_vendor = Rails::HTML5::Sanitizer
97
114
  #
98
- # NOTE: Rails::HTML5::Sanitizer is not supported on JRuby, so on JRuby platforms \Rails will
99
- # fall back to use Rails::HTML4::Sanitizer.
115
+ # NOTE: +Rails::HTML5::Sanitizer+ is not supported on JRuby, so on JRuby platforms \Rails will
116
+ # fall back to using +Rails::HTML4::Sanitizer+.
100
117
  def sanitize(html, options = {})
101
118
  self.class.safe_list_sanitizer.sanitize(html, options)&.html_safe
102
119
  end
103
120
 
104
- # Sanitizes a block of CSS code. Used by +sanitize+ when it comes across a style attribute.
121
+ # Sanitizes a block of CSS code. Used by #sanitize when it comes across a style attribute.
105
122
  def sanitize_css(style)
106
123
  self.class.safe_list_sanitizer.sanitize_css(style)
107
124
  end
@@ -1,8 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/code_generator"
3
4
  require "active_support/core_ext/enumerable"
4
5
  require "active_support/core_ext/string/output_safety"
5
- require "set"
6
+ require "active_support/core_ext/string/inflections"
6
7
  require "action_view/helpers/capture_helper"
7
8
  require "action_view/helpers/output_safety_helper"
8
9
 
@@ -46,8 +47,171 @@ module ActionView
46
47
  include CaptureHelper
47
48
  include OutputSafetyHelper
48
49
 
49
- HTML_VOID_ELEMENTS = %i(area base br col embed hr img input keygen link meta param source track wbr).to_set
50
- SVG_SELF_CLOSING_ELEMENTS = %i(animate animateMotion animateTransform circle ellipse line path polygon polyline rect set stop use view).to_set
50
+ def self.define_element(name, code_generator:, method_name: name)
51
+ return if method_defined?(name)
52
+
53
+ code_generator.class_eval do |batch|
54
+ batch << "\n" <<
55
+ "def #{method_name}(content = nil, escape: true, **options, &block)" <<
56
+ " tag_string(#{name.inspect}, content, options, escape: escape, &block)" <<
57
+ "end"
58
+ end
59
+ end
60
+
61
+ def self.define_void_element(name, code_generator:, method_name: name)
62
+ code_generator.class_eval do |batch|
63
+ batch << "\n" <<
64
+ "def #{method_name}(escape: true, **options, &block)" <<
65
+ " self_closing_tag_string(#{name.inspect}, options, escape, '>')" <<
66
+ "end"
67
+ end
68
+ end
69
+
70
+ def self.define_self_closing_element(name, code_generator:, method_name: name)
71
+ code_generator.class_eval do |batch|
72
+ batch << "\n" <<
73
+ "def #{method_name}(content = nil, escape: true, **options, &block)" <<
74
+ " if content || block" <<
75
+ " tag_string(#{name.inspect}, content, options, escape: escape, &block)" <<
76
+ " else" <<
77
+ " self_closing_tag_string(#{name.inspect}, options, escape)" <<
78
+ " end" <<
79
+ "end"
80
+ end
81
+ end
82
+
83
+ ActiveSupport::CodeGenerator.batch(self, __FILE__, __LINE__) do |code_generator|
84
+ define_void_element :area, code_generator: code_generator
85
+ define_void_element :base, code_generator: code_generator
86
+ define_void_element :br, code_generator: code_generator
87
+ define_void_element :col, code_generator: code_generator
88
+ define_void_element :embed, code_generator: code_generator
89
+ define_void_element :hr, code_generator: code_generator
90
+ define_void_element :img, code_generator: code_generator
91
+ define_void_element :input, code_generator: code_generator
92
+ define_void_element :keygen, code_generator: code_generator
93
+ define_void_element :link, code_generator: code_generator
94
+ define_void_element :meta, code_generator: code_generator
95
+ define_void_element :source, code_generator: code_generator
96
+ define_void_element :track, code_generator: code_generator
97
+ define_void_element :wbr, code_generator: code_generator
98
+
99
+ define_self_closing_element :animate, code_generator: code_generator
100
+ define_self_closing_element :animateMotion, code_generator: code_generator, method_name: :animate_motion
101
+ define_self_closing_element :animateTransform, code_generator: code_generator, method_name: :animate_transform
102
+ define_self_closing_element :circle, code_generator: code_generator
103
+ define_self_closing_element :ellipse, code_generator: code_generator
104
+ define_self_closing_element :line, code_generator: code_generator
105
+ define_self_closing_element :path, code_generator: code_generator
106
+ define_self_closing_element :polygon, code_generator: code_generator
107
+ define_self_closing_element :polyline, code_generator: code_generator
108
+ define_self_closing_element :rect, code_generator: code_generator
109
+ define_self_closing_element :set, code_generator: code_generator
110
+ define_self_closing_element :stop, code_generator: code_generator
111
+ define_self_closing_element :use, code_generator: code_generator
112
+ define_self_closing_element :view, code_generator: code_generator
113
+
114
+ define_element :a, code_generator: code_generator
115
+ define_element :abbr, code_generator: code_generator
116
+ define_element :address, code_generator: code_generator
117
+ define_element :article, code_generator: code_generator
118
+ define_element :aside, code_generator: code_generator
119
+ define_element :audio, code_generator: code_generator
120
+ define_element :b, code_generator: code_generator
121
+ define_element :bdi, code_generator: code_generator
122
+ define_element :bdo, code_generator: code_generator
123
+ define_element :blockquote, code_generator: code_generator
124
+ define_element :body, code_generator: code_generator
125
+ define_element :button, code_generator: code_generator
126
+ define_element :canvas, code_generator: code_generator
127
+ define_element :caption, code_generator: code_generator
128
+ define_element :cite, code_generator: code_generator
129
+ define_element :code, code_generator: code_generator
130
+ define_element :colgroup, code_generator: code_generator
131
+ define_element :data, code_generator: code_generator
132
+ define_element :datalist, code_generator: code_generator
133
+ define_element :dd, code_generator: code_generator
134
+ define_element :del, code_generator: code_generator
135
+ define_element :details, code_generator: code_generator
136
+ define_element :dfn, code_generator: code_generator
137
+ define_element :dialog, code_generator: code_generator
138
+ define_element :div, code_generator: code_generator
139
+ define_element :dl, code_generator: code_generator
140
+ define_element :dt, code_generator: code_generator
141
+ define_element :em, code_generator: code_generator
142
+ define_element :fieldset, code_generator: code_generator
143
+ define_element :figcaption, code_generator: code_generator
144
+ define_element :figure, code_generator: code_generator
145
+ define_element :footer, code_generator: code_generator
146
+ define_element :form, code_generator: code_generator
147
+ define_element :h1, code_generator: code_generator
148
+ define_element :h2, code_generator: code_generator
149
+ define_element :h3, code_generator: code_generator
150
+ define_element :h4, code_generator: code_generator
151
+ define_element :h5, code_generator: code_generator
152
+ define_element :h6, code_generator: code_generator
153
+ define_element :head, code_generator: code_generator
154
+ define_element :header, code_generator: code_generator
155
+ define_element :hgroup, code_generator: code_generator
156
+ define_element :html, code_generator: code_generator
157
+ define_element :i, code_generator: code_generator
158
+ define_element :iframe, code_generator: code_generator
159
+ define_element :ins, code_generator: code_generator
160
+ define_element :kbd, code_generator: code_generator
161
+ define_element :label, code_generator: code_generator
162
+ define_element :legend, code_generator: code_generator
163
+ define_element :li, code_generator: code_generator
164
+ define_element :main, code_generator: code_generator
165
+ define_element :map, code_generator: code_generator
166
+ define_element :mark, code_generator: code_generator
167
+ define_element :menu, code_generator: code_generator
168
+ define_element :meter, code_generator: code_generator
169
+ define_element :nav, code_generator: code_generator
170
+ define_element :noscript, code_generator: code_generator
171
+ define_element :object, code_generator: code_generator
172
+ define_element :ol, code_generator: code_generator
173
+ define_element :optgroup, code_generator: code_generator
174
+ define_element :option, code_generator: code_generator
175
+ define_element :output, code_generator: code_generator
176
+ define_element :p, code_generator: code_generator
177
+ define_element :picture, code_generator: code_generator
178
+ define_element :portal, code_generator: code_generator
179
+ define_element :pre, code_generator: code_generator
180
+ define_element :progress, code_generator: code_generator
181
+ define_element :q, code_generator: code_generator
182
+ define_element :rp, code_generator: code_generator
183
+ define_element :rt, code_generator: code_generator
184
+ define_element :ruby, code_generator: code_generator
185
+ define_element :s, code_generator: code_generator
186
+ define_element :samp, code_generator: code_generator
187
+ define_element :script, code_generator: code_generator
188
+ define_element :search, code_generator: code_generator
189
+ define_element :section, code_generator: code_generator
190
+ define_element :select, code_generator: code_generator
191
+ define_element :slot, code_generator: code_generator
192
+ define_element :small, code_generator: code_generator
193
+ define_element :span, code_generator: code_generator
194
+ define_element :strong, code_generator: code_generator
195
+ define_element :style, code_generator: code_generator
196
+ define_element :sub, code_generator: code_generator
197
+ define_element :summary, code_generator: code_generator
198
+ define_element :sup, code_generator: code_generator
199
+ define_element :table, code_generator: code_generator
200
+ define_element :tbody, code_generator: code_generator
201
+ define_element :td, code_generator: code_generator
202
+ define_element :template, code_generator: code_generator
203
+ define_element :textarea, code_generator: code_generator
204
+ define_element :tfoot, code_generator: code_generator
205
+ define_element :th, code_generator: code_generator
206
+ define_element :thead, code_generator: code_generator
207
+ define_element :time, code_generator: code_generator
208
+ define_element :title, code_generator: code_generator
209
+ define_element :tr, code_generator: code_generator
210
+ define_element :u, code_generator: code_generator
211
+ define_element :ul, code_generator: code_generator
212
+ define_element :var, code_generator: code_generator
213
+ define_element :video, code_generator: code_generator
214
+ end
51
215
 
52
216
  def initialize(view_context)
53
217
  @view_context = view_context
@@ -62,28 +226,22 @@ module ActionView
62
226
  tag_options(attributes.to_h).to_s.strip.html_safe
63
227
  end
64
228
 
65
- def p(*arguments, **options, &block)
66
- tag_string(:p, *arguments, **options, &block)
229
+ def tag_string(name, content = nil, options, escape: true, &block)
230
+ content = @view_context.capture(self, &block) if block
231
+
232
+ content_tag_string(name, content, options, escape)
67
233
  end
68
234
 
69
- def tag_string(name, content = nil, escape: true, **options, &block)
70
- content = @view_context.capture(self, &block) if block_given?
71
- self_closing = SVG_SELF_CLOSING_ELEMENTS.include?(name)
72
- if (HTML_VOID_ELEMENTS.include?(name) || self_closing) && content.nil?
73
- "<#{name.to_s.dasherize}#{tag_options(options, escape)}#{self_closing ? " />" : ">"}".html_safe
74
- else
75
- content_tag_string(name.to_s.dasherize, content || "", options, escape)
76
- end
235
+ def self_closing_tag_string(name, options, escape = true, tag_suffix = " />")
236
+ "<#{name}#{tag_options(options, escape)}#{tag_suffix}".html_safe
77
237
  end
78
238
 
79
239
  def content_tag_string(name, content, options, escape = true)
80
240
  tag_options = tag_options(options, escape) if options
81
241
 
82
- if escape
83
- name = ERB::Util.xml_name_escape(name)
242
+ if escape && content.present?
84
243
  content = ERB::Util.unwrapped_html_escape(content)
85
244
  end
86
-
87
245
  "<#{name}#{tag_options}>#{PRE_CONTENT_STRINGS[name]}#{content}</#{name}>".html_safe
88
246
  end
89
247
 
@@ -163,8 +321,12 @@ module ActionView
163
321
  true
164
322
  end
165
323
 
166
- def method_missing(called, *args, **options, &block)
167
- tag_string(called, *args, **options, &block)
324
+ def method_missing(called, *args, escape: true, **options, &block)
325
+ name = called.name.dasherize
326
+
327
+ TagHelper.ensure_valid_html5_tag_name(name)
328
+
329
+ tag_string(name, *args, options, escape: escape, &block)
168
330
  end
169
331
  end
170
332
 
@@ -245,6 +407,14 @@ module ActionView
245
407
  # # A void element:
246
408
  # tag.br # => <br>
247
409
  #
410
+ # Note that when using the block form options should be wrapped in
411
+ # parenthesis.
412
+ #
413
+ # <%= tag.a(href: "/about", class: "font-bold") do %>
414
+ # About the author
415
+ # <% end %>
416
+ # # => <a href="/about" class="font-bold">About the author</a>
417
+ #
248
418
  # === Building HTML attributes
249
419
  #
250
420
  # Transforms a Hash into HTML attributes, ready to be interpolated into
@@ -310,7 +480,7 @@ module ActionView
310
480
  if name.nil?
311
481
  tag_builder
312
482
  else
313
- name = ERB::Util.xml_name_escape(name) if escape
483
+ ensure_valid_html5_tag_name(name)
314
484
  "<#{name}#{tag_builder.tag_options(options, escape) if options}#{open ? ">" : " />"}".html_safe
315
485
  end
316
486
  end
@@ -344,6 +514,8 @@ module ActionView
344
514
  # <% end -%>
345
515
  # # => <div class="strong">Hello world!</div>
346
516
  def content_tag(name, content_or_options_with_block = nil, options = nil, escape = true, &block)
517
+ ensure_valid_html5_tag_name(name)
518
+
347
519
  if block_given?
348
520
  options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash)
349
521
  tag_builder.content_tag_string(name, capture(&block), options, escape)
@@ -400,6 +572,11 @@ module ActionView
400
572
  end
401
573
 
402
574
  private
575
+ def ensure_valid_html5_tag_name(name)
576
+ raise ArgumentError, "Invalid HTML5 tag name: #{name.inspect}" unless /\A[a-zA-Z][^\s\/>]*\z/.match?(name)
577
+ end
578
+ module_function :ensure_valid_html5_tag_name
579
+
403
580
  def build_tag_values(*args)
404
581
  tag_values = []
405
582
 
@@ -10,12 +10,13 @@ module ActionView
10
10
  include FormOptionsHelper
11
11
 
12
12
  class CheckBoxBuilder < Builder # :nodoc:
13
- def check_box(extra_html_options = {})
13
+ def checkbox(extra_html_options = {})
14
14
  html_options = extra_html_options.merge(@input_html_options)
15
15
  html_options[:multiple] = true
16
16
  html_options[:skip_default_ids] = false
17
- @template_object.check_box(@object_name, @method_name, html_options, @value, nil)
17
+ @template_object.checkbox(@object_name, @method_name, html_options, @value, nil)
18
18
  end
19
+ alias_method :check_box, :checkbox
19
20
  end
20
21
 
21
22
  def render(&block)
@@ -24,7 +25,7 @@ module ActionView
24
25
 
25
26
  private
26
27
  def render_component(builder)
27
- builder.check_box + builder.label
28
+ builder.checkbox + builder.label
28
29
  end
29
30
 
30
31
  def hidden_field_name
@@ -106,7 +106,8 @@ module ActionView
106
106
 
107
107
  def hidden_field
108
108
  hidden_name = @html_options[:name] || hidden_field_name
109
- @template_object.hidden_field_tag(hidden_name, "", id: nil)
109
+ options = { id: nil, form: @html_options[:form] }
110
+ @template_object.hidden_field_tag(hidden_name, "", options)
110
111
  end
111
112
 
112
113
  def hidden_field_name