actionview 7.1.6 → 7.2.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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +99 -425
  3. data/README.rdoc +1 -1
  4. data/lib/action_view/base.rb +24 -9
  5. data/lib/action_view/cache_expiry.rb +9 -3
  6. data/lib/action_view/dependency_tracker/{ripper_tracker.rb → ruby_tracker.rb} +4 -3
  7. data/lib/action_view/dependency_tracker.rb +1 -1
  8. data/lib/action_view/digestor.rb +6 -2
  9. data/lib/action_view/gem_version.rb +2 -2
  10. data/lib/action_view/helpers/asset_tag_helper.rb +19 -7
  11. data/lib/action_view/helpers/atom_feed_helper.rb +1 -1
  12. data/lib/action_view/helpers/cache_helper.rb +2 -2
  13. data/lib/action_view/helpers/csrf_helper.rb +1 -1
  14. data/lib/action_view/helpers/form_helper.rb +222 -217
  15. data/lib/action_view/helpers/form_options_helper.rb +6 -3
  16. data/lib/action_view/helpers/form_tag_helper.rb +80 -47
  17. data/lib/action_view/helpers/output_safety_helper.rb +5 -6
  18. data/lib/action_view/helpers/tag_helper.rb +208 -18
  19. data/lib/action_view/helpers/tags/collection_helpers.rb +2 -1
  20. data/lib/action_view/helpers/text_helper.rb +11 -4
  21. data/lib/action_view/helpers/url_helper.rb +3 -77
  22. data/lib/action_view/layouts.rb +8 -10
  23. data/lib/action_view/log_subscriber.rb +8 -4
  24. data/lib/action_view/railtie.rb +0 -1
  25. data/lib/action_view/render_parser/prism_render_parser.rb +127 -0
  26. data/lib/action_view/{ripper_ast_parser.rb → render_parser/ripper_render_parser.rb} +152 -9
  27. data/lib/action_view/render_parser.rb +21 -169
  28. data/lib/action_view/renderer/abstract_renderer.rb +1 -1
  29. data/lib/action_view/renderer/partial_renderer.rb +2 -2
  30. data/lib/action_view/renderer/renderer.rb +32 -38
  31. data/lib/action_view/renderer/template_renderer.rb +3 -3
  32. data/lib/action_view/rendering.rb +4 -4
  33. data/lib/action_view/template/error.rb +11 -0
  34. data/lib/action_view/template/handlers/erb.rb +45 -37
  35. data/lib/action_view/template/renderable.rb +7 -1
  36. data/lib/action_view/template/resolver.rb +0 -2
  37. data/lib/action_view/template.rb +36 -8
  38. data/lib/action_view/test_case.rb +7 -10
  39. data/lib/action_view.rb +1 -0
  40. metadata +14 -13
@@ -496,7 +496,8 @@ module ActionView
496
496
  # <option value="France">France</option>
497
497
  # </optgroup>
498
498
  #
499
- # Parameters:
499
+ # ==== Parameters
500
+ #
500
501
  # * +grouped_options+ - Accepts a nested array or hash of strings. The first value serves as the
501
502
  # <tt><optgroup></tt> label while the second value must be an array of options. The second value can be a
502
503
  # nested array of text-value pairs. See <tt>options_for_select</tt> for more info.
@@ -507,7 +508,8 @@ module ActionView
507
508
  # which will have the +selected+ attribute set. Note: It is possible for this value to match multiple options
508
509
  # as you might have the same option in multiple groups. Each will then get <tt>selected="selected"</tt>.
509
510
  #
510
- # Options:
511
+ # ==== Options
512
+ #
511
513
  # * <tt>:prompt</tt> - set to true or a prompt string. When the select element doesn't have a value yet, this
512
514
  # prepends an option with a generic prompt - "Please select" - or the given prompt string.
513
515
  # * <tt>:divider</tt> - the divider for the options groups.
@@ -599,7 +601,8 @@ module ActionView
599
601
 
600
602
  # Returns a string of option tags for the days of the week.
601
603
  #
602
- # Options:
604
+ # ====Options
605
+ #
603
606
  # * <tt>:index_as_value</tt> - Defaults to false, set to true to use the indexes from
604
607
  # <tt>I18n.translate("date.day_names")</tt> as the values. By default, Sunday is always 0.
605
608
  # * <tt>:day_format</tt> - The I18n key of the array to use for the weekday options.
@@ -522,25 +522,6 @@ module ActionView
522
522
  # submit_tag "Edit", class: "edit_button"
523
523
  # # => <input class="edit_button" data-disable-with="Edit" name="commit" type="submit" value="Edit" />
524
524
  #
525
- # ==== Deprecated: \Rails UJS attributes
526
- #
527
- # Prior to \Rails 7, \Rails shipped with the JavaScript library called @rails/ujs on by default. Following \Rails 7,
528
- # this library is no longer on by default. This library integrated with the following options:
529
- #
530
- # * <tt>confirm: 'question?'</tt> - If present the unobtrusive JavaScript
531
- # drivers will provide a prompt with the question specified. If the user accepts,
532
- # the form is processed normally, otherwise no action is taken.
533
- # * <tt>:disable_with</tt> - Value of this parameter will be used as the value for a
534
- # disabled version of the submit button when the form is submitted. This feature is
535
- # provided by the unobtrusive JavaScript driver. To disable this feature for a single submit tag
536
- # pass <tt>:data => { disable_with: false }</tt> Defaults to value attribute.
537
- #
538
- # submit_tag "Complete sale", data: { disable_with: "Submitting..." }
539
- # # => <input name="commit" data-disable-with="Submitting..." type="submit" value="Complete sale" />
540
- #
541
- # submit_tag "Save", data: { confirm: "Are you sure?" }
542
- # # => <input name='commit' type='submit' value='Save' data-disable-with="Save" data-confirm="Are you sure?" />
543
- #
544
525
  def submit_tag(value = "Save changes", options = {})
545
526
  options = options.deep_stringify_keys
546
527
  tag_options = { "type" => "submit", "name" => "commit", "value" => value }.update(options)
@@ -582,26 +563,6 @@ module ActionView
582
563
  # # <strong>Ask me!</strong>
583
564
  # # </button>
584
565
  #
585
- # ==== Deprecated: \Rails UJS attributes
586
- #
587
- # Prior to \Rails 7, \Rails shipped with a JavaScript library called @rails/ujs on by default. Following \Rails 7,
588
- # this library is no longer on by default. This library integrated with the following options:
589
- #
590
- # * <tt>confirm: 'question?'</tt> - If present, the
591
- # unobtrusive JavaScript drivers will provide a prompt with
592
- # the question specified. If the user accepts, the form is
593
- # processed normally, otherwise no action is taken.
594
- # * <tt>:disable_with</tt> - Value of this parameter will be
595
- # used as the value for a disabled version of the submit
596
- # button when the form is submitted. This feature is provided
597
- # by the unobtrusive JavaScript driver.
598
- #
599
- # button_tag "Save", data: { confirm: "Are you sure?" }
600
- # # => <button name="button" type="submit" data-confirm="Are you sure?">Save</button>
601
- #
602
- # button_tag "Checkout", data: { disable_with: "Please wait..." }
603
- # # => <button data-disable-with="Please wait..." name="button" type="submit">Checkout</button>
604
- #
605
566
  def button_tag(content_or_options = nil, options = nil, &block)
606
567
  if content_or_options.is_a? Hash
607
568
  options = content_or_options
@@ -675,11 +636,13 @@ module ActionView
675
636
  # <% end %>
676
637
  # # => <fieldset class="format"><p><input id="name" name="name" type="text" /></p></fieldset>
677
638
  def field_set_tag(legend = nil, options = nil, &block)
678
- output = tag(:fieldset, options, true)
679
- output.safe_concat(content_tag("legend", legend)) unless legend.blank?
680
- output.concat(capture(&block)) if block_given?
681
- output.safe_concat("</fieldset>")
639
+ content = []
640
+ content << content_tag("legend", legend) unless legend.blank?
641
+ content << capture(&block) if block_given?
642
+
643
+ content_tag(:fieldset, safe_join(content), options)
682
644
  end
645
+ alias_method :fieldset_tag, :field_set_tag
683
646
 
684
647
  # Creates a text field of type "color".
685
648
  #
@@ -762,14 +725,14 @@ module ActionView
762
725
  # date_field_tag 'name'
763
726
  # # => <input id="name" name="name" type="date" />
764
727
  #
765
- # date_field_tag 'date', '01/01/2014'
766
- # # => <input id="date" name="date" type="date" value="01/01/2014" />
728
+ # date_field_tag 'date', '2014-12-31'
729
+ # # => <input id="date" name="date" type="date" value="2014-12-31" />
767
730
  #
768
731
  # date_field_tag 'date', nil, class: 'special_input'
769
732
  # # => <input class="special_input" id="date" name="date" type="date" />
770
733
  #
771
- # date_field_tag 'date', '01/01/2014', class: 'special_input', disabled: true
772
- # # => <input disabled="disabled" class="special_input" id="date" name="date" type="date" value="01/01/2014" />
734
+ # date_field_tag 'date', '2014-12-31', class: 'special_input', disabled: true
735
+ # # => <input disabled="disabled" class="special_input" id="date" name="date" type="date" value="2014-12-31" />
773
736
  def date_field_tag(name, value = nil, options = {})
774
737
  text_field_tag(name, value, options.merge(type: :date))
775
738
  end
@@ -784,6 +747,23 @@ module ActionView
784
747
  # * <tt>:max</tt> - The maximum acceptable value.
785
748
  # * <tt>:step</tt> - The acceptable value granularity.
786
749
  # * <tt>:include_seconds</tt> - Include seconds and ms in the output timestamp format (true by default).
750
+ #
751
+ # ==== Examples
752
+ #
753
+ # time_field_tag 'name'
754
+ # # => <input id="name" name="name" type="time" />
755
+ #
756
+ # time_field_tag 'time', '01:01'
757
+ # # => <input id="time" name="time" type="time" value="01:01" />
758
+ #
759
+ # time_field_tag 'time', nil, class: 'special_input'
760
+ # # => <input class="special_input" id="time" name="time" type="time" />
761
+ #
762
+ # time_field_tag 'time', '01:01', include_seconds: true
763
+ # # => <input id="time" name="time" type="time" value="01:01:00.000" />
764
+ #
765
+ # time_field_tag 'time', '01:01', min: '00:00', max: '23:59', step: 1
766
+ # # => <input id="time" max="23:59" min="00:00" name="time" step="1" type="time" value="01:01" />
787
767
  def time_field_tag(name, value = nil, options = {})
788
768
  text_field_tag(name, value, options.merge(type: :time))
789
769
  end
@@ -798,6 +778,20 @@ module ActionView
798
778
  # * <tt>:max</tt> - The maximum acceptable value.
799
779
  # * <tt>:step</tt> - The acceptable value granularity.
800
780
  # * <tt>:include_seconds</tt> - Include seconds in the output timestamp format (true by default).
781
+ #
782
+ # ==== Examples
783
+ #
784
+ # datetime_field_tag 'name'
785
+ # # => <input id="name" name="name" type="datetime-local" />
786
+ #
787
+ # datetime_field_tag 'datetime', '2014-01-01T01:01'
788
+ # # => <input id="datetime" name="datetime" type="datetime-local" value="2014-01-01T01:01" />
789
+ #
790
+ # datetime_field_tag 'datetime', nil, class: 'special_input'
791
+ # # => <input class="special_input" id="datetime" name="datetime" type="datetime-local" />
792
+ #
793
+ # datetime_field_tag 'datetime', '2014-01-01T01:01', class: 'special_input', disabled: true
794
+ # # => <input disabled="disabled" class="special_input" id="datetime" name="datetime" type="datetime-local" value="2014-01-01T01:01" />
801
795
  def datetime_field_tag(name, value = nil, options = {})
802
796
  text_field_tag(name, value, options.merge(type: "datetime-local"))
803
797
  end
@@ -813,6 +807,20 @@ module ActionView
813
807
  # * <tt>:min</tt> - The minimum acceptable value.
814
808
  # * <tt>:max</tt> - The maximum acceptable value.
815
809
  # * <tt>:step</tt> - The acceptable value granularity.
810
+ #
811
+ # ==== Examples
812
+ #
813
+ # month_field_tag 'name'
814
+ # # => <input id="name" name="name" type="month" />
815
+ #
816
+ # month_field_tag 'month', '2014-01'
817
+ # # => <input id="month" name="month" type="month" value="2014-01" />
818
+ #
819
+ # month_field_tag 'month', nil, class: 'special_input'
820
+ # # => <input class="special_input" id="month" name="month" type="month" />
821
+ #
822
+ # month_field_tag 'month', '2014-01', class: 'special_input', disabled: true
823
+ # # => <input disabled="disabled" class="special_input" id="month" name="month" type="month" value="2014-01" />
816
824
  def month_field_tag(name, value = nil, options = {})
817
825
  text_field_tag(name, value, options.merge(type: :month))
818
826
  end
@@ -826,6 +834,20 @@ module ActionView
826
834
  # * <tt>:min</tt> - The minimum acceptable value.
827
835
  # * <tt>:max</tt> - The maximum acceptable value.
828
836
  # * <tt>:step</tt> - The acceptable value granularity.
837
+ #
838
+ # ==== Examples
839
+ #
840
+ # week_field_tag 'name'
841
+ # # => <input id="name" name="name" type="week" />
842
+ #
843
+ # week_field_tag 'week', '2014-W01'
844
+ # # => <input id="week" name="week" type="week" value="2014-W01" />
845
+ #
846
+ # week_field_tag 'week', nil, class: 'special_input'
847
+ # # => <input class="special_input" id="week" name="week" type="week" />
848
+ #
849
+ # week_field_tag 'week', '2014-W01', class: 'special_input', disabled: true
850
+ # # => <input disabled="disabled" class="special_input" id="week" name="week" type="week" value="2014-W01" />
829
851
  def week_field_tag(name, value = nil, options = {})
830
852
  text_field_tag(name, value, options.merge(type: :week))
831
853
  end
@@ -934,6 +956,17 @@ module ActionView
934
956
  # ==== Options
935
957
  #
936
958
  # Supports the same options as #number_field_tag.
959
+ #
960
+ # ==== Examples
961
+ #
962
+ # range_field_tag 'quantity', '1'
963
+ # # => <input id="quantity" name="quantity" type="range" value="1" />
964
+ #
965
+ # range_field_tag 'quantity', in: 1...10
966
+ # # => <input id="quantity" name="quantity" min="1" max="9" type="range" />
967
+ #
968
+ # range_field_tag 'quantity', min: 1, max: 10, step: 2
969
+ # # => <input id="quantity" name="quantity" min="1" max="10" step="2" type="range"
937
970
  def range_field_tag(name, value = nil, options = {})
938
971
  number_field_tag(name, value, options.merge(type: :range))
939
972
  end
@@ -24,11 +24,11 @@ module ActionView # :nodoc:
24
24
  # the supplied separator, are HTML escaped unless they are HTML
25
25
  # safe, and the returned string is marked as HTML safe.
26
26
  #
27
- # safe_join([raw("<p>foo</p>"), "<p>bar</p>"], "<br />")
28
- # # => "<p>foo</p>&lt;br /&gt;&lt;p&gt;bar&lt;/p&gt;"
27
+ # safe_join([tag.p("foo"), "<p>bar</p>"], "<br>")
28
+ # # => "<p>foo</p>&lt;br&gt;&lt;p&gt;bar&lt;/p&gt;"
29
29
  #
30
- # safe_join([raw("<p>foo</p>"), raw("<p>bar</p>")], raw("<br />"))
31
- # # => "<p>foo</p><br /><p>bar</p>"
30
+ # safe_join([tag.p("foo"), tag.p("bar")], tag.br)
31
+ # # => "<p>foo</p><br><p>bar</p>"
32
32
  #
33
33
  def safe_join(array, sep = $,)
34
34
  sep = ERB::Util.unwrapped_html_escape(sep)
@@ -38,8 +38,7 @@ module ActionView # :nodoc:
38
38
 
39
39
  # Converts the array to a comma-separated sentence where the last element is
40
40
  # joined by the connector word. This is the html_safe-aware version of
41
- # ActiveSupport's {Array#to_sentence}[https://api.rubyonrails.org/classes/Array.html#method-i-to_sentence].
42
- #
41
+ # ActiveSupport's Array#to_sentence.
43
42
  def to_sentence(array, options = {})
44
43
  options.assert_valid_keys(:words_connector, :two_words_connector, :last_word_connector, :locale)
45
44
 
@@ -1,7 +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"
6
+ require "active_support/core_ext/string/inflections"
5
7
  require "set"
6
8
  require "action_view/helpers/capture_helper"
7
9
  require "action_view/helpers/output_safety_helper"
@@ -46,8 +48,183 @@ module ActionView
46
48
  include CaptureHelper
47
49
  include OutputSafetyHelper
48
50
 
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
51
+ def self.define_element(name, code_generator:, method_name: name.to_s.underscore)
52
+ code_generator.define_cached_method(method_name, namespace: :tag_builder) do |batch|
53
+ batch.push(<<~RUBY) unless instance_methods.include?(method_name.to_sym)
54
+ def #{method_name}(content = nil, escape: true, **options, &block)
55
+ tag_string("#{name}", content, options, escape: escape, &block)
56
+ end
57
+ RUBY
58
+ end
59
+ end
60
+
61
+ def self.define_void_element(name, code_generator:, method_name: name.to_s.underscore)
62
+ code_generator.define_cached_method(method_name, namespace: :tag_builder) do |batch|
63
+ batch.push(<<~RUBY)
64
+ def #{method_name}(content = nil, escape: true, **options, &block)
65
+ if content || block
66
+ ActionView.deprecator.warn <<~TEXT
67
+ Putting content inside a void element (#{name}) is invalid
68
+ according to the HTML5 spec, and so it is being deprecated
69
+ without replacement. In Rails 8.0, passing content as a
70
+ positional argument will raise, and using a block will have
71
+ no effect.
72
+ TEXT
73
+ tag_string("#{name}", content, options, escape: escape, &block)
74
+ else
75
+ self_closing_tag_string("#{name}", options, escape, ">")
76
+ end
77
+ end
78
+ RUBY
79
+ end
80
+ end
81
+
82
+ def self.define_self_closing_element(name, code_generator:, method_name: name.to_s.underscore)
83
+ code_generator.define_cached_method(method_name, namespace: :tag_builder) do |batch|
84
+ batch.push(<<~RUBY)
85
+ def #{method_name}(content = nil, escape: true, **options, &block)
86
+ if content || block
87
+ tag_string("#{name}", content, options, escape: escape, &block)
88
+ else
89
+ self_closing_tag_string("#{name}", options, escape)
90
+ end
91
+ end
92
+ RUBY
93
+ end
94
+ end
95
+
96
+ ActiveSupport::CodeGenerator.batch(self, __FILE__, __LINE__) do |code_generator|
97
+ define_void_element :area, code_generator: code_generator
98
+ define_void_element :base, code_generator: code_generator
99
+ define_void_element :br, code_generator: code_generator
100
+ define_void_element :col, code_generator: code_generator
101
+ define_void_element :embed, code_generator: code_generator
102
+ define_void_element :hr, code_generator: code_generator
103
+ define_void_element :img, code_generator: code_generator
104
+ define_void_element :input, code_generator: code_generator
105
+ define_void_element :keygen, code_generator: code_generator
106
+ define_void_element :link, code_generator: code_generator
107
+ define_void_element :meta, code_generator: code_generator
108
+ define_void_element :source, code_generator: code_generator
109
+ define_void_element :track, code_generator: code_generator
110
+ define_void_element :wbr, code_generator: code_generator
111
+
112
+ define_self_closing_element :animate, code_generator: code_generator
113
+ define_self_closing_element :animateMotion, code_generator: code_generator
114
+ define_self_closing_element :animateTransform, code_generator: code_generator
115
+ define_self_closing_element :circle, code_generator: code_generator
116
+ define_self_closing_element :ellipse, code_generator: code_generator
117
+ define_self_closing_element :line, code_generator: code_generator
118
+ define_self_closing_element :path, code_generator: code_generator
119
+ define_self_closing_element :polygon, code_generator: code_generator
120
+ define_self_closing_element :polyline, code_generator: code_generator
121
+ define_self_closing_element :rect, code_generator: code_generator
122
+ define_self_closing_element :set, code_generator: code_generator
123
+ define_self_closing_element :stop, code_generator: code_generator
124
+ define_self_closing_element :use, code_generator: code_generator
125
+ define_self_closing_element :view, code_generator: code_generator
126
+
127
+ define_element :a, code_generator: code_generator
128
+ define_element :abbr, code_generator: code_generator
129
+ define_element :address, code_generator: code_generator
130
+ define_element :article, code_generator: code_generator
131
+ define_element :aside, code_generator: code_generator
132
+ define_element :audio, code_generator: code_generator
133
+ define_element :b, code_generator: code_generator
134
+ define_element :bdi, code_generator: code_generator
135
+ define_element :bdo, code_generator: code_generator
136
+ define_element :blockquote, code_generator: code_generator
137
+ define_element :body, code_generator: code_generator
138
+ define_element :button, code_generator: code_generator
139
+ define_element :canvas, code_generator: code_generator
140
+ define_element :caption, code_generator: code_generator
141
+ define_element :cite, code_generator: code_generator
142
+ define_element :code, code_generator: code_generator
143
+ define_element :colgroup, code_generator: code_generator
144
+ define_element :data, code_generator: code_generator
145
+ define_element :datalist, code_generator: code_generator
146
+ define_element :dd, code_generator: code_generator
147
+ define_element :del, code_generator: code_generator
148
+ define_element :details, code_generator: code_generator
149
+ define_element :dfn, code_generator: code_generator
150
+ define_element :dialog, code_generator: code_generator
151
+ define_element :div, code_generator: code_generator
152
+ define_element :dl, code_generator: code_generator
153
+ define_element :dt, code_generator: code_generator
154
+ define_element :em, code_generator: code_generator
155
+ define_element :fieldset, code_generator: code_generator
156
+ define_element :figcaption, code_generator: code_generator
157
+ define_element :figure, code_generator: code_generator
158
+ define_element :footer, code_generator: code_generator
159
+ define_element :form, code_generator: code_generator
160
+ define_element :h1, code_generator: code_generator
161
+ define_element :h2, code_generator: code_generator
162
+ define_element :h3, code_generator: code_generator
163
+ define_element :h4, code_generator: code_generator
164
+ define_element :h5, code_generator: code_generator
165
+ define_element :h6, code_generator: code_generator
166
+ define_element :head, code_generator: code_generator
167
+ define_element :header, code_generator: code_generator
168
+ define_element :hgroup, code_generator: code_generator
169
+ define_element :html, code_generator: code_generator
170
+ define_element :i, code_generator: code_generator
171
+ define_element :iframe, code_generator: code_generator
172
+ define_element :ins, code_generator: code_generator
173
+ define_element :kbd, code_generator: code_generator
174
+ define_element :label, code_generator: code_generator
175
+ define_element :legend, code_generator: code_generator
176
+ define_element :li, code_generator: code_generator
177
+ define_element :main, code_generator: code_generator
178
+ define_element :map, code_generator: code_generator
179
+ define_element :mark, code_generator: code_generator
180
+ define_element :menu, code_generator: code_generator
181
+ define_element :meter, code_generator: code_generator
182
+ define_element :nav, code_generator: code_generator
183
+ define_element :noscript, code_generator: code_generator
184
+ define_element :object, code_generator: code_generator
185
+ define_element :ol, code_generator: code_generator
186
+ define_element :optgroup, code_generator: code_generator
187
+ define_element :option, code_generator: code_generator
188
+ define_element :output, code_generator: code_generator
189
+ define_element :p, code_generator: code_generator
190
+ define_element :picture, code_generator: code_generator
191
+ define_element :portal, code_generator: code_generator
192
+ define_element :pre, code_generator: code_generator
193
+ define_element :progress, code_generator: code_generator
194
+ define_element :q, code_generator: code_generator
195
+ define_element :rp, code_generator: code_generator
196
+ define_element :rt, code_generator: code_generator
197
+ define_element :ruby, code_generator: code_generator
198
+ define_element :s, code_generator: code_generator
199
+ define_element :samp, code_generator: code_generator
200
+ define_element :script, code_generator: code_generator
201
+ define_element :search, code_generator: code_generator
202
+ define_element :section, code_generator: code_generator
203
+ define_element :select, code_generator: code_generator
204
+ define_element :slot, code_generator: code_generator
205
+ define_element :small, code_generator: code_generator
206
+ define_element :span, code_generator: code_generator
207
+ define_element :strong, code_generator: code_generator
208
+ define_element :style, code_generator: code_generator
209
+ define_element :sub, code_generator: code_generator
210
+ define_element :summary, code_generator: code_generator
211
+ define_element :sup, code_generator: code_generator
212
+ define_element :table, code_generator: code_generator
213
+ define_element :tbody, code_generator: code_generator
214
+ define_element :td, code_generator: code_generator
215
+ define_element :template, code_generator: code_generator
216
+ define_element :textarea, code_generator: code_generator
217
+ define_element :tfoot, code_generator: code_generator
218
+ define_element :th, code_generator: code_generator
219
+ define_element :thead, code_generator: code_generator
220
+ define_element :time, code_generator: code_generator
221
+ define_element :title, code_generator: code_generator
222
+ define_element :tr, code_generator: code_generator
223
+ define_element :u, code_generator: code_generator
224
+ define_element :ul, code_generator: code_generator
225
+ define_element :var, code_generator: code_generator
226
+ define_element :video, code_generator: code_generator
227
+ end
51
228
 
52
229
  def initialize(view_context)
53
230
  @view_context = view_context
@@ -62,28 +239,22 @@ module ActionView
62
239
  tag_options(attributes.to_h).to_s.strip.html_safe
63
240
  end
64
241
 
65
- def p(*arguments, **options, &block)
66
- tag_string(:p, *arguments, **options, &block)
242
+ def tag_string(name, content = nil, options, escape: true, &block)
243
+ content = @view_context.capture(self, &block) if block
244
+
245
+ content_tag_string(name, content, options, escape)
67
246
  end
68
247
 
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
248
+ def self_closing_tag_string(name, options, escape = true, tag_suffix = " />")
249
+ "<#{name}#{tag_options(options, escape)}#{tag_suffix}".html_safe
77
250
  end
78
251
 
79
252
  def content_tag_string(name, content, options, escape = true)
80
253
  tag_options = tag_options(options, escape) if options
81
254
 
82
- if escape
83
- name = ERB::Util.xml_name_escape(name)
255
+ if escape && content.present?
84
256
  content = ERB::Util.unwrapped_html_escape(content)
85
257
  end
86
-
87
258
  "<#{name}#{tag_options}>#{PRE_CONTENT_STRINGS[name]}#{content}</#{name}>".html_safe
88
259
  end
89
260
 
@@ -163,8 +334,12 @@ module ActionView
163
334
  true
164
335
  end
165
336
 
166
- def method_missing(called, *args, **options, &block)
167
- tag_string(called, *args, **options, &block)
337
+ def method_missing(called, *args, escape: true, **options, &block)
338
+ name = called.name.dasherize
339
+
340
+ TagHelper.ensure_valid_html5_tag_name(name)
341
+
342
+ tag_string(name, *args, options, escape: escape, &block)
168
343
  end
169
344
  end
170
345
 
@@ -245,6 +420,14 @@ module ActionView
245
420
  # # A void element:
246
421
  # tag.br # => <br>
247
422
  #
423
+ # Note that when using the block form options should be wrapped in
424
+ # parenthesis.
425
+ #
426
+ # <%= tag.a(href: "/about", class: "font-bold") do %>
427
+ # About the author
428
+ # <% end %>
429
+ # # => <a href="/about" class="font-bold">About the author</a>
430
+ #
248
431
  # === Building HTML attributes
249
432
  #
250
433
  # Transforms a Hash into HTML attributes, ready to be interpolated into
@@ -310,7 +493,7 @@ module ActionView
310
493
  if name.nil?
311
494
  tag_builder
312
495
  else
313
- name = ERB::Util.xml_name_escape(name) if escape
496
+ ensure_valid_html5_tag_name(name)
314
497
  "<#{name}#{tag_builder.tag_options(options, escape) if options}#{open ? ">" : " />"}".html_safe
315
498
  end
316
499
  end
@@ -344,6 +527,8 @@ module ActionView
344
527
  # <% end -%>
345
528
  # # => <div class="strong">Hello world!</div>
346
529
  def content_tag(name, content_or_options_with_block = nil, options = nil, escape = true, &block)
530
+ ensure_valid_html5_tag_name(name)
531
+
347
532
  if block_given?
348
533
  options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash)
349
534
  tag_builder.content_tag_string(name, capture(&block), options, escape)
@@ -400,6 +585,11 @@ module ActionView
400
585
  end
401
586
 
402
587
  private
588
+ def ensure_valid_html5_tag_name(name)
589
+ raise ArgumentError, "Invalid HTML5 tag name: #{name.inspect}" unless /\A[a-zA-Z][^\s\/>]*\z/.match?(name)
590
+ end
591
+ module_function :ensure_valid_html5_tag_name
592
+
403
593
  def build_tag_values(*args)
404
594
  tag_values = []
405
595
 
@@ -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
@@ -166,7 +166,7 @@ module ActionView
166
166
  # highlight('You searched for: rails', 'rails', highlighter: '<a href="search?q=\1">\1</a>')
167
167
  # # => "You searched for: <a href=\"search?q=rails\">rails</a>"
168
168
  #
169
- # highlight('You searched for: rails', 'rails') { |match| link_to(search_path(q: match, match)) }
169
+ # highlight('You searched for: rails', 'rails') { |match| link_to(search_path(q: match)) }
170
170
  # # => "You searched for: <a href=\"search?q=rails\">rails</a>"
171
171
  #
172
172
  # highlight('<a href="javascript:alert(\'no!\')">ruby</a> on rails', 'rails', sanitize: false)
@@ -260,7 +260,14 @@ module ActionView
260
260
  prefix, first_part = cut_excerpt_part(:first, first_part, separator, options)
261
261
  postfix, second_part = cut_excerpt_part(:second, second_part, separator, options)
262
262
 
263
- affix = [first_part, separator, phrase, separator, second_part].join.strip
263
+ affix = [
264
+ first_part,
265
+ !first_part.empty? ? separator : "",
266
+ phrase,
267
+ !second_part.empty? ? separator : "",
268
+ second_part
269
+ ].join.strip
270
+
264
271
  [prefix, affix, postfix].join
265
272
  end
266
273
 
@@ -271,7 +278,7 @@ module ActionView
271
278
  #
272
279
  # The word will be pluralized using rules defined for the locale
273
280
  # (you must define your own inflection rules for languages other than English).
274
- # See ActiveSupport::Inflector.pluralize
281
+ # See ActiveSupport::Inflector.pluralize.
275
282
  #
276
283
  # pluralize(1, 'person')
277
284
  # # => "1 person"
@@ -346,7 +353,7 @@ module ActionView
346
353
  # ==== Options
347
354
  # * <tt>:sanitize</tt> - If +false+, does not sanitize +text+.
348
355
  # * <tt>:sanitize_options</tt> - Any extra options you want appended to the sanitize.
349
- # * <tt>:wrapper_tag</tt> - String representing the wrapper tag, defaults to <tt>"p"</tt>
356
+ # * <tt>:wrapper_tag</tt> - String representing the wrapper tag, defaults to <tt>"p"</tt>.
350
357
  #
351
358
  # ==== Examples
352
359
  # my_text = "Here is some basic text...\n...with a line break."